Pinniped Logo

Pinniped blog

Pinniped v0.33.0: Externally-managed CA bundles for Pinniped's custom resources

Joshua T. Casey and Ryan Richard

Aug 7, 2024

Juvenile Southern fur seal Photo from Unsplash

Pinniped’s v0.33.0 release enables Pinniped administrators to use externally-provided CA bundles for all custom resources for which Pinniped acts as a client. This includes OIDC identity providers, LDAP and Active Directory servers, GitHub Enterprise Servers, and any JWT or webhook authenticators running on or off the cluster.

This should reduce manual steps to install or configure Pinniped, since administrators no longer need to provide the CA bundle inline within a Pinniped custom resource, and can instead use a ConfigMap or Secret object in the same namespace as Pinniped Supervisor or Concierge. Often, these ConfigMap and Secret objects will be managed by tooling such as cert-manager, trust-manager, or Vault, all of which can help manage certificate distribution.

Concierge and Supervisor will monitor these ConfigMap or Secret objects, and automatically read in any changes. This means that manual updates to custom resources is no longer required, to ensure almost no downtime if the certificate needs to be rotated.

Example

Here is an example of how you would previously configure a CA bundle for an ActiveDirectoryIdentityProvider.

apiVersion: idp.supervisor.pinniped.dev/v1alpha1
kind: ActiveDirectoryIdentityProvider
metadata:
  name: my-active-directory-idp
  namespace: pinniped-supervisor
spec:
  host: "activedirectory.example.com:636"
  bind:
    secretName: "active-directory-bind-account"
  tls:
    # This would contain your base64 encoded CA bundle.
    certificateAuthorityData: LS0tLS1CRUdJTiBDR...

And here is an example of how you can alternatively configure the CA bundle using this new feature, with the new certificateAuthorityDataSource configuration option.

apiVersion: idp.supervisor.pinniped.dev/v1alpha1
kind: ActiveDirectoryIdentityProvider
metadata:
  name: my-active-directory-idp
  namespace: pinniped-supervisor
spec:
  host: "activedirectory.example.com:636"
  bind:
    secretName: "active-directory-bind-account"
  tls:
    # This is the new feature! Instead of embedding your CA
    # bundle here, you can refer to a Secret or ConfigMap.
    certificateAuthorityDataSource:
      kind: ConfigMap
      name: my-ca-bundle-config-map
      key: ca.crt

In the above example, the ConfigMap must be in the pinniped-supervisor namespace. Its content will be dynamically loaded, and will be automatically watched for any updates, which will also be dynamically reloaded.

This gives you the advantage of being able to more easily update your CA bundles using any automation tools that can update ConfigMaps or Secrets.

The same feature has been added to all Concierge authenticator resource types (WebhookAuthenticator and JWTAuthenticator) and all Supervisor identity provider resource types (OIDCIdentityProvider, GitHubIdentityProvider, ActiveDirectoryIdentityProvider, and LDAPIdentityProvider).

Demo using local-user-authenticator as a service and trust-manager to manage the CA bundles

This demo will show using the new certificateAuthorityDataSource feature on a WebhookAuthenticator. In order to demo that, we need a webhook provider and a CA bundle. We can install Pinniped’s local-user-authenticator to act as webhook provider and source of identities, and then configure the Concierge WebhookAuthenticator to use it. During installation, local-user-authenticator will generate its own self-signed CA bundle that we must provide to Concierge in the WebhookAuthenticator custom resource.

NOTE: The local-user-authenticator is not production-ready. It’s used here only for demonstration purposes because it is easy to install and configure as an identity provider for a WebhookAuthenticator.

We will use trust-manager to distribute the CA bundle from the local-user-authenticator namespace to the pinniped-concierge namespace. Usually, trust-manager is used to distribute certificates generated by cert-manager, but in this demo we don’t need the added complexity that would come from also using cert-manager.

Setup and creating a cluster

In order to perform the following steps, you should have recent versions of the following tools:

Create a local kind cluster:

$ kind create cluster -n pinniped-testing

Install local-user-authenticator:

$ kubectl apply --filename https://get.pinniped.dev/v0.33.0/install-local-user-authenticator.yaml

# Create a user "pinny" with password "password123" and group "some-group".
# Each secret in this namespace acts like a user definition.
$ kubectl create secret generic pinny \
    --namespace local-user-authenticator \
    --from-literal=groups=some-group \
    --from-literal=passwordHash=$(htpasswd -nbBC 10 x password123 | sed -e "s/^x://")
# Wait for the CA bundle to be generated by local-user-authenticator
$ kubectl wait \
    --for=jsonpath={.data.caCertificate} \
    secret/local-user-authenticator-tls-serving-certificate \
    --namespace local-user-authenticator \
    --timeout 60s
