karmada-io / karmada

Open, Multi-Cloud, Multi-Cluster Kubernetes Orchestration
https://karmada.io
Apache License 2.0
4.39k stars 867 forks source link

Karmada plugin shoule use User Impersonation to limit access permission expansion #5485

Open xigang opened 1 week ago

xigang commented 1 week ago

What happened:

When the request reaches the federated cluster, if the cache and cluster plugin cannot handle it, the request reaches the karmada plugin, which uses the admin permissions to access the federated cluster kube-apiserver, causing the cluster access permissions amplification problem.

https://github.com/karmada-io/karmada/blob/master/pkg/search/proxy/framework/plugins/karmada/karmada.go#L81

What you expected to happen:

We should use User Impersionation to control user access rights.

How to reproduce it (as minimally and precisely as possible):

Anything else we need to know?:

Environment:

XiShanYongYe-Chang commented 1 week ago

@xigang Thanks for your feedback! Any idea to solve this issue?

xigang commented 1 week ago

@xigang Thanks for your feedback! Any idea to solve this issue?

@XiShanYongYe-Chang Kubernetes User Impersionation can be used to control the requested permissions.

I have implemented it simply. Is there any problem with this?

Sample code:

func (p *Karmada) Connect(ctx context.Context, request framework.ProxyRequest) (http.Handler, error) {
    return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
        if p.secretLister == nil {
            // This should not happen unless it's a test environment
            klog.Errorf("secret lister is not initialized")
            responsewriters.InternalError(rw, req, errors.New("secret lister is not initialized"))
            return
        }

        secretGetter := func(ctx context.Context, namespace, name string) (*corev1.Secret, error) {
            return p.secretLister.Secrets(namespace).Get(name)
        }

        secret, err := secretGetter(ctx, "karmada-system", "clusterfed-api")
        if err == nil {
            requester, exist := endpointrequest.UserFrom(req.Context())
            if !exist {
                responsewriters.InternalError(rw, req, errors.New("no user found for request"))
                return
            }

            req.Header.Set(authenticationv1.ImpersonateUserHeader, requester.GetName())
            for _, group := range requester.GetGroups() {
                if !skipGroup(group) {
                    req.Header.Add(authenticationv1.ImpersonateGroupHeader, group)
                }
            }

            impersonateToken, err := getImpersonateToken(secret)
            if err != nil {
                errorMsg := fmt.Sprintf("failed to get impresonate token: %v", err)
                responsewriters.InternalError(rw, req, errors.New(errorMsg))
                return
            }

            req.Header.Set("Authorization", fmt.Sprintf("bearer %s", impersonateToken))
        }

        location, transport := p.resourceLocation()
        location.Path = path.Join(location.Path, request.ProxyPath)
        location.RawQuery = req.URL.RawQuery

        handler := proxy.NewThrottledUpgradeAwareProxyHandler(location, transport, true, false, request.Responder)
        handler.ServeHTTP(rw, req)
    }), nil
}
xigang commented 1 week ago

/cc @RainbowMango @ikaven1024 Let’s take a look together. Is there any problem?

XiShanYongYe-Chang commented 1 week ago

which uses the admin permissions to access the federated cluster kube-apiserver, causing the cluster access permissions amplification problem.

Hi @xigang can you give an example of the impact of this operation?

xigang commented 1 week ago

which uses the admin permissions to access the federated cluster kube-apiserver, causing the cluster access permissions amplification problem.

Hi @xigang can you give an example of the impact of this operation?

@XiShanYongYe-Chang Look at the following diagram to illustrate the path a request takes:

image

  1. First, for example, when creating a deployment workload, the request will first reach the federated cluster kube-apiserver, using the redis-user user.
  2. If the request is not intercepted by the cache and cluster plugin, the request will reach the karmada plugin, but because the request has been completed using redis user authentication and authorization, the karmada plugin will use the admin user for authentication and authorization again. (This will cause the request permission to be enlarged)
XiShanYongYe-Chang commented 1 week ago

Thank you for your detailed explanation. Will this permission magnification cause some damage to the system? For example, can a read-only user modify or delete resources on the karmada control plane through this operation?

xigang commented 1 week ago

Thank you for your detailed explanation. Will this permission magnification cause some damage to the system? For example, can a read-only user modify or delete resources on the karmada control plane through this operation?

Yes, read-only users will have the permission to modify and delete resources on the karmada control plane, but member clusters will not be affected. you can test this scenario.

XiShanYongYe-Chang commented 1 week ago

cc @ikaven1024 @cmicat

Hi @ikaven1024 @cmicat, would you like to have a look?