diff --git a/content/docs/usage/csi-driver-spiffe/README.md b/content/docs/usage/csi-driver-spiffe/README.md index f8588ad3a2..9acfa65a5b 100644 --- a/content/docs/usage/csi-driver-spiffe/README.md +++ b/content/docs/usage/csi-driver-spiffe/README.md @@ -18,6 +18,7 @@ These documents in turn have the following properties: - private key never leaves the node's virtual memory ✔️ - each Pod's document is unique ✔️ - the document shares the same life cycle as the Pod and is destroyed on Pod termination ✔️ +- enable mTLS within a trust domain ✔️ ```yaml ... @@ -31,40 +32,38 @@ These documents in turn have the following properties: readOnly: true ``` -SPIFFE documents can then be used by Pods for mutual TLS (mTLS) or other authentication within their Trust Domain. ### Components -The project is split into two components. +The project is split into two components: the driver and the approver. #### 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 +The CSI driver runs as DaemonSet on the cluster and is responsible for +generating, requesting, and mounting a certificate and private key to Pods on the node it manages. The CSI driver creates and manages a -[tmpfs](https://www.kernel.org/doc/html/latest/filesystems/tmpfs.html) directory -which is used to create and mount Pod volumes from. +[tmpfs](https://www.kernel.org/doc/html/latest/filesystems/tmpfs.html) directory. When a Pod is created with the CSI volume configured, the driver will locally generate a private key, and create a cert-manager [CertificateRequest](../../usage/certificaterequest.md) in the same Namespace as the Pod. -The driver uses [CSI Token Request](https://kubernetes-csi.github.io/docs/token-requests.html) 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. +The driver uses [CSI Token Requests](https://kubernetes-csi.github.io/docs/token-requests.html). +This means that the token of the pod being created is passed to the driver. -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. +This token's details are used for creating the SPIFFE ID which represents the pod's identity, +and the token is used for creating the actual CertificateRequest for the SVID. + +Once the certificate is signed by the configured cert-manager issuer, the driver +mounts the private key and certificate into the Pod's Volume, and watches the +certificate to renew it and the private key based on the certificate's +expiry date. #### Approver A distinct [cert-manager approver](../../usage/certificaterequest.md#approval) -Deployment is responsible for managing the approval and denial condition of -created CertificateRequests that target the configured SPIFFE Trust Domain -signer. +Deployment is responsible for managing approval and denial of csi-driver-spiffe +CertificateRequests. The approver ensures that requests have: @@ -76,16 +75,48 @@ The approver ensures that requests have: the CertificateRequest; 5. a SPIFFE ID Trust Domain matching the one that was configured at startup. -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](../../usage/certificaterequest.md) -that has been configured. +The approver only considers CertificateRequests which have the +`spiffe.csi.cert-manager.io/identity` annotation, which is added by csi-driver-spiffe + to all requests it creates. ## Installation See the [installation guide](./installation.md) for instructions on how to install csi-driver-spiffe. +## Security Considerations + +csi-driver-spiffe deals with highly valuable credentials which should be kept secret. The design +of using a Kubernetes CSI volume makes it simple to restrict access to only the pod which mounts +the CSI volume, but care should still be taken not to expose the private key created by csi-driver-spiffe. + +csi-driver-spiffe _always_ uses the token of the pod it's issuing for to create the CertificateRequest +resource for the SVID it creates. That means that: + +1. During issuance, csi-driver-spiffe has the ability to do whatever the Pod can do; bear in mind that a + compromised csi-driver-spiffe pod could abuse these permissions, although in normal operation only the + CertificateRequest permission is used. +2. Importantly: all pods using csi-driver-spiffe must have permission to create CertificateRequest resources. + +The requirement for a pod to have permission to create CertificateRequests has important security +implications. If a Pod with these permissions is compromised it can create arbitrary CertificateRequest +resources, which could reference arbitrary issuers in the cluster. + +Since csi-driver-spiffe requires that approval is used in-cluster, this risk is mitigated by approval. + +It's important, though, that any other forms of approval in the cluster (such as approver-policy) are +carefully configured not to overlap with the csi-driver-spiffe approver and to restrict access to other +issuers. + +For example, an approver-policy `CertificateRequestPolicy` resource which allowed any pod to issue +using an ACME (Let's Encrypt) issuer might allow a compromised Pod to issue a publicly trusted +certificate, if that Pod used csi-driver-spiffe and thus had permissions to create CertificateRequest +resources. + +Using csi-driver-spiffe safely means considering which approval methods are available in your cluster +and carefully configuring those approvers to ensure that pods cannot target issuers they're not meant +to be able to use. + ## Usage Once the driver is successfully installed, Pods can begin to request and mount @@ -115,6 +146,30 @@ kubectl exec -n sandbox \ # expected output: URI:spiffe://foo.bar/ns/sandbox/sa/example-app ``` +### Runtime Configuration + +If csi-driver-spiffe was installed with runtime configuration enabled, it will watch +a named ConfigMap for issuer configuration. If that ConfigMap exists and contains a valid issuer +reference, that issuer will be used for all created CertificateRequest resources. + +If the ConfigMap is deleted or does not contain a valid issuer reference, it will be +ignored. If a default issuer was specified at install time, that default will be used +as a fallback. If no valid runtime configuration is provided and no default issuer was +specified, issuance (and therefore pod creation) will fail until a valid issuer is configured. + +The name of the runtime configuration ConfigMap is set with the `app.runtimeIssuanceConfigMap` +Helm value at install time. A valid ConfigMap must contain the `issuer-name`, `issuer-kind` +and `issuer-group` keys. + +An example of creating a ConfigMap for a ClusterIssuer named `my-issuer-name` is below: + +```console +kubectl create configmap spiffe-issuer -n cert-manager \ + --from-literal=issuer-name=my-issuer-name \ + --from-literal=issuer-kind=ClusterIssuer \ + --from-literal=issuer-group=cert-manager.io +``` + ### FS-Group When running Pods with a specified user or group, the volume will not be @@ -144,6 +199,9 @@ kubectl exec -n sandbox $(kubectl get pod -n sandbox -l app=my-csi-app-fs-group ### Root CA Bundle +> ⚠️ This feature is significantly less powerful than [trust-manager](../../trust/trust-manager/README.md), +> and is much harder to use and update. It's recommended to use trust-manager instead. + 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 @@ -159,8 +217,8 @@ 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.runtimeIssuanceConfigMap=spiffe-issuer" --set "app.issuer.name=csi-driver-spiffe-ca" \ --set "app.issuer.kind=ClusterIssuer" \ --set "app.issuer.group=cert-manager.io" \ diff --git a/content/docs/usage/csi-driver-spiffe/installation.md b/content/docs/usage/csi-driver-spiffe/installation.md index 9bc3edbfd0..dd6f14dfb6 100644 --- a/content/docs/usage/csi-driver-spiffe/installation.md +++ b/content/docs/usage/csi-driver-spiffe/installation.md @@ -15,6 +15,9 @@ a default installation of cert-manager **will not work**. If the default approver is not disabled, the csi-driver-spiffe approver will race with cert-manager and policy enforcement will become useless. +Policy enforcement is absolutely critical for using csi-driver-spiffe safely. See +the [security considerations](./README.md#security-considerations) section for more details. + ```bash helm repo add jetstack https://charts.jetstack.io --force-update @@ -30,20 +33,25 @@ helm upgrade -i -n cert-manager cert-manager jetstack/cert-manager \ ### 2. Configure an Issuer / ClusterIssuer -Install or configure a [ClusterIssuer](../../configuration/README.md) to give -cert-manager the ability to sign against your Trust Domain. +> This step can be deferred if you use [runtime configuration](./README.md#runtime-configuration), +> but a valid issuer must be configured before a pod can successfully use +> csi-driver-spiffe to obtain an SVID. + +Install or configure some kind of issuer which will be used for signing +CertificateRequest resources in your Trust Domain. -If you want a namespace-scoped Issuer, then it must be created in every namespace +If you wish to use a namespace-scoped issuer it 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; -ACME issuers won't generally work, and the SelfSigned issuer is not appropriate. +You must use an Issuer type which is compatible with signing certificates with a custom +URI SAN. ACME issuers won't generally work, and the SelfSigned issuer is not appropriate. An example demo [ClusterIssuer](../../concepts/issuer.md#namespaces) can be found [in the csi-driver-spiffe repo](https://github.com/cert-manager/csi-driver-spiffe/blob/ed646ccf28b1ecdf63f628bf16f1d350a9b850c1/deploy/example/clusterissuer.yaml). -> ⚠️ This Trust Domain's root CA is generated by cert-manager and **the private key is stored in the cluster** -> This might not be appropriate for production deployments! +> ⚠️ This Trust Domain's root CA is generated by cert-manager and **the private key is stored in the cluster**! +> +> This might not be appropriate for production deployments, depending on your threat model. We'll also use [cmctl](../../reference/cmctl.md) to approve the CertificateRequest, since the default approver was disabled above. @@ -63,6 +71,41 @@ 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](../../usage/certificaterequest.md#rbac-syntax). +Installation varies slightly depending on whether you want to use runtime configuration, which is recommended. + +#### With Runtime Configuration + +First, create a ConfigMap in the installation namespace with details of the issuer to want to use. + +The issuer doesn't have to exist until a pod tries to mount csi-driver-spiffe. + +The name of the ConfigMap is passed into csi-driver-spiffe at install time. + +```bash +kubectl create configmap -n cert-manager spiffe-issuer \ + --from-literal=issuer-name=csi-driver-spiffe-ca \ + --from-literal=issuer-kind=ClusterIssuer \ + --from-literal=issuer-group=cert-manager.io +``` + +```bash +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.issuer.name=" \ + --set "app.issuer.kind=" \ + --set "app.issuer.group=" \ + --set "app.runtimeIssuanceConfigMap=spiffe-issuer" +``` + +In the example above the default issuer values (i.e. `app.issuer.name`, `app.issuer.kind` and `app.issuer.group`) +are explicitly set to be empty, meaning that only runtime configuration will be used. + +You can set the default issuer values if you want to fall back to that issuer in the event that the runtime +configuration ConfigMap is invalid or deleted. + +#### Without Runtime Configuration + Note that the `issuer.name`, `issuer.kind` and `issuer.group` will need to be changed to match the issuer you're actually using! @@ -70,8 +113,6 @@ the issuer you're actually using! 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"