Develop a Custom Signer
Third-party signers
You can also introduce your own custom signer, which should have a similar
prefixed name but using your own domain name. For example, if you represent an
open source project that uses the domain open-fictional.example then you might
use issuer.open-fictional.example/service-mesh as a signer name.
To implement your custom signer, you need to provide a set of controllers that use the Kubernetes API to interact with CertificateSigningRequests, PodCertificateRequests, and ClusterTrustBundles that are linked to your signer's name.
Useful ClusterRoles
CertificateSigningRequests
CertificateSigningRequests have three roles — requesters, approvers, and signers. Depending on the signer's logic around approvals, the approver and signer role may be shared by the same controller.
Requesters need to be able to create and read CertificateSigningRequests. Allowing broad read access to CSRs is not a security issue, because the CSRs only contain public (not private) keys.
- Verbs: create, get, list, watch, group:
certificates.k8s.io, resource:certificatesigningrequests
If your cluster uses RBAC, here's an example ClusterRole for a CSR requester:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csr-creator
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- create
- get
- list
- watch
Approvers need to be able to watch CSRs, and approve or deny CSRs addressed to their signer name:
- Verbs: get, list, watch, group:
certificates.k8s.io, resource:certificatesigningrequests - Verbs: update, group:
certificates.k8s.io, resource:certificatesigningrequests/approval - Verbs: approve, group:
certificates.k8s.io, resource:signers, resourceName:<signerNameDomain>/<signerNamePath>or<signerNameDomain>/*
If your cluster uses RBAC, here's an example ClusterRole for a CSR approver:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csr-approver
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- get
- list
- watch
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests/approval
verbs:
- update
- apiGroups:
- certificates.k8s.io
resources:
- signers
resourceNames:
- example.com/my-signer-name # example.com/* can be used to authorize for all signers in the 'example.com' domain
verbs:
- approve
Issuers need to be able to watch CSRs, and issue or fail CSRs addressed to their signer name:
- Verbs: get, list, watch, group:
certificates.k8s.io, resource:certificatesigningrequests - Verbs: update, group:
certificates.k8s.io, resource:certificatesigningrequests/status - Verbs: sign, group:
certificates.k8s.io, resource:signers, resourceName:<signerNameDomain>/<signerNamePath>or<signerNameDomain>/*
If your cluster uses RBAC, here's an example ClusterRole for a CSR issuer:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csr-signer
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- get
- list
- watch
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests/status
verbs:
- update
- apiGroups:
- certificates.k8s.io
resources:
- signers
resourceNames:
- example.com/my-signer-name # example.com/* can be used to authorize for all signers in the 'example.com' domain
verbs:
- sign
PodCertificateRequests
For PodCertificateRequests, the requester role is almost always filled by
kubelet, which automatically has the necessary permissions to create and read
PCRs. There is no approver role.
Issuers need to be able to watch CSRs, and issue or fail CSRs addressed to their signer name:
- Verbs: get, list, watch, group:
certificates.k8s.io, resource:podcertificaterequests - Verbs: update, group:
certificates.k8s.io, resource:podcertificaterequests/status - Verbs: sign, group:
certificates.k8s.io, resource:signers, resourceName:<signerNameDomain>/<signerNamePath>or<signerNameDomain>/*
If your cluster uses RBAC, here's an example ClusterRole for a PCR issuer:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pcr-signer
rules:
- apiGroups:
- certificates.k8s.io
resources:
- podcertificaterequests
verbs:
- get
- list
- watch
- apiGroups:
- certificates.k8s.io
resources:
- podcertificaterequests/status
verbs:
- update
- apiGroups:
- certificates.k8s.io
resources:
- signers
resourceNames:
- example.com/my-signer-name # example.com/* can be used to authorize for all signers in the 'example.com' domain
verbs:
- sign
ClusterTrustBundles
ClusterTrustBundles have two broad roles: consumers and attesters.
Consumers need to be able to read ClusterTrustBundles. In most scenarios, the
consumer will be kubelet (automatic permission via the node authorizer, if
enabled) or a service account (automatic permission via an RBAC bootstrap
ClusterRole, if enabled).
- Verbs: get, list, watch, group
certificates.k8s.io, resource:clustertrustbundles
Attesters are typically a signer controller, and will need permission to create and maintain specific signer-linked ClusterTrustBundles
- Verbs: create, get, list, watch, group:
certificates.k8s.io, resource:clustertrustbundles - Verbs: attest, group:
certificates.k8s.io, resource:signers, resourceName:<signerNameDomain>/<signerNamePath>or<signerNameDomain>/*
Writing a PodCertificateRequest controller
Under the hood, kubelet runs the state machine depicted in Figure 1 for each
podCertificate projection. The actions your signer takes on the
PodCertificateRequests that kubelet generates control the state transitions.
- The projection starts out in
Initialstate. - Kubelet generates a private key and holds it in memory.
- Kubelet creates a
PodCertificateRequest
addressed to the requested signer. Kubelet then moves the projection into
the
Waitstate. - If the PodCertificateRequest is marked "Denied", move to the
Deniedstate. This is a permanent error state, and the container(s) that mount this projection will fail to start. - If the PodCertificateRequest is marked "Failed", move to the
Failedstate. This is a permanent error state, and the container(s) that mount this projection will fail to start. - If the PodCertificate is marked "Issued", move to the
Freshstate. Kubelet holds the private key and certificate chain in memory, and will periodically write them to the filesystem at the requested location. The container that mounts this projection will start up and run (assuming nothing else blocks its execution). - The signer indicated an appropriate time to begin refreshing the certificate
when it issued the PodCertificateRequest. Once that time has passed Kubelet
will generate a new private key, create a new PodCertificateRequest, and move
the projection into
WaitRefreshstate. - If the PodCertificateRequest is marked "Denied", move to the
Deniedstate. This is a permanent error state, and the container(s) will begin to get Kubelet volume remount errors. - If the PodCertificateRequest is marked "Failed", move to the
Failedstate. This is a permanent error state, and the container(s) will begin to get Kubelet volume remount errors. - If the PodCertificate is marked "Issued", move back to the
Freshstate. The container(s) will continue to run, with the new private key and certificate chain written to the filesystem.
What's next
- Read detailed API references:
- CertificateSigningRequest: CertificateSigningRequest
- PodCertificateRequest:
- ClusterTrustBundle: ClusterTrustBundle
- Read Manage TLS Certificates in a Cluster
- Read Issue a Certificate for a Kubernetes API Client Using A CertificateSigningRequest
- View the source code for the
kube-controller-managerbuilt in signer - View the source code for the
kube-controller-managerbuilt in approver - For details of X.509 itself, refer to RFC 5280 section 3.1
- For information on the syntax of PKCS#10 certificate signing requests, refer to RFC 2986