跳转至

csi-driver-spiffe

csi-driver-spiffe 是一个容器存储接口(CSI)驱动插件,用于 Kubernetes 与cert-manager一起工作。 这个 CSI 驱动程序以 X.509 证书密钥对的形式透明地交付 SPIFFE SVIDs 来安装 Kubernetes Pods。

最终的结果是,运行在 Kubernetes 中的所有 Pod 都可以通过最小的配置安全地从信任域请求他们的 SPIFFE 身份文档。 这些文件是:

  • 自动更新; ✔️
  • 私钥永远不会离开节点的虚拟内存; ✔️
  • 每个 Pod 的文档都是唯一的; ✔️
  • 文档与 Pod 共享相同的生命周期,并在 Pod 终止时销毁。 ✔️
...
          volumeMounts:
          - mountPath: "/var/run/secrets/spiffe.io"
            name: spiffe
      volumes:
        - name: spiffe
          csi:
            driver: spiffe.csi.cert-manager.io
            readOnly: true

SPIFFE 文档可用于相互 TLS (mTLS)或 Pod 在其信任域内进行身份验证。

组件

该项目分为两个部分;

CSI Driver

The CSI driver runs as DaemonSet on the cluster which is responsible for generating, requesting, and mounting the certificate key pair to Pods on the node it manages. The CSI driver creates and manages a tmpfs directory which is used to create and mount Pod volumes from.

When a Pod is created with the CSI volume configured, the driver will locally generate a private key, and create a cert-manager CertificateRequest in the same Namespace as the Pod.

The driver uses CSI Token Request to both discover the Pod's identity to form the SPIFFE identity contained in the X.509 certificate signing request, as well as securely impersonate its ServiceAccount when creating the CertificateRequest.

Once signed by the pre-configured target signer, the driver will mount the private key and signed certificate into the Pod's Volume to be made available as a Volume Mount. This certificate key pair is regularly renewed based on the expiry of the signed certificate.

Approver

A distinct cert-manager approver Deployment is responsible for managing the approval and denial condition of created CertificateRequests that target the configured SPIFFE Trust Domain signer. The approver ensures that requests have:

  1. the correct key type (ECDSA P-521);
  2. acceptable key usages (Key Encipherment, Digital Signature, Client Auth, Server Auth);
  3. the requested duration matches the enforced duration (default 1 hour);
  4. no SANs or other identifiable attributes except a single URI SANs;
  5. the single URI SAN is the SPIFFE identity of the ServiceAccount who created the CertificateRequest;
  6. the SPIFFE ID Trust Domain is the same as configured.

If any of these checks do not pass, the CertificateRequest will be marked as Denied, else it will be marked as Approved. The approver will only manage CertificateRequests who request from the same IssuerRef that has been configured.

安装

⚠ Requires Kubernetes version v1.21+ or v1.20 with the --feature-gates=CSIServiceAccountToken=true flag.

⚠ Requires cert-manager v1.3 or higher.

  1. cert-manager is required to be installed with csi-driver-spiffe.

⚠

It is important that the default approver is disabled in cert-manager. If the default approver is not disabled in cert-manager, the csi-driver-spiffe approver will race with cert-manager and thus its policy enforcement becomes useless.

$ helm repo add jetstack https://charts.jetstack.io --force-update
$ helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager --set extraArgs={--controllers='*\,-certificaterequests-approver'} --set installCRDs=true --create-namespace

⚠

  1. Install or configure a ClusterIssuer to give cert-manager the ability to sign against your Trust Domain. If a namespace scoped Issuer is desired, then that Issuer must be created in every namespace that Pods will mount volumes from. You must use an Issuer type which is compatible with signing URI SAN certificates and the private does not need to be available to the signer, for example CA, Vault, Venafi, AWS PCA, Google CAS, Small Step. Issuers such as SelfSigned or ACME will not work.

An example demo ClusterIssuer can be found here. This Trust Domain's root CA is self-signed by cert-manager and private key is stored in the cluster.

$ kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/main/deploy/example/clusterissuer.yaml
# We must also approve the CertificateRequest since we have disabled the default approver
$ kubectl cert-manager approve -n cert-manager $(kubectl get cr -n cert-manager -ojsonpath='{.items[0].metadata.name}')
  1. Install csi-driver-spiffe into the cluster using the issuer we configured. We must also configure the issuer resource type and name of the issuer we configured so that the approver has permissions to approve referencing CertificateRequests.

  2. Change signer name to match your issuer type.

  3. Change name, kind, and group to your issuer.
$ helm upgrade -i -n cert-manager cert-manager-csi-driver-spiffe jetstack/cert-manager-csi-driver-spiffe --wait \
  --set "app.logLevel=1" \
  --set "app.trustDomain=my.trust.domain" \
  --set "app.approver.signerName=clusterissuers.cert-manager.io/csi-driver-spiffe-ca" \
  \
  --set "app.issuer.name=csi-driver-spiffe-ca" \
  --set "app.issuer.kind=ClusterIssuer" \
  --set "app.issuer.group=cert-manager.io"

使用

Once the driver is successfully installed, Pods can begin to request and mount their key and SPIFFE certificate. Since the Pod's ServiceAccount is impersonated when creating CertificateRequests, every ServiceAccount must be given that permission which intends to use the volume.

Example manifest with a dummy Deployment:

$ kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/main/deploy/example/example-app.yaml

$ kubectl exec -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') -- cat /var/run/secrets/spiffe.io/tls.crt | openssl x509 --noout --text | grep Issuer:
        Issuer: CN = csi-driver-spiffe-ca
$ kubectl exec -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') -- cat /var/run/secrets/spiffe.io/tls.crt | openssl x509 --noout --text | grep URI:
                URI:spiffe://foo.bar/ns/sandbox/sa/example-app

FS-Group

When running Pods with a specified user or group, the volume will not be readable by default due to Unix based file system permissions. The mounting volumes file group can be specified using the following volume attribute:

---
securityContext:
  runAsUser: 123
  runAsGroup: 456
volumes:
  - name: spiffe
    csi:
      driver: spiffe.csi.cert-manager.io
      readOnly: true
      volumeAttributes:
        spiffe.csi.cert-manager.io/fs-group: "456"
$ kubectl apply -f https://raw.githubusercontent.com/cert-manager/csi-driver-spiffe/main/deploy/example/fs-group-app.yaml

$ kubectl exec -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app-fs-group -o jsonpath='{.items[0].metadata.name}') -- cat /var/run/secrets/spiffe.io/tls.crt | openssl x509 --noout --text | grep URI:
                URI:spiffe://foo.bar/ns/sandbox/sa/fs-group-app

根 CA 包

By default, the CSI driver will only mount the Pod's private key and signed certificate. csi-driver-spiffe can be optionally configured to also mount a statically defined CA bundle from a volume that will be written to all Pod volumes.

If the CSI driver detects this bundle has changed (through overwrite, renewal, etc), the new bundle will be written to all existing volumes.

The following example mounts the CA certificate used by the Trust Domain ClusterIssuer.

$ helm upgrade -i -n cert-manager cert-manager-csi-driver-spiffe jetstack/cert-manager-csi-driver-spiffe --wait \
  --set "app.logLevel=1" \
  --set "app.trustDomain=my.trust.domain" \
  --set "app.approver.signerName=clusterissuers.cert-manager.io/csi-driver-spiffe-ca" \
  \
  --set "app.issuer.name=csi-driver-spiffe-ca" \
  --set "app.issuer.kind=ClusterIssuer" \
  --set "app.issuer.group=cert-manager.io" \
  \
  --set "app.driver.volumes[0].name=root-cas" \
  --set "app.driver.volumes[0].secret.secretName=csi-driver-spiffe-ca" \
  --set "app.driver.volumeMounts[0].name=root-cas" \
  --set "app.driver.volumeMounts[0].mountPath=/var/run/secrets/cert-manager-csi-driver-spiffe" \
  --set "app.driver.sourceCABundle=/var/run/secrets/cert-manager-csi-driver-spiffe/ca.crt"
$ kubectl rollout restart deployment -n sandbox my-csi-app
$ kubectl exec -it -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app -o jsonpath='{.items[0].metadata.name}') -- ls /var/run/secrets/spiffe.io/
ca.crt   tls.crt  tls.key