J W W W . T I F F A N Y F A Y . D E V AUTHENTICATION & AUTHORIZATION • AUTHN (authentication): who are you? • AUTHZ (authorization): what are you allowed to do?
J W W W . T I F F A N Y F A Y . D E V PROVISIONING USERS At least three possibilities: • certificates ◦ can use your own CA (e.g. Vault), or Kubernetes' ◦ warning: Kubernetes API server doesn't support revocation, so you need short-lived certs • OIDC tokens ◦ can use an auth provider of your choice (e.g. okta, keycloak…) or something linked to your cloud's IAM • (ab)use serviceaccounts to provision users (a service account is really just a user named system:serviceaccount:<namespace>:<serviceaccountname>)
J W W W . T I F F A N Y F A Y . D E V • Example creation with OpenSSL: # Generate key and CSR for our user openssl genrsa 4096 > user.key openssl req -new -key user.key \ -subj /CN=ada.lovelace/O=devs/O=ops > user.csr • After that, transfer the CSR to the CA CERTIFICATES
J W W W . T I F F A N Y F A Y . D E V # Copy the CSR to the CA # (for instance, a kubeadm-deployed control plane node) # Then generate the cert: sudo openssl x509 -req \ -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key \ -in user.csr -days 1 -set_serial 1234 > user.crt # Copy certificate (user.crt) back to the user! CERTIFICATES – SELF-HOSTED
J W W W . T I F F A N Y F A Y . D E V • Or, we can use Kubernetes CA through the CSR API • The Kubernetes cluster admin can submit the CSR like this: kubectl apply -f - <<EOF apiVersion: certificates.k8s.io/v1 kind: CertificateSigningRequest metadata: name: user=ada.lovelace spec: #expirationSeconds: 3600 request: $(base64 -w0 < user.csr) signerName: kubernetes.io/kube-apiserver-client usages: - digital signature - key encipherment - client auth EOF CERTIFICATES – THROUGH THE CSR API (1)
J W W W . T I F F A N Y F A Y . D E V CERTIFICATES – THROUGH THE CSR API (2) • Then approve it: kubectl certificate approve user=ada.lovelace • And retrieve the certificate like this: kubectl get csr user=ada.lovelace -o jsonpath={.status.certificate} | base64 -d > user.crt • Now give back the user.crt file to the user!
J W W W . T I F F A N Y F A Y . D E V ONCE WE HAVE OUR CERTIFICATE… # Add the key and cert to our kubeconfig file like this: kubectl config set-credentials ada.lovelace \ --client-key=user.key --client-certificate=user.crt
J W W W . T I F F A N Y F A Y . D E V • OIDC is conceptually similar to TLS (but different set of protocols) • On self-hosted clusters, you'd need to add a few command-line flags to API server: ◦ --oidc-issuer-url → URL of the OpenID provider ◦ --oidc-client-id → OpenID app requesting the authentication (=our cluster) • More details on k8s.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens • On managed clusters, there may be ways to achieve the same results, e.g. on EKS: docs.aws.amazon.com/eks/latest/userguide/authenticate-oidc-identity-provider.html TOKENS: OIDC
J W W W . T I F F A N Y F A Y . D E V TOKENS: SERVICE ACCOUNT • A Service Account is just a user with a funny name: "system:serviceaccount:<namespace>:<serviceaccountname>" • "Service Account Tokens" are JWT generated by the Kubernetes control plane • By default, in each container, Kubernetes will automatically place a token in that file: /var/run/secrets/kubernetes.io/serviceaccount/token • That token is a token for the Service Account of the Pod that the container belongs to • Kubernetes client libraries know to automatically detect and use that token • We're going to see that in practice!
on a slide just to mention them, but who cares!) • quick explainer of the concept: "you put permissions in ROLES then you bind the roles to users/groups/serviceaccounts" • example: we gonna [give someone access to a namespace] or [set up permissions for an autoscaler] (or something else if you feel like these examples have been done too many times?) • DEMO FLOW! • then at the end, mention the tools (kubectl auth can-i, access matrix, etc?)
J W W W . T I F F A N Y F A Y . D E V RBAC High level idea on Kubernetes: 1. Define a ROLE (or ClusterRole), which is a collection of permissions ("things that can be done") e.g. list pods 2. Bind the ROLE to a USER or GROUP or SERVICEACCOUNT (with a RoleBinding or ClusterRoleBinding)
J W W W . T I F F A N Y F A Y . D E V RBAC • kubectl get --raw /api/v1 (core resources with apiVersion: v1) • kubectl get --raw /apis/<group>/<version> (for other resources)
J W W W . T I F F A N Y F A Y . D E V Example Role apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: get-pods namespace: devoxx rules: - apiGroups: - "" resources: - pods verbs: - get - list
J W W W . T I F F A N Y F A Y . D E V Example RoleBinding apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: get-pods namespace: devoxx roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: get-pods subjects: - kind: ServiceAccount name: default namespace: devoxx
J W W W . T I F F A N Y F A Y . D E V RBAC AUDITING After setting permissions, audit them: • kubectl auth can-i --list • kubectl who-can / kubectl-who-can by Aqua Security • kubectl access-matrix / Rakkess (Review Access) by Cornelius Weig • kubectl rbac-lookup / RBAC Lookup by FairwindsOps • kubectl rbac-tool / RBAC Tool by insightCloudSec
J W W W . T I F F A N Y F A Y . D E V k create ns devoxx kubens devoxx k create deploy nginx --image=nginx:1.24.0 k create deploy web --image=nginx:1.24.0 k get pods k run -it tester --rm --image=nixery.dev/shell/kubectl/curl/jq -- sh #check that if we "kubectl get pods" in the pod (it won't work) kubectl get pods kubectl auth can-i --list WINDOW 2 WINDOW 1 DEMO P0
J W W W . T I F F A N Y F A Y . D E V #create a role that can get pods k create role get-pods \ --verb=get --verb=list \ --resource=pods #bind role (create RoleBinding) to the NS default SA k create rolebinding get-pods --role=get-pods --serviceaccount=devoxx:default kubectl get pods #now "kubectl get pods -v6" so we see the req URL kubectl get pods -v6 #-k, --insecure Allow insecure server connections when using SSL Basically I don't care about the cert shown to me by the Kubernetes API server. I trust that I am talking to my cluster and not some impersonator curl https://$IP:443/api/v1/namespaces/devoxx/ pods -k WINDOW 2 WINDOW 1 DEMO: USING DEFAULT NS SA
J W W W . T I F F A N Y F A Y . D E V cat /var/run/secrets/kubernetes.io/serviceaccount/token #copy it and paste it in jwt.io to see what it shows TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) We can also find the HOST:PORT in env as $KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT env curl https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/devoxx/pods -k -H "Authorization: Bearer $TOKEN" | jq .items[].metadata.name #Gets API server certificate curl https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/devoxx/pods -H "Authorization: Bearer $TOKEN" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt | jq .items[].metadata.name WINDOW 2 DEMO: USING DEFAULT NS SA
J W W W . T I F F A N Y F A Y . D E V k run -it pirate --rm --image=nixery.dev/shell/kubectl/curl/jq -- sh TOKEN="<ctrl-v>" #Gets API server certificate curl https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/devoxx/pods -H "Authorization: Bearer $TOKEN" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt | jq .items[].metadata.name #since the token is gone this should fail curl https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/devoxx/pods -H "Authorization: Bearer $TOKEN" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt | jq exit WINDOW 1 #In window 2 type exit and hit enter in the pod and wait for it to be completely gone DEMO: BOUND SERVICE ACCOUNT TOKENS
J W W W . T I F F A N Y F A Y . D E V #create new sa called scaler k create sa scaler #create pod using this new service account k run -it scaler --rm --image=nixery.dev/shell/kubectl/curl/jq --overrides='{ "spec": { "serviceAccount": "scaler" } }' -- sh WINDOW 2 WINDOW 1 DEMO: CREATING A NEW SA
J W W W . T I F F A N Y F A Y . D E V #add ability to view resources using an existing cluster role k create clusterrolebinding scaler-view --clusterrole=view --serviceaccount=devoxx:scaler #verify it works kubectl get all #you only have view, so this should fail kubectl scale deployment nginx --replicas=2 WINDOW 2 WINDOW 1 DEMO: FACTORY ROLES
J W W W . T I F F A N Y F A Y . D E V #create a role that can scale deployments k create role scaler --verb=patch --resource=deployments/scale --resource-name=nginx #bind role (create RoleBinding) to the scaler SA k create rolebinding scaler --role=scaler --serviceaccount=devoxx:scaler kubectl scale deployment nginx --replicas=2 #will fail since it is tied to nginx kubectl scale deployment web --replicas=2 kubectl delete deployment nginx WINDOW 2 WINDOW 1 DEMO: SCALER
J W W W . T I F F A N Y F A Y . D E V #remove scaler-view clusterrolebinding k delete clusterrolebinding scaler-view #will fail since you can't get the deployment anymore kubectl scale deployment nginx --replicas=2 WINDOW 2 WINDOW 1 DEMO: SCALER
J W W W . T I F F A N Y F A Y . D E V #edit scaler role to add get to the nginx deployment k edit role scaler #Add another apigroup under the rules like this: rules: - apiGroups: - apps resourceNames: - nginx resources: - deployments verbs: - get kubectl scale deploy nginx --replicas=1 #will fail since you can't get any other resources kubectl get deploy kubectl get deploy nginx exit k delete ns devoxx WINDOW 2 WINDOW 1 DEMO: SCALER