5 min read

Implement Oauth2 Proxy on Kubernetes

Sometimes, we may want to add authentication features to our service running on Kubernetes, but the application itself does not include authentication capabilities.

Fortunately, there is a third-party application that we can use to address this issue, called oauth2-proxy.

Oauth2-proxy is a third-party application that we can use as centralized authentication for other services in Kubernetes.

Oauth2-proxy is a reverse proxy and static file server that provides authentication using Providers (Google, Keycloak, GitHub and others) to validate accounts by email, domain or group.

Requirements

  1. Kubernetes Cluster v1.18+
  2. Public Domain for oauth2-proxy ingress
  3. Nginx Ingress Controller
  4. Helm Installed
  5. Cert Manager for TLS Ingress

Diagram

Preparation

The public domains I've set up as example are oauth.rjhaikal.my.id for oauth2-proxy and code.rjhaikal.my.id for the app.

You can also see how I deploy the applications used in this example on the following blog.

Setup Code Server: VS Code in the browser
Run VS Code on any machine anywhere and access it in the browser. Requirements See requirements for minimum specs, as well as instructions on how to set up a Google VM on which you can install code-server. TL;DR: Linux machine with WebSockets enabled, 1 GB RAM, and 2 vCPUs

Setup Azure AD

1. Register an application

Update the redirect URI to your suit your domain and protocol.

https://<yourdomain>/oauth2/callback

Once your application is created take a note of the Application (client) ID on the overview page. We will need this later.

Within our application registration we will also need to create a client secret to be used to identify our application. We could also use a certificate for higher assurance however for this example a secret will suffice.

Save this secret, and denote the following information under the 'Overview' Tab for later

  • Secret Key
  • App (Client) ID
  • Directory (Tenant) ID

Setup Redis

1. Create namespace

kubectl create ns rj-oauth2

2. Add Redis repo

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

3. edit values

vim values.yml
---
global:
  storageClass: <your storageclass>
  redis:
    password: "YourSecurePassword!"
helm install redis bitnami/redis --values=values.yml -n rj-oauth2
export REDIS_PASSWORD=$(kubectl get secret --namespace rj-oauth2 redis -o jsonpath="{.data.redis-password}" | base64 --decode)

Oauth2 Proxy Installation

1. Collate and Deploy Secrets

dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d -- '\n' | tr -- '+/' '-_'; echo

SGEOwUnlydJJGWG3YYcrxLUscUdv00Cg4Jb_ewcvAx4=
export COOKIE_SECRET="SGEOwUnlydJJGWG3YYcrxLUscUdv00Cg4Jb_ewcvAx4="
export AZURE_TENANT_ID="yyyyyyyyyyyyyyy"
export AZURE_CLIENT_ID="xxxxxxxxxxxxxxx"
export AZURE_CLIENT_SECRET="zzzzzzzzzzzzzz"
kubectl create secret generic oauth2-proxy-creds -n rj-oauth2 \
    --from-literal=cookie-secret="$COOKIE_SECRET" \
    --from-literal=azure-tenant="$AZURE_TENANT_ID" \
    --from-literal=client-id="$AZURE_CLIENT_ID" \
    --from-literal=client-secret="$AZURE_CLIENT_SECRET"

2. Install Oauth2 Proxy

vim oauth2-proxy.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    application: oauth2-proxy
  name: oauth2-proxy
  namespace: rj-oauth2
spec:
  replicas: 1
  selector:
    matchLabels:
      application: oauth2-proxy
  template:
    metadata:
      labels:
        application: oauth2-proxy
    spec:
      containers:
      - args:
        - --provider=azure
        - --azure-tenant=<tenant-id> # Microsoft Entra ID OAuth2 Proxy application Tenant ID
        - --pass-access-token=true
        - --cookie-name=_proxycookie # this can be any name of your choice which you would like OAuth2 Proxy to use for the session cookie
        - --email-domain=*
        - --upstream=file:///dev/null
        - --http-address=0.0.0.0:4180
        - --oidc-issuer-url=https://login.microsoftonline.com/<tenant-id>/v2.0
        - --whitelist-domain=.rjhaikal.my.id
        - --cookie-domain=.rjhaikal.my.id
        - --cookie-secure=false
        - --cookie-csrf-per-request=true
        - --cookie-csrf-expire=5m
        - --redirect-url=https://oauth.rjhaikal.my.id/oauth2/callback
        - --session-store-type=redis
        - --reverse-proxy=true
        - --redis-connection-url=redis://redis-master.rj-oauth2.svc.cluster.local:6379
        - --redis-password=<redis-password>
        name: oauth2-proxy
        image: quay.io/oauth2-proxy/oauth2-proxy:v7.5.1
        imagePullPolicy: Always
        env:
        - name: OAUTH2_PROXY_CLIENT_ID # keep this name - it's required to be defined like this by OAuth2 Proxy
          valueFrom:
            secretKeyRef:
              name: oauth2-proxy-creds
              key: client-id
        - name: OAUTH2_PROXY_CLIENT_SECRET # keep this name - it's required to be defined like this by OAuth2 Proxy
          valueFrom:
            secretKeyRef:
              name: oauth2-proxy-creds
              key: client-secret
        - name: OAUTH2_PROXY_COOKIE_SECRET # keep this name - it's required to be defined like this by OAuth2 Proxy
          valueFrom:
            secretKeyRef:
              name: oauth2-proxy-creds
              key: cookie-secret
        ports:
        - containerPort: 4180
          protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  labels:
    application: oauth2-proxy
  name: oauth2-proxy-svc
  namespace: rj-oauth2
spec:
  ports:
  - name: http
    port: 4180
    protocol: TCP
    targetPort: 4180
  selector:
    application: oauth2-proxy
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: letsencrypt
    nginx.ingress.kubernetes.io/proxy-body-size: "2000m"
    nginx.ingress.kubernetes.io/proxy-buffer-size: "32k"
  name: oauth2-proxy-ingress
  namespace: rj-oauth2
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - 'oauth.rjhaikal.my.id'
      secretName: rjhaikal.my.id
  rules:
    - host: oauth.rjhaikal.my.id
      http:
        paths:
          - path: /oauth2
            pathType: Prefix
            backend:
              service:
                name: oauth2-proxy-svc
                port:
                  number: 4180

Reconfigure Code Server (Application)


Changing the authentication to "none" because I will be using External Authentication.

sudo vim /home/code-user/.config/code-server/config.yaml
---
bind-addr: 127.0.0.1:8080
auth: none  # change to this
password: [REDACTED]
cert: false
sudo systemctl restart [email protected]

Add OAuth2 Proxy annotations for Code Server Ingress

This service will depend on oauth2-proxy. If a user accesses the ingress of this service, the service will redirect the user's request to oauth2-proxy to check whether the user has been authenticated or not.

1. Add proper OAuth2 Proxy annotations to Code Server Ingress

cat<<EOF | kubectl replace -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/auth-url: "https://oauth.rjhaikal.my.id/oauth2/auth" # <- Add this
    nginx.ingress.kubernetes.io/auth-signin: "https://oauth.rjhaikal.my.id/oauth2/start" # <- Add this
  name: rj-codeserver-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: code.rjhaikal.my.id
    http:
      paths:
      - backend:
          service:
            name: rj-codeserver-service
            port:
              number: 8080
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - 'code.rjhaikal.my.id'
    secretName: code-rjhaikal-secret
EOF

Try accessing code.rjhaikal.my.id and ensure that the request is redirected to oauth.rjhaikal.my.id, then further redirected to Azure AD. After login, it should be redirected back to code.rjhaikal.my.id.