How to load the private and public keys of a certificate from the PKI Secret Backend, separately using CSI driver?

Description

$subject for the certificate resource (created using Terraform resource type) needs to be performed at a Kubernetes cluster.

We are currently using the HashiCorp Vault CSI driver for Vault.
We are unable to find any knowledge source containing instructions or spec definitions which help us perform $subject.

The following is an example we have been currently using to load the public and private keys of a Certificate resource from Azure Key Vault.

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: service-ingress-tls-secret
spec:
  secretObjects:
    - secretName: service-tls
      type: kubernetes.io/tls
      data:
        - objectName: ingress-tls-certificate
          key: tls.key
        - objectName: ingress-tls-certificate
          key: tls.crt
  parameters:
    objects: |
      array:
        - |
          objectName: ingress-tls-certificate
          objectType: secret
          objectVersion: "xxxxxxxxxxxxxxxx"

Even though the objectType referred here is secret, this is actually a Certificate resource at the Azure Key Vault end. When using the secret type, we can load the private and public key pair together.

Do we have a similar option when using HashiCorp Vault CSI provider?

Opened a GitHub issue as a request for improving documentation - https://github.com/hashicorp/vault-csi-provider/issues/372.

Hello! There are two examples that demonstrate pulling dynamic database credentials from Vault and extracting the generated username and password. One for storing the resulting credentials as files and one for storing as Kubernetes secrets to use for environment variables.

I came up with the following for a PKI specific use case. Are you able to try this?

File based dynamic PKI

apiVersion: secrets-store.csi.x-k8s.io/v1alpha1
kind: SecretProviderClass
metadata:
  name: vault-pki
spec:
  provider: vault
  parameters:
    roleName: 'app'
    objects: |
      - objectName: "certificate"
        secretPath: "pki/issue/example"
        secretKey: "certificate"
      - objectName: "issuingCA"
        secretPath: "pki/issue/example"
        secretKey: "issuing_ca"
      - objectName: "privateKey"
        secretPath: "pki/issue/example"
        secretKey: "private_key"

Next, a pod can be created to use this Secret Provider Class to populate the secrets in the pod:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
  labels:
    app: demo
spec:
  selector:
    matchLabels:
      app: demo
  replicas: 1
  template:
    metadata:
      annotations:
      labels:
        app: demo
    spec:
      serviceAccountName: app
      containers:
        - name: app
          image: my-app:1.0.0
          volumeMounts:
            - name: 'vault-pki'
              mountPath: '/mnt/secrets-store'
              readOnly: true
      volumes:
        - name: vault-pki
          csi:
            driver: 'secrets-store.csi.k8s.io'
            readOnly: true
            volumeAttributes:
              secretProviderClass: 'vault-pki'
1 Like

First of all, thanks for the prompt response.

Just a quick clarification on the above - are the secretKeys you have referenced (certificate, issuing_ca and private_key) pre-defined details which a user can load for an existing certificate resource, using the CSI driver?

Also,
we will give the above a try and update this ticket.
Appreciate it if this kind of an example for the given use case (i.e. loading asymmetric key pair information using the CSI driver) can be documented clearly under the relevant official documentation.
Especially since this is a pretty common use case (e.g. loading a TLS key pair for an Ingress TLS Secret).

secretPath is where the secret lives within Vault, or the specific path to generate a secret in Vault. In this case that is pki/issue/<role>

secretKey is the specific item within the response data we want to use. We can look at a sample response of a PKI issue call to see the data returned from Vault. From there we can decide what items in the response we want to use. In my example above, we use certificate, private_key, and issuing_ca.

Another example for dynamic database credentials. secretPath would be database/creds/<role> and secretKey would be username and password as that is what is returned in the database API call.

Secret provider class example

objects: |
      - objectName: "dbUsername"
        secretPath: "database/creds/db-app"
        secretKey: "username"
      - objectName: "dbPassword"
        secretPath: "database/creds/db-app"
        secretKey: "password"
# "objectName" is an alias used within the SecretProviderClass to reference
# that specific secret. This will also be the filename containing the secret.
# "secretPath" is the path in Vault where the secret should be retrieved.
# "secretKey" is the key within the Vault secret response to extract a value from.
1 Like