Integrate Keycloak and Kubeapps on AWS EKS
Tags: aws, eks, keycloak, kubernetes
Published: 2022-09-25 09:47:15

In this article, I am going to show you how to integrate keycloak with kubeapps on AWS EKS.

**Steps:**
- Create [EKS Cluster](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html)

- Register a domain in [route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-register.html) or create a subdomain, ex: example.com 
- Request a certificate from [ACM](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html)

- Install Keycloak
```
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-release bitnami/keycloak --version 9.8.1
```
No needs to use the service Loadbalancer for keycloak as we will use the Nginx ingress controller.
Let's switch the service of keycloak to ClusterIP. 


```
kubectl patch svc my-release-keycloak  -p '{"spec": {"type": "ClusterIP"}}'
```

Note the admin password of keycloak
```
kubectl get secrets/my-release-keycloak -o jsonpath='{.data.admin-password}' | base64 --decode
```

- Install Nginx Ingress Controller
Update the AWS ACM certificate on the below installation

```
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install nginx ingress-nginx/ingress-nginx --set controller.service.type=LoadBalancer  --set controller.service.targetPorts.https=http --set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-ssl-cert"="arn:aws:acm:eu-west-1:XXXX:certificate/XXXX-XXX-XXX-XXXX-XXXXX"  --set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-backend-protocol"=http --set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-ssl-ports"=443   --set-string controller.config.use-forwarded-headers="true" --version 4.2.3
```

- Create ingress for keycloak 
```
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-buffer-size: 10k
  name: keycloak
  namespace: default
spec:
  rules:
  - host: auth.example.com
    http:
      paths:
      - backend:
          service:
            name: my-release-keycloak
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
```

Login to the keycloak console https://auth.example.com, with username: **user** and password in the keycloak installation step.


Create a new realm, name it kubeapps

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lxumh4q0r0jg2txorxt1.png)

**Groups Claim**
By default, there is no "groups" scope/claim. We will create a global client scope for groups.

In the admin console:

- Click "Client Scopes" from the left navigator menu
- Click on "Create" from the table (top right corner)
- Provide a name, ensure the protocol is set to "openid-connect" and that the option "Include in Token Scope" is on.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3171x6bn8f15h5urr3ig.png)

Once the client scope is created, you should be redirected to a page with several tabs. Navigate to the "Mappers" tab as we need to create a mapper to populate the value of the associated claim:

- Click on the "Mappers" tab
- Click on "Create" from the table to create a new mapper
- Configure:
- Enter a name
- Select "Group Membership" as the claim type
- Enter "groups" as the token claim name
- Ensure the "Full group path" is OFF
- Keep the other knobs as ON
- Click ‘Save'


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mek8it15nuzx46mpgwx0.png)

**Kubeapps Client on Keycloak**

- Click "Clients" from the left navigator
- Click "Create" from the table
- Enter an "id" and Save (e.g. kubeapps)
Once created, configure the authentication as follows:

- Ensure the protocol is set to "openid-connect"
- Configure the "Access Type" to be "confidential". This will add a new "Credentials" tab from which you can get the client secret
- Ensure "Standard Flow Enabled" is enabled, this is required for the login screen.
- "Direct Access Grants Enabled" can be disabled.
- In the "Valid Redirect URIs" field, enter "https://kubeapps.example.com/*" as a placeholder. 
- Save

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6uxznimcwrb6s2a54ny.png)


Note: take copy of the secret in Credentials

As for the cluster clients, we need to configure the client scopes:

- Click the "Client Scopes" tab
- Ensure the "email" scope is available either in the "Assigned Default Client Scopes" list or the "Assigned Optional Client Scopes" list
- The "groups" client scope should be available in the lists on the left. 
- Add it either to the "Assigned Default Client 
Scopes" list or the "Assigned Optional Client Scopes" list.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zbo351dsfhe8l2u00be.png)

**Note the issuer URL from keycloak Realm**

Realm Settings →  Endpoints →  OpenID Endpoint Configuration then copy the issuer URL 
ex: https://auth.example.com/realms/kubeapps

**On the EKS Cluster:**
To the EKS cluster and then to Authentication

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eeflewcj54yni07jmeii.png)

Add the identity provider like the following:
Issuer URL: https://auth.example.com/realms/kubeapps
Client ID: kubernetes
Username claim: email
Groups claim: groups

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z3gsc3byyr7jw0dfw4j3.png)

the process takes around 20 minutes.

**Back to Keycloak**

