Search code examples
kubernetespermissionsdocker-desktopkubernetes-security

Kubernetes service account default permissions


I am experimenting with service accounts. I believe the following should produce an access error (but it doesn't):

apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-sa

---

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  serviceAccountName: test-sa
  containers:
  - image: alpine
    name: test-container
    command: [sh]
    args:
    - -ec
    - |
      apk add curl;
      KUBE_NAMESPACE="$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)";
      curl \
        --cacert "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" \
        -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
        "https://kubernetes.default.svc/api/v1/namespaces/$KUBE_NAMESPACE/services";
      while true; do sleep 1; done;
kubectl apply -f test.yml
kubectl logs test-pod

What I see is a successful listing of services, but I would expect a permissions error because I never created any RoleBindings or ClusterRoleBindings for test-sa.

I'm struggling to find ways to list the permissions available to a particular SA, but according to Kubernetes check serviceaccount permissions, it should be possible with:

kubectl auth can-i list services --as=system:serviceaccount:default:test-sa
> yes

Though I'm skeptical whether that command is actually working, because I can replace test-sa with any gibberish and it still says "yes".


According to the documentation, service accounts by default have "discovery permissions given to all authenticated users". It doesn't say what that actually means, but from more reading I found this resource which is probably what it's referring to:

kubectl get clusterroles system:discovery -o yaml
> [...]
> rules:
> - nonResourceURLs:
>   - /api
>   - /api/*
> [...]
>   verbs:
>   - get

Which would imply that all service accounts have get permissions on all API endpoints, though the "nonResourceURLs" bit implies this wouldn't apply to APIs for resources like services, even though those APIs live under that path… (???)


If I remove the Authorization header entirely, I see an access error as expected. But I don't understand why it's able to get data using this empty service account. What's my misunderstanding and how can I restrict permissions correctly?


Solution

  • It turns out this is a bug in Docker Desktop for Mac's Kubernetes support.

    It automatically adds a ClusterRoleBinding giving cluster-admin to all service accounts (!). It only intends to give this to service accounts inside the kube-system namespace.

    It was originally raised in docker/for-mac#3694 but fixed incorrectly. I have raised a new issue docker/for-mac#4774 (the original issue is locked due to age).

    A quick fix while waiting for the bug to be resolved is to run:

    kubectl apply -f - <<EOF
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: docker-for-desktop-binding
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: cluster-admin
    subjects:
    - apiGroup: rbac.authorization.k8s.io
      kind: Group
      name: system:serviceaccounts:kube-system
    EOF
    

    I don't know if that might cause issues with future Docker Desktop upgrades but it does the job for now.

    With that fixed, the code above correctly gives a 403 error, and would require the following to explicitly grant access to the services resource:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: service-reader
    rules:
    - apiGroups: [""]
      resources: [services]
      verbs: [get, list]
    
    ---
    
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: test-sa-service-reader-binding
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: service-reader
    subjects:
    - kind: ServiceAccount
      name: test-sa
    

    A useful command for investigating is kubectl auth can-i --list --as system:serviceaccount, which shows the rogue permissions were applying to all service accounts:

    Resources                       Non-Resource URLs   Resource Names   Verbs
    *.*                             []                  []               [*]
                                    [*]                 []               [*]
    [...]