Files
container.training/slides/k8s/openid-connect.md
2019-06-26 09:53:17 -05:00

8.7 KiB

OpenID Connect

  • The Kubernetes API server can perform authentication with OpenID connect

  • This requires an OpenID provider

    (external authorization server using the OAuth 2.0 protocol)

  • We can use a third-party provider (e.g. Google) or run our own (e.g. Dex)

  • We are going to give an overview of the protocol

  • We will show it in action (in a simplified scenario)


Workflow overview

  • We want to access our resources (a Kubernetes cluster)

  • We authenticate with the OpenID provider

    • we can do this directly (e.g. by going to https://accounts.google.com)

    • or maybe a kubectl plugin can open a browser page on our behalf

  • After authenticating us, the OpenID provider gives us:

    • an id token (a short-lived signed JSON Web Token, see next slide)

    • a refresh token (to renew the id token when needed)

  • We can now issue requests to the Kubernetes API with the id token

  • The API server will verify that token's content to authenticate us


JSON Web Tokens

  • A JSON Web Token (JWT) has three parts:

    • a header specifying algorithms and token type

    • a payload (indicating who issued the token, for whom, which purposes...)

    • a signature generated by the issuer (the issuer = the OpenID provider)

  • Anyone can verify a JWT without contacting the issuer

    (except to obtain the issuer's public key)

  • Pro tip: we can inspect a JWT with https://jwt.io/


How the Kubernetes API uses JWT

  • Server side

    • enable OIDC authentication

    • indicate which issuer (provider) should be allowed

    • indicate which audience (or "client id") should be allowed

    • optionally, map or prefix user and group names

  • Client side

    • obtain JWT as described earlier

    • pass JWT as authentication token

    • renew JWT when needed (using the refresh token)


Demo time!

  • We will use Google Accounts as our OpenID provider

  • We will use the Google OAuth Playground as the "audience" or "client id"

  • We will obtain a JWT through Google Accounts and the OAuth Playground

  • We will enable OIDC in the Kubernetes API server

  • We will use the JWT to authenticate

.footnote[If you can't or won't use a Google account, you can try to adapt this to another provider.]


Checking the API server logs

  • The API server logs will be particularly useful in this section

    (they will indicate e.g. why a specific token is rejected)

  • Let's keep an eye on the API server output!

.exercise[

  • Tail the logs of the API server:
    kubectl logs kube-apiserver-node1 --follow --namespace=kube-system
    

]


Authenticate with the OpenID provider

  • We will use the Google OAuth Playground for convenience

  • In a real scenario, we would need our own OAuth client instead of the playground

    (even if we were still using Google as the OpenID provider)

.exercise[

  • Open the Google OAuth Playground:

    https://developers.google.com/oauthplayground/
    
  • Enter our own custom scope in the text field:

    https://www.googleapis.com/auth/userinfo.email
    
  • Click on "Authorize APIs" and allow the playground to access our email address

]


Obtain our JSON Web Token

  • The previous step gave us an "authorization code"

  • We will use it to obtain tokens

.exercise[

  • Click on "Exchange authorization code for tokens"

]

  • The JWT is the very long id_token that shows up on the right hand side

    (it is a base64-encoded JSON object, and should therefore start with eyJ)


Using our JSON Web Token

  • We need to create a context (in kubeconfig) for our token

    (if we just add the token or use kubectl --token, our certificate will still be used)

.exercise[

  • Create a new authentication section in kubeconfig:

    kubectl config set-credentials myjwt --token=eyJ...
    
  • Try to use it:

    kubectl --user=myjwt get nodes
    

]

We should get an Unauthorized response, since we haven't enabled OpenID Connect in the API server yet. We should also see invalid bearer token in the API server log output.


Enabling OpenID Connect

  • We need to add a few flags to the API server configuration

  • These two are mandatory:

    --oidc-issuer-url → URL of the OpenID provider

    --oidc-client-id → app requesting the authentication
    (in our case, that's the ID for the Google OAuth Playground)

  • This one is optional:

    --oidc-username-claim → which field should be used as user name
    (we will use the user's email address instead of an opaque ID)

  • See the API server documentation for more details about all available flags


Updating the API server configuration

  • The instructions below will work for clusters deployed with kubeadm

    (or where the control plane is deployed in static pods)

  • If your cluster is deployed differently, you will need to adapt them

.exercise[

  • Edit /etc/kubernetes/manifests/kube-apiserver.yaml

  • Add the following lines to the list of command-line flags:

    - --oidc-issuer-url=https://accounts.google.com
    - --oidc-client-id=407408718192.apps.googleusercontent.com
    - --oidc-username-claim=email
    

]


Restarting the API server

  • The kubelet monitors the files in /etc/kubernetes/manifests

  • When we save the pod manifest, kubelet will restart the corresponding pod

    (using the updated command line flags)

.exercise[

  • After making the changes described on the previous slide, save the file

  • Issue a simple command (like kubectl version) until the API server is back up

    (it might take between a few seconds and one minute for the API server to restart)

  • Restart the kubectl logs command to view the logs of the API server

]


Using our JSON Web Token

  • Now that the API server is set up to recognize our token, try again!

.exercise[

  • Try an API command with our token:
    kubectl --user=myjwt get nodes
    kubectl --user=myjwt get pods
    

]

We should see a message like:

Error from server (Forbidden): nodes is forbidden: User "jean.doe@gmail.com"
cannot list resource "nodes" in API group "" at the cluster scope

→ We were successfully authenticated, but not authorized.


Authorizing our user

  • As an extra step, let's grant read access to our user

  • We will use the pre-defined ClusterRole view

.exercise[

  • Create a ClusterRoleBinding allowing us to view resources:

      kubectl create clusterrolebinding i-can-view \
              --user=`jean.doe@gmail.com` --clusterrole=view
    

    (make sure to put your Google email address there)

  • Confirm that we can now list pods with our token:

    kubectl --user=myjwt get pods
    

]


From demo to production

.warning[This was a very simplified demo! In a real deployment...]

  • We wouldn't use the Google OAuth Playground

  • We probably wouldn't even use Google at all

    (it doesn't seem to provide a way to include groups!)

  • Some popular alternatives:

  • We would use a helper (like the kubelogin plugin) to automatically obtain tokens


class: extra-details

Service Account tokens

  • The tokens used by Service Accounts are JWT tokens as well

  • They are signed and verified using a special service account key pair

.exercise[

  • Extract the token of a service account in the current namespace:

    kubectl get secrets -o jsonpath={..token} | base64 -d
    
  • Copy-paste the token to a verification service like https://jwt.io

  • Notice that it says "Invalid Signature"

]


class: extra-details

Verifying Service Account tokens

  • JSON Web Tokens embed the URL of the "issuer" (=OpenID provider)

  • The issuer provides its public key through a well-known discovery endpoint

    (similar to https://accounts.google.com/.well-known/openid-configuration)

  • There is no such endpoint for the Service Account key pair

  • But we can provide the public key ourselves for verification


class: extra-details

Verifying a Service Account token

  • On clusters provisioned with kubeadm, the Service Account key pair is:

    /etc/kubernetes/pki/sa.key (used by the controller manager to generate tokens)

    /etc/kubernetes/pki/sa.pub (used by the API server to validate the same tokens)

.exercise[

  • Display the public key used to sign Service Account tokens:

    sudo cat /etc/kubernetes/pki/sa.pub
    
  • Copy-paste the key in the "verify signature" area on https://jwt.io

  • It should now say "Signature Verified"

]