Kubernetes Single Sign-on with Pinniped OpenID Connect

In this tutorial I will setup Pinniped, a Single Sign-on solution from the VMware Tanzu project.

Wtah is Pinniped

Pinniped gives you a unified login experience across all your clusters, including on-premises and managed cloud environments.

Pinniped consists of two components, Supervisor and Concierge:

  • The Pinniped Supervisor is an OIDC server which allows users to authenticate with an external identity provider (IDP), and then issues its own federation ID tokens to be passed on to clusters based on the user information from the IDP.

  • The Pinniped Concierge is a credential exchange API which takes as input a credential from an identity source (e.g., Pinniped Supervisor, proprietary IDP), authenticates the user via that credential, and returns another credential which is understood by the host Kubernetes cluster or by an impersonation proxy which acts on behalf of the user.

Pinniped Architecture

Install the Pinniped Supervisor

Run below command to install Supervisor, this will install everything to the pinniped-supervisor namespace.

kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-supervisor.yaml

kubens pinniped-supervisor
kubectl get po

After installing Pinniped, create a secret with the client ID and secret from your identity provider. In below command I have named the secret oidc-client:

kubectl create secret generic oidc-client \
  --namespace pinniped-supervisor \
  --type secrets.pinniped.dev/oidc-client \
  --from-literal=clientID="<client-id-goes-here>" \
  --from-literal=clientSecret="<secret-goes-here>"

The supervisor needs to be reachable from other clusters, we need to create a service and an ingress resource.

nano pinniped-supervisor-ingress.yaml
---
apiVersion: v1
kind: Service
metadata:
  namespace: pinniped-supervisor
  name: pinniped-supervisor
spec:
  selector:
    app: pinniped-supervisor
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: pinniped-supervisor
  namespace: pinniped-supervisor
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: ca-issuer
spec:
  tls:
  - hosts:
    - supervisor.k8s.intra
    secretName: supervisor-cert
  rules:
  - host: supervisor.k8s.intra
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: pinniped-supervisor
            port:
              number: 80

Create and apply a FederationDomain resource. Give it a name and set the issuer to the URL you will be using for Supervisor.

nano FederationDomain.yaml
---
apiVersion: config.supervisor.pinniped.dev/v1alpha1
kind: FederationDomain
metadata:
  name: keycloak
  namespace: pinniped-supervisor
spec:
  issuer: https://supervisor.k8s.intra

Create and apply a OIDCIdentityProvider resource.

nano OIDCIdentityProvider.yaml
---
apiVersion: idp.supervisor.pinniped.dev/v1alpha1
kind: OIDCIdentityProvider
metadata:
  name: keycloak-idp
  namespace: pinniped-supervisor
spec:
  issuer: https://sso.k8s.intra/auth/realms/k8s
  claims:
    username: email
    groups: groups
  authorizationConfig:
    additionalScopes: ['email', 'profile']
  client:
    secretName: oidc-client

Install the Pinniped Concierge

Install Concierge on any managed or unmanaged cluster you would like to use OIDC login on. This can include the cluster where Supervisor is running.

kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-concierge-crds.yaml
kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-concierge.yaml

kubens pinniped-concierge
kubectl get po

Create and apply a JWTAuthenticator resource, it is cluster scoped so no need for namespaces. Create a random string for the audience property using openssl rand -base64 24 command.

openssl rand -base64 24
jCIGaxMT5Yw9NvafTTVoXxGLkviPPyg6

If you ise a self signed certificate for the ingress you need o add the base64 encoded CA pem to the JWTAuthenticator.

cat k8s.pem | base64
LS0tLS1CR...
nano JWTAuthenticator.yaml
---
apiVersion: authentication.concierge.pinniped.dev/v1alpha1
kind: JWTAuthenticator
metadata:
   name: supervisor-jwt-authenticator
   namespace: pinniped-concierge
spec:
   issuer: https://supervisor.k8s.intra
   audience: jCIGaxMT5Yw9NvafTTVoXxGLkviPPyg6
   claims:
     username: username
     groups: groups
  # tls:
  #   certificateAuthorityData: LS0tLS1CR... # optional base64 CA data if using a self-signed certificate

The supervisor.k8s.intra domain must be resolwable from the pinniped-concierge. You can add supervisor.k8s.intra as like hists file to the coredns like this.

Below is an example of a ClusterRoleBinding that binds the role cluster-admin to the Keycloak group devops-team. (In my case it came from ldap) Create your own role bindings to fit your needs and apply them to the cluster.

nano devops-team_ClusterRoleBinding.yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-admin-it-afdeling
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: Group
    name: devops-team

Install the Pinniped command-line tool

For osx you can use brew to install the command-line tool:

brew install vmware-tanzu/pinniped/pinniped-cli

Or find the appropriate binary for your platform from the latest release the put the command-line tool somewhere on your $PATH.

Generate the kubeconfig file wigh the default admin kubeconfig on the cluster’s master node:

pinniped get kubeconfig \
--oidc-ca-bundle /opt/k8s_sec_lab/cert/k8s.pem \
--output pinniped-kubeconfig

kubectl --kubeconfig pinniped-kubeconfig get pods -n pinniped-concierge
NAME                                                  READY   STATUS    RESTARTS   AGE
pinniped-concierge-9bc8bbdc5-qg75p                    1/1     Running   0          16m
pinniped-concierge-9bc8bbdc5-zxsjj                    1/1     Running   0          16m
pinniped-concierge-kube-cert-agent-5dcccbbb4b-56jbj   1/1     Running   0          79m

The k8s.pem file is containes the CA certificate from ca-issuer.