Featured image of post Kubernetes Authorization via OIDC

Kubernetes Authorization via OIDC

Using Google and Authentik for authorization in Kubernetes

Introduction

To authenticate in Kubernetes through kubeconfig for access via kubectl, ServiceAccounts are generally used. They are simply easier to create, but this is not the right approach. Tokens from ServiceAccounts are accessible to anyone who can read secrets in the namespace where the ServiceAccount was created, which means that actions can be taken on your behalf. Not secure…

You can create a certificate, sign it in the cluster, and operate as a User. This is much better; no one can do anything on your behalf because we generated the private part locally and did not share it. I’ll explain how to do this since I’ve figured it out.

Recently, I finally tried Authentik and also attempted to link it to the cluster via API server configuration. It turned out to be even simpler in some aspects than dealing with certificates for users. This is what I will talk about.

Access via ServiceAccount

Let’s create a ServiceAccount.

1
kubectl create serviceaccount test

Previously, before Kubernetes 1.22, a token was automatically generated; now it needs to be created separately. You can generate a temporary one.

1
kubectl create token test --duration=10m

Alternatively, you can obtain a permanent token by creating a secret for it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: test-sa-secret
  annotations:
    kubernetes.io/service-account.name: test
type: kubernetes.io/service-account-token
EOF
SA_TOKEN="$(kubectl get -o yaml secret test-sa-secret -o jsonpath='{.data.token}' | base64 -d)"

Now we can write it in kubeconfig and use it.

1
2
3
4
kubectl config current-context | \
xargs kubectl config get-contexts --no-headers | \
awk '{print $4}' | \
xargs kubectl config set-credentials "--token=${SA_TOKEN}"

Creating a User

This part is a bit more complicated. You need to create a certificate, a signing request, sign it, retrieve the public part, and only then are we ready to proceed. I’m attaching my script, but I don’t want to describe all of this. I’m focusing on OIDC, so consider this as a nice bonus 😄

How to use it:

  1. Write your name in the variable NAME
  2. Write your company name in the variable GROUP
  3. Change CLUSTERROLE if you really want to
  4. Run the script and read the instructions on the screen
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
NAME='john-johnson'
GROUP='asdfqwer'
CLUSTERROLE='cluster-admin'

openssl ecparam -genkey -name prime256v1 | openssl ec -out "$NAME.key"
KEY_BASE64="$(base64 -w0 "$NAME.key")"
openssl req -new -key "$NAME.key" -out "$NAME.csr" -subj "/CN=$NAME/O=$GROUP"
CSR_BASE64="$(base64 -w0 "$NAME.csr")"
rm -f "$NAME.csr" "$NAME.key"

cat <<EOF
#  ┬ ┐┐─┐┬─┐┬─┐  ┌─┐┬─┐┬─┐┬─┐┌┐┐o┌─┐┌┐┐
#  │ │└─┐├─ │┬┘  │  │┬┘├─ │─┤ │ ││ ││││
#  ┘─┘──┘┴─┘┘└┘  └─┘┘└┘┴─┘┘ ┘ ┘ ┘┘─┘┘└┘

1. Please, execute the following code using kubectl that has access to the cluster (using default admin credentials, for example)

kubectl apply -f - <<EOT
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: $GROUP:$NAME
spec:
  expirationSeconds: 31536000 # 1 year
  signerName: kubernetes.io/kube-apiserver-client
  usages: ["client auth"]
  username: $NAME
  request: $CSR_BASE64
EOT
kubectl certificate approve '$GROUP:$NAME'
kubectl apply -f - <<EOT
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: user:${NAME}:${CLUSTERROLE}
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: $CLUSTERROLE
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: $NAME
EOT
kubectl get -o jsonpath='{.status.certificate}' csr '$GROUP:$NAME'
echo

2. Please, execute the following code locally, on your PC, where you want to have those credentials inserted

kubectl config set "users.$GROUP:$NAME.client-key-data" "$KEY_BASE64"
kubectl config set "users.$GROUP:$NAME.client-certificate-data" "-" --set-raw-bytes=true

3. Now place the certificate that has been printed during step #1 in your kubeconfig client-certificate-data for user $GROUP:$NAME marker
4. Specify $GROUP:$NAME user in the context you want to use
EOF

Google OIDC

  1. Configure the OAuth consent screen

  2. Create OAuth2 credentials of type Web application, Authorized redirect URIs - in the list below.

    1
    2
    3
    4
    
    http://127.0.0.1:18000
    http://127.0.0.1:8000
    http://localhost:18000
    http://localhost:8000
    
  3. As a result, you will get the Client ID and Client Secret.

Great, now you need to install the kubectl oidc-login plugin.

After installation, try to ensure everything works.

