OAuth2 Proxy with Nginx and Azure AD: A Secure Setup Guide

In this article , we would explain how to set up Oauth2 proxy in your organization using Azure , ArgoCD to set up more security during your application access.

What is Oauth2 proxy ?

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

In our article , we will explain how to set up a simple hello world app ( Not Kibana and elastic search )

Prerequisites.

  • ArgoCD
  • AKS with Nginx Ingress controller installed.
  • Application registred in Application registration in Azure.
  • Working app ( Hello World application ).
  • GitHub repository

1- Install Nginx ingress controller in your AKS kubernetes cluster.

Click on this link and follow the Microsoft Documentation.

Once installed make sure that ingress controller pod is up and running.

2- Deploy a demo application “ Hello World”

  apiVersion: v1
  kind: Service
  metadata:
    name: hello-world
    namespace: demo
  spec:
    ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: http
    selector:
      app: hello-world
    sessionAffinity: None
    type: ClusterIP
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: hello-world
  name: hello-world
  namespace: demo
spec:
  containers:
  - image: gcr.io/google-samples/hello-app:1.0
    imagePullPolicy: Always
    name: hello-world
    ports:
    - containerPort: 8080
      name: http
      protocol: TCP

And once deployed , make sure the pod is running perfectly

Now we have to register our application in Application registration service of Azure.

We will chose to set up the Redirect URI to this below address.https://medium-demo.com/oauth2/callback

3- Register the application in Azure App registration service

Once we set it up and saved it , we will be routed to the below page.

We can see above that our application is registered and that helps Azure to expose some parameters for us like ClientID and TenantID , however we still need to create some credentials for our Client , Let’s do it.

We click on “Add a certificate or secret

Once we click we will be routed to the below page.

We name our secret and we set the expiry date and then we save everything.

Our secret is now created ( Sorry I had to hide it ).

Please make sure to save it somewhere.

4- Deploy Oauth2 manifest on AKS kubernetes using ArgoCD.

So for this step , we could deploy manually Oauth2 manifests but in order to put ourselves in a real situation in a real project , I prefer we proceed this way as it is more professional ( we could package it with helm but this will make the article longer ) , so I will point to my GitHub repository to pull the required manifest.

In order to deploy it using GitOps approach , we would need to have ArgoCD installed on your AKS cluster.

After logging in ArgoCD , we need to add the repository , in our case , I forked the helm chart into my personal GitHub repository.

Right after that , we create a new app in ArgoCD where we call our Oauth2 repository.

Our Oauth2 proxy has several credentials to be filled, and as we can see below, there is 3 secrets to be created ( client-id , client-secret , cookies-secret ).

In order to create these secrets , we need to run the below commands.

kubectl create secret generic client-id --from-literal=oauth2_proxy_client_id=CLIENT_ID -n medium

kubectl create secret generic client-secret --from-literal=SECRET_ID -n medium

kubectl create secret generic cookie-secret --from-literal=oauth2_proxy_cookie_secret=LOOK AT THE COMMAND BELOW -n medium


For Cookies Secret , it could be generated using the below command:
dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d -- '\n' | tr -- '+/' '-_'; echo
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    meta.helm.sh/release-name: kubecost # can be removed if your application isn\'t deployed through Helm
    meta.helm.sh/release-namespace: kubecost # can be removed if your application isn\'t deployed through Helm
  labels:
    application: kubecost-oauth2-proxy
    app.kubernetes.io/managed-by: Helm # can be removed if your application isn\'t deployed through Helm
  name: kubecost-oauth2-proxy-deployment
  namespace: medium
