Kubernetes Ingress

Guard your Kubernetes ingress with Kanidm authentication and authorization.

Prerequisites

We recommend you have the following before continuing:

Instructions

  1. Create a Kanidm account and group:

    1. Create a Kanidm account. Please see the section Creating Accounts.
    2. Give the account a password. Please see the section Resetting Account Credentials.
    3. Make the account a person. Please see the section People Accounts.
    4. Create a Kanidm group. Please see the section Creating Accounts.
    5. Add the account you created to the group you create. Please see the section Creating Accounts.
  2. Create a Kanidm OAuth2 resource:

    1. Create the OAuth2 resource for your domain. Please see the section Create the Kanidm Configuration.
    2. Add a scope mapping from the resource you created to the group you create with the openid, profile, and email scopes. Please see the section Create the Kanidm Configuration.
  3. Create a Cookie Secret to for the placeholder <COOKIE_SECRET> in step 4:

    docker run -ti --rm python:3-alpine python -c 'import secrets,base64; print(base64.b64encode(base64.b64encode(secrets.token_bytes(16))).decode("utf-8"));'
    
  4. Create a file called k8s.kanidm-nginx-auth-example.yaml with the block below. Replace every <string> (drop the <>) with appropriate values:

    1. <FQDN>: The fully qualified domain name with an A record pointing to your k8s ingress.
    2. <KANIDM_FQDN>: The fully qualified domain name of your Kanidm deployment.
    3. <COOKIE_SECRET>: The output from step 3.
    4. <OAUTH2_RS_NAME>: Please see the output from step 2.1 or get the OAuth2 resource you create from that step.
    5. <OAUTH2_RS_BASIC_SECRET>: Please see the output from step 2.1 or get the OAuth2 resource you create from that step.

    This will deploy the following to your cluster:

    ---
    apiVersion: v1
    kind: Namespace
    metadata:
      name: kanidm-example
      labels:
        pod-security.kubernetes.io/enforce: restricted
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      namespace: kanidm-example
      name: website
      labels:
        app: website
    spec:
      revisionHistoryLimit: 1
      replicas: 1
      selector:
        matchLabels:
          app: website
      template:
        metadata:
          labels:
            app: website
        spec:
          containers:
            - name: website
              image: modem7/docker-starwars
              imagePullPolicy: Always
              ports:
                - containerPort: 8080
              securityContext:
                allowPrivilegeEscalation: false
                capabilities:
                  drop: ["ALL"]
          securityContext:
            runAsNonRoot: true
            seccompProfile:
              type: RuntimeDefault
    
    ---
    apiVersion: v1
    kind: Service
    metadata:
      namespace: kanidm-example
      name: website
    spec:
      selector:
        app: website
      ports:
        - protocol: TCP
          port: 8080
          targetPort: 8080
    
    ---
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      annotations:
        cert-manager.io/cluster-issuer: lets-encrypt-cluster-issuer
        nginx.ingress.kubernetes.io/auth-url: "https://$host/oauth2/auth"
        nginx.ingress.kubernetes.io/auth-signin: "https://$host/oauth2/start?rd=$escaped_request_uri"
      name: website
      namespace: kanidm-example
    spec:
      ingressClassName: nginx
      tls:
        - hosts:
            - <FQDN>
          secretName: <FQDN>-ingress-tls # replace . with - in the hostname
      rules:
      - host: <FQDN>
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: website
                port:
                  number: 8080
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        k8s-app: oauth2-proxy
      name: oauth2-proxy
      namespace: kanidm-example
    spec:
      replicas: 1
      selector:
        matchLabels:
          k8s-app: oauth2-proxy
      template:
        metadata:
          labels:
            k8s-app: oauth2-proxy
        spec:
          containers:
          - args:
            - --provider=oidc
            - --email-domain=*
            - --upstream=file:///dev/null
            - --http-address=0.0.0.0:4182
            - --oidc-issuer-url=https://<KANIDM_FQDN>/oauth2/openid/<OAUTH2_RS_NAME>
            - --code-challenge-method=S256
            env:
            - name: OAUTH2_PROXY_CLIENT_ID
              value: <OAUTH2_RS_NAME>
            - name: OAUTH2_PROXY_CLIENT_SECRET
              value: <OAUTH2_RS_BASIC_SECRET>
            - name: OAUTH2_PROXY_COOKIE_SECRET
              value: <COOKIE_SECRET>
            image: quay.io/oauth2-proxy/oauth2-proxy:latest
            imagePullPolicy: Always
            name: oauth2-proxy
            ports:
            - containerPort: 4182
              protocol: TCP
            securityContext:
              allowPrivilegeEscalation: false
              capabilities:
                drop: ["ALL"]
          securityContext:
            runAsNonRoot: true
            seccompProfile:
              type: RuntimeDefault
    ---
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        k8s-app: oauth2-proxy
      name: oauth2-proxy
      namespace: kanidm-example
    spec:
      ports:
      - name: http
        port: 4182
        protocol: TCP
        targetPort: 4182
      selector:
        k8s-app: oauth2-proxy
    
    ---
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: oauth2-proxy
      namespace: kanidm-example
    spec:
      ingressClassName: nginx
      rules:
      - host: <FQDN>
        http:
          paths:
          - path: /oauth2
            pathType: Prefix
            backend:
              service:
                name: oauth2-proxy
                port:
                  number: 4182
      tls:
      - hosts:
        - <FQDN>
        secretName: <FQDN>-ingress-tls # replace . with - in the hostname
    
  5. Apply the configuration by running the following command:

    kubectl apply -f k8s.kanidm-nginx-auth-example.yaml
    
  6. Check your deployment succeeded by running the following commands:

    kubectl -n kanidm-example get all
    kubectl -n kanidm-example get ingress
    kubectl -n kanidm-example get Certificate
    

    You may use kubectl's describe and log for troubleshooting. If there are ingress errors see the Ingress NGINX documentation's troubleshooting page. If there are certificate errors see the CertManger documentation's troubleshooting page.

    Once it has finished deploying, you will be able to access it at https://<FQDN> which will prompt you for authentication.

Cleaning Up

  1. Remove the resources create for this example from k8s:

    kubectl delete namespace kanidm-example
    
  2. Remove the objects created for this example from Kanidm:

    1. Delete the account created in section Instructions step 1.
    2. Delete the group created in section Instructions step 2.
    3. Delete the OAuth2 resource created in section Instructions step 3.

References

  1. NGINX Ingress Controller: External OAUTH Authentication
  2. OAuth2 Proxy: OpenID Connect Provider