In my current project, the PKI heirarchy I want to create needs to be dynamic as automated and agile certificate topologies bring a lot of value to the defensive security story.
With the security hat on, I want the root of the certificate hierarchy, the root CA, rooted with its private key in an HSM. Since I'm running this on GKE it makes sense to leverage GCP's services here, and indeed there is a cert-manager issuer controller for googles Private CA Services.
Yikes, it's not cheap, and I couldn't figure out if you can sign intermediates that reside ouside their CA product (external certs can be imported). Choosing KMS can offer us much better pricing to get an HMS-backed key if we manage the CA operations ourselves, and that's a big point of using cert-manager in the first place.
Searching for "cert-manager kms" lands you on Skyscanner's excellent opensource Issuer controller that gives cert-manager the power to sign certs with an AWS KMS key, but I'm not on AWS.
This is where the opensource spirit comes in and I make a project based on Skyscanner's. I initially tried to make a PR to add GCP KMS capabilities to the project, but while the code was able to act as an excellent framework for implementing an external Issuer, it's a bit too tightly woven with AWS semantics to be extended.
With this I can keep my root certificate long-lived and secure in a Google HSM (and managed via config connector), while keeping my intermediate and leaf certificates agile and oft updated. Give it a try and lmk what you think.
Here is an example deployment with config connector used to create the KSMCrypto(Ring|Key)
and setup workload identity and policy to give the issuer access to run the sign operation via the GCP KMS API:
Basically it boils down to these resources:
apiVersion: kms.cnrm.cloud.google.com/v1beta1
kind: KMSKeyRing
metadata:
name: yoke
annotations:
cnrm.cloud.google.com/deletion-policy: "abandon"
spec:
location: us-central1
---
apiVersion: kms.cnrm.cloud.google.com/v1beta1
kind: KMSCryptoKey
metadata:
name: yokeroot
spec:
keyRingRef:
name: yoke
purpose: ASYMMETRIC_SIGN
versionTemplate:
algorithm: RSA_SIGN_PSS_2048_SHA256
protectionLevel: HSM
importOnly: false
---
apiVersion: iam.cnrm.cloud.google.com/v1beta1
kind: IAMServiceAccount
metadata:
name: yoke-kms-issuer
spec:
displayName: yoke-kms-issuer
---
apiVersion: iam.cnrm.cloud.google.com/v1beta1
kind: IAMPolicyMember
metadata:
name: yoke-kms-issuer-key-signerverifier
spec:
member: serviceAccount:yoke-kms-issuer@gptops-playground.iam.gserviceaccount.com
role: roles/cloudkms.signerVerifier
resourceRef:
apiVersion: kms.cnrm.cloud.google.com/v1beta1
kind: KMSCryptoKey
name: yokeroot
---
apiVersion: iam.cnrm.cloud.google.com/v1beta1
kind: IAMPolicy
metadata:
name: yoke-kms-issuer-workloadidentity
spec:
resourceRef:
apiVersion: iam.cnrm.cloud.google.com/v1beta1
kind: IAMServiceAccount
name: yoke-kms-issuer
bindings:
- role: roles/iam.workloadIdentityUser
members:
- serviceAccount:gptops-playground.svc.id.goog[yoke/kms-issuer-controller-manager]
---
apiVersion: iam.cnrm.cloud.google.com/v1beta1
kind: IAMPolicyMember
metadata:
name: yoke-kms-issuer-key-viewer
spec:
member: serviceAccount:yoke-kms-issuer@gptops-playground.iam.gserviceaccount.com
role: roles/cloudkms.viewer
resourceRef:
apiVersion: kms.cnrm.cloud.google.com/v1beta1
kind: KMSCryptoKey
name: yokeroot
---
apiVersion: cert-manager.drzzl.io/v1alpha1
kind: KMSIssuer
metadata:
name: yokeroot
spec:
keyRef:
name: yokeroot
commonName: Yoke Ephemeral Cluster Root
duration: 87600h # 10 years
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: kms-test-ca
spec:
isCA: true
commonName: Test Cluster KMS CA
secretName: test-ca-pki
privateKey:
algorithm: RSA
size: 2048
issuerRef:
name: yokeroot
kind: KMSIssuer
group: cert-manager.drzzl.io