spec:
  replicas: 1
  selector:
    matchLabels:
      application: kubecost-oauth2-proxy
  template:
    metadata:
      labels:
        application: kubecost-oauth2-proxy
    spec:
      containers:
      - args:
        - --provider=oidc
        - --azure-tenant=ADD YOUR TENANT ID FROM AZURE # Azure AD 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="http://ADD YOUR FQDN :80" #we changed to this FQDN
        - --http-address=0.0.0.0:4180
        - --oidc-issuer-url=https://login.microsoftonline.com/ADD YOUR TENANT ID /v2.0
        name: kubecost-oauth2-proxy
        image: quay.io/oauth2-proxy/oauth2-proxy:v7.3.0
        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: client-id
              key: oauth2_proxy_client_id
        - name: OAUTH2_PROXY_CLIENT_SECRET # keep this name - it\'s required to be defined like this by OAuth2 Proxy
          valueFrom:
            secretKeyRef:
              name: client-secret
              key: oauth2_proxy_client_secret
        - name: OAUTH2_PROXY_COOKIE_SECRET # keep this name - it\'s required to be defined like this by OAuth2 Proxy
          valueFrom:
            secretKeyRef:
              name: cookie-secret
              key: oauth2_proxy_cookie_secret
        ports:
        - containerPort: 4180
          protocol: TCP
        resources:
          limits:
            cpu: 100m
            memory: 128Mi
          requests:
            cpu: 100m
            memory: 128Mi

Then we create the service.

apiVersion: v1
kind: Service
metadata:
  annotations:
    meta.helm.sh/release-name: kubecost # can be removed if your application isn\'t deployed through Helm
    meta.helm.sh/release-namespace: kubecost # can be removed if your application isn\'t deployed through Helm
  labels:
    application: kubecost-oauth2-proxy
    app.kubernetes.io/managed-by: Helm # can be removed if your application isn\'t deployed through Helm
  name: kubecost-oauth2-proxy-svc
  namespace: medium
spec:
  ports:
  - name: http
    port: 4180
    protocol: TCP
    targetPort: 4180
  selector:
    application: kubecost-oauth2-proxy

And finally the Ingress ( We will skip the TLS and SSL termination here )

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    cert-manager.io/cluster-issuer: letsencrypt
    nginx.ingress.kubernetes.io/proxy-body-size: "2000m"
    nginx.ingress.kubernetes.io/proxy-buffer-size: "32k"
    meta.helm.sh/release-name: kubecost # can be removed if your application isn\'t deployed through Helm
    meta.helm.sh/release-namespace: kubecost # can be removed if your application isn\'t deployed through Helm
  labels:
    app.kubernetes.io/managed-by: Helm # can be removed if your application isn\'t deployed through Helm
  name: kubecost-oauth2-proxy-ingress
  namespace: medium
spec:
  ingressClassName: nginx
   # tls:
   #  - hosts:
   #      - kubecost-kris.unicorndomain.com
   #   secretName: kubecost-oauth2-proxy-ingress-tls-secret
  rules:
    - host: medium-demo.com
      http:
        paths:
          - path: /oauth2
            pathType: Prefix
            backend:
              service:
                name: kubecost-oauth2-proxy-svc
                port:
                  number: 4180

Once all objects are set up in your GitHub repository , ArgoCD will pull the manifests and creates the objects on Kubernetes.

5- Deploy Hello World Application

In that stage , it is very simple as we will do it with ArgoCD like before , so I would skip the steps as they are the same.

This deployment is very simple but there is something we need to explain here , It is the ingress Object.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubecost-cost-analyzer
  namespace: medium
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /oauth2
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "route"
    nginx.ingress.kubernetes.io/session-cookie-hash: "sha1"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
    nginx.ingress.kubernetes.io/proxy-body-size: "2000m"
    nginx.ingress.kubernetes.io/proxy-buffer-size: "32k"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    meta.helm.sh/release-name: medium
    meta.helm.sh/release-namespace: medium
    nginx.ingress.kubernetes.io/auth-url: "http://medium-demo.com/oauth2/auth"
    nginx.ingress.kubernetes.io/auth-signin: "http://medium-demo.com/oauth2/start?rd=https://medium-demo.com/oauth2/callback"
  labels:
    app: cost-analyzer
    app.kubernetes.io/instance: kubecost
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: cost-analyzer
spec:
  ingressClassName: nginx
   #tls:
   #- hosts:
   # - kubecost-kris.unicorndomain.com
   # secretName: kubecost-cost-analyzer-tls-cert
  rules:
  - host: medium-demo.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: hello-world
            port:
              number: 80