1
2
3
4
kubectl oidc-login setup \
  --oidc-issuer-url=https://accounts.google.com \
  --oidc-client-id='YOUR CLIENT ID' \
  --oidc-client-secret='YOUR CLIENT SECRET'

If everything is fine, your browser should open, you log in as someone, and a big beautiful instruction will appear in the console. Something like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
authentication in progress...
Opening in existing browser session.

## 2. Verify authentication

You got a token with the following claims:

{
  "iss": "https://accounts.google.com",
  "azp": "longalphanum",
  "aud": "longalphanum",
  "sub": "longalphanum",
  "at_hash": "longalphanum",
  "nonce": "longalphanum",
  "iat": longalphanum,
  "exp": longalphanum
}

## 3. Bind a cluster role

Run the following command:

    kubectl create clusterrolebinding oidc-cluster-admin --clusterrole=cluster-admin --user='https://accounts.google.com#12345678901234567890'

## 4. Set up the Kubernetes API server

Add the following options to the kube-apiserver:

    --oidc-issuer-url=https://accounts.google.com
    --oidc-client-id=longalphanum

## 5. Set up the kubeconfig

Run the following command:

    kubectl config set-credentials oidc \
      --exec-api-version=client.authentication.k8s.io/v1beta1 \
      --exec-command=kubectl \
      --exec-arg=oidc-login \
      --exec-arg=get-token \
      --exec-arg=--oidc-issuer-url=https://accounts.google.com \
      --exec-arg=--oidc-client-id=longalphanum \
      --exec-arg=--oidc-client-secret=longalphanum

## 6. Verify cluster access

Make sure you can access the Kubernetes cluster.

    kubectl --user=oidc get nodes

You can switch the default context to oidc.

    kubectl config set-context --current

 --user=oidc

Authentik

This tool is great! Among its direct analogs, it’s worth mentioning Okta and Keycloak. All three products are excellent; Okta is often considered the benchmark. Other options may be better or worse, but they are always compared to Okta. 😄

I installed Authentik from the official chart. It’s running locally, and I needed a certificate. I obtained one using Certbot with DNS verification. After that, we proceed as follows:

  1. Applications > Providers

    Create a Kubernetes provider of type OAuth2/OIDC

    • Client type: Confidential
    • Client ID: remember this
    • Client Secret: remember this
    • Redirect URIs: http://(127.0.0.1|localhost):1?8000(/.*)?
  2. Application > Applications

    Create an application Kubernetes and select the provider you created in step 1.

  3. System

    Create users (surprise, right?) and also create groups. I made kubernetes:admin and kubernetes:restricted. The first group will be assigned the cluster-admin role in Kubernetes, while the second will have read-only access. Don’t forget to add users to the corresponding groups.

  4. System > Certificates

    I’m not sure about this step; I just haven’t checked it without a certificate. I uploaded the certificate obtained via Certbot here, and then selected this certificate in Application > Providers > Kubernetes > Edit > Signing Key. Kubernetes didn’t complain, so it might be possible to use a self-signed certificate, but I’m not sure; you’ll have to try it yourself.

  5. Customization > Property Mappings

    Create a mapping with the following parameters:

    • Name: Kubernetes

    • Scope name: kubernetes

    • Expression

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      all_groups = [
        "kubernetes:admin",
        "kubernetes:restricted"
      ]
      return dict(
        groups=[
          group for group in all_groups
          if ak_is_group_member(request.user, name=group)
        ]
      )
      

    This way, for those who request this scope, Authentik will append the groups the user belongs to, iterating only through the Kubernetes groups.

  6. Verify kube-oidc-login

    1
    2
    3
    4
    5
    
    kubectl oidc-login setup \
      --oidc-issuer-url=https://authentik.example.com/application/o/kubernetes/  \
      --oidc-client-id='YOUR CLIENT ID' \
      --oidc-client-secret='YOUR CLIENT SECRET' \
      --oidc-extra-scope=kubernetes
    

    Everything should work; just follow the instructions.

  7. Grant permissions to the group in Kubernetes

    Authentik will pass the group list, and now we need Kubernetes to pay attention to this. Add the following arguments to the API server:

    1
    2
    
    --oidc-groups-claim=groups
    --oidc-groups-prefix=oidc:
    

    Now you can create resources like these:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: oidc:kubernetes:admin
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: cluster-admin
    subjects:
    - apiGroup: rbac.authorization.k8s.io
      kind: Group
      name: oidc:kubernetes:admin
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: this-name-is-custom
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: view
    subjects:
    - apiGroup: rbac.authorization.k8s.io
      kind: Group
      name: oidc:kubernetes:restricted
    
All rights reserved
Built with Hugo
Theme Stack designed by Jimmy