# Label this secret, so that trust-manager can find it later
$ kubectl label secret local-user-authenticator-tls-serving-certificate \
    --namespace local-user-authenticator \
    readable-by-trust-manager=yes-please

Install trust-manager, with steps taken from cert-manager installation and trust-manager installation:

$ helm repo add jetstack https://charts.jetstack.io --force-update
# Install trust-manager into the local-user-authenticator namespace,
# just so that it can read secrets in that namespace.
# This is not meant for production use.
$ helm upgrade trust-manager jetstack/trust-manager \
  --install \
  --namespace local-user-authenticator \
  --wait \
  --set app.webhook.tls.helmCert.enabled=true \
  --set app.trust.namespace=local-user-authenticator
# Various warnings will print out related to this installation
# being unsuitable for production use

Configure trust-manager to replicate the local-user-authenticator CA bundle into the desired namespaces. This is merely one way of many to use trust-manager and not meant to be a specific recommendation.

First, install Concierge’s resources, which will create a namespace called pinniped-concierge.

$ kubectl apply --filename https://get.pinniped.dev/v0.33.0/install-pinniped-concierge-crds.yaml
$ kubectl apply --filename https://get.pinniped.dev/v0.33.0/install-pinniped-concierge-resources.yaml

# Apply a label for trust-manager's namespaceSelector
$ kubectl label namespaces pinniped-concierge \
  allow-trust-manager-bundles=yes-please \
  --overwrite=true

# Create a Bundle so that trust-manager will propagate the CA bundle from
# local-user-authenticator to concierge
$ cat << EOF > my-trust-manager-bundle.yaml
apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
  name: my-trust-manager-bundle
spec:
  sources:
  - secret:
      selector:
        matchLabels:
          readable-by-trust-manager: yes-please
      key: "caCertificate"
  target:
    # Sync the bundle to a ConfigMap called 'my-trust-manager-bundle'.
    configMap:
      key: "local-user-authenticator-ca.pem"
    namespaceSelector:
      matchLabels:
        allow-trust-manager-bundles: "yes-please"
EOF
$ kubectl apply --filename my-trust-manager-bundle.yaml
# Confirm that the ConfigMap was created
$ kubectl get configmap my-trust-manager-bundle \
    --namespace pinniped-concierge \
    --output yaml

Create a WebhookAuthenticator to tell Concierge to validate static tokens using the installed local-user-authenticator.

$ cat << EOF > concierge.webhookauthenticator.yaml
apiVersion: authentication.concierge.pinniped.dev/v1alpha1
kind: WebhookAuthenticator
metadata:
  name: local-user-authenticator
spec:
  endpoint: https://local-user-authenticator.local-user-authenticator.svc/authenticate
  tls:
    # This API element is introduced in v0.33.0 as part of this new feature.
    certificateAuthorityDataSource:
      kind: ConfigMap
      name: my-trust-manager-bundle
      key: local-user-authenticator-ca.pem
EOF

# Create the webhook authenticator
$ kubectl apply --filename concierge.webhookauthenticator.yaml

Attempt a login!

# As an admin user, let's grant the user's group some permissions
$ kubectl create clusterrolebinding members-of-some-group-are-admins \
    --clusterrole=view \
    --group=some-group
# Now we get a user kubeconfig...
$ pinniped get kubeconfig \
    --static-token "pinny:password123" \
    --concierge-authenticator-type webhook \
    --concierge-authenticator-name local-user-authenticator \
    > pinniped-kubeconfig.yaml
# ... and perform an action as that user
$ kubectl get pods -A --kubeconfig pinniped-kubeconfig.yaml
# Success!

Where to read more

  • See the original proposal for this body of work here

Join the Pinniped community!

Pinniped is better because of our contributors and maintainers. It's because of you that we can bring great software to the community.

Connect with the community on GitHub and Slack.

Join our Google Group to receive updates and meeting invitations.

Related content

Pinniped v0.31.0: GitHub as an identity provider

Pinniped v0.31.0: GitHub as an identity provider

With the release of v0.31.0, Pinniped brings GitHub identities to Kubernetes clusters everywhere

Pinniped v0.25.0: With External Certificate Management for the Impersonation Proxy and more!

Pinniped v0.25.0: With External Certificate Management for the Impersonation Proxy and more!

With v0.25.0 you get external certificate management for the impersonation proxy, easier scheduling of the kube-cert-agent, and more

Pinniped v0.26.0: Multiple identity providers and identity transformations

Pinniped v0.26.0: Multiple identity providers and identity transformations

With the release of v0.26.0, Pinniped now supports multiple identity providers and identity transformations

Getting started

Learn how Pinniped works, see how to use it on your clusters, and dive into internals of Pinniped's APIs and architecture.