Create a new Client
- Click "Clients" from the left navigator
- Click "Create" from the table
- Enter an "id" kubernetes and Save 

Once created, configure the authentication as follows:

- Ensure the protocol is set to "openid-connect"
- Configure the "Access Type" to be "confidential"
- turn on the "Standard Flow Enabled"
- Ensure "Direct Access Grants Enabled" is enabled, as this is how we can get the tokens via API
- Save


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ajiv1wzsh5se57l8mapo.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ylt3qpb9hi31i6pe57mj.png)

**On Kubeapps client**

Clients → Mappers → Create 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u2ni584i81vqxnbog7l9.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x8qo8a8niu8vyaodns7z.png)

**Create a group**
The group name is admin
Groups --> New

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myqwclblaxaa0oijky5i.png)

**Add users to join the group**
Users --> Your User --> Groups --> choose admin --> join

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/esb025hcealj9xi8kh63.png)

**Install Kubeapps**

Update the following:

- clientSecret gets it from the above step Kubeapps Client on keycloak
- issuer-url and redirect-url for your domain.

```
helm repo add bitnami https://charts.bitnami.com/bitnami

helm install kubeapps bitnami/kubeapps \
  --set authProxy.enabled=true \
  --set authProxy.provider=oidc \
  --set authProxy.clientID=kubeapps \
  --set authProxy.clientSecret=<secret client credentials in the kubeapps client> \
  --set authProxy.cookieSecret=$(echo "not-good-secret" | base64) \
  --set authProxy.extraFlags="{--cookie-secure=false,--oidc-issuer-url=https://auth.example.com/realms/kubeapps,--redirect-url=https://kubeapps.example.com/oauth2/callback}" \
```

Create an ingress for kubeapps

```
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-buffer-size: 10k
  name: kubeapps
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: kubeapps.example.com
    http:
      paths:
      - backend:
          service:
            name: kubeapps
            port:
              number: 80
        pathType: ImplementationSpecific
 
```
Important to add proxy-buffer-size on the ingress controller otherwise you will get an error that says "upstream sent too big header while reading response header from upstream"


Create a new user on the keycloak, make sure you add email, and Email Verified is on.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfrbtu7115jwngq2rz0k.png)

The last step we need to create a role binding to access the Kubernetes cluster, this group has the same name admin as the keycloak group 

```
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  managedFields:
  - apiVersion: rbac.authorization.k8s.io/v1
  name: keycloak-admin-group
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: admin
```

Finally, log in to Kubeapps at https://kubeapps.example.com


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n18tdhos7zkhmqc9n85a.png)


**If you want to connect to EKS cluster by using OIDC**

Install [kubelogin](https://github.com/int128/kubelogin):

- Go to keycloak and then go back to the Kubernetes client we created. You will see a tab called Credentials, grab the client secret.
- Run the below command in your terminal to verify authentication to keycloak:

```
kubectl oidc-login setup --oidc-issuer-url=https://auth.example.com/realms/kubeapps --oidc-client-id=kubernetes --oidc-client-secret=XXXXXXXXXXX
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atbb6x77yt7zg1k6sso2.png)

**Bind a cluster role**

```
kubectl create clusterrolebinding oidc-cluster-admin --clusterrole=cluster-admin --user='https://auth.example.com/realms/kubeapps#f5092396-d3b8-452c-9d1a-2b1dfbd58718'

```

**Set up the kubeconfig**
Use the same secret of Kubernetes client
```
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://auth.example.com/realms/kubeapps \
	  --exec-arg=--oidc-client-id=kubernetes \
	  --exec-arg=--oidc-client-secret=XXXXXXX \
          --exec-arg=--oidc-extra-scope=groups
```

**Verify cluster access**

```
kubectl --user=oidc get nodes
```

**Switch the default context to oidc**
```
kubectl config set-context --current --user=oidc
```

In case you have issues, you need to check if your token has added admin group:

 **"groups": [
    "admin"
  ]**

By
```
curl -L -X POST 'https://auth.example.com/realms/kubeapps/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=kubernetes' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_secret=XXXXXXX' \
--data-urlencode 'scope=openid email groups' \
--data-urlencode 'username=mohamed.radwan' \
--data-urlencode 'password=XXXXX' \
| jq -r .id_token
```
check your token by [jwt.io](https://jwt.io/)


**Sources**:
https://docs.aws.amazon.com/eks/latest/userguide/authenticate-oidc-identity-provider.html
https://github.com/vmware-tanzu/kubeapps/blob/main/site/content/docs/latest/howto/OIDC/OAuth2OIDC-keycloak.md