Getting started¶
This page takes you from an empty cluster to a certificate issued by your CMP server in five steps. Every command is meant to be run in order, and each step shows what success looks like so you can tell where you are.
The example uses PasswordBasedMac protection, which needs only a reference and a secret from your CMP administrator. Certificate-signature protection is covered in Message protection once you have the basic flow working.
What you need¶
- A Kubernetes cluster and
kubectl - Helm v3
- cert-manager already installed and running
- A CMP server, and from its administrator:
- the CMP endpoint URL
- the recipient distinguished name
- a PasswordBasedMac reference and secret
- the CA certificate that signs the server's CMP responses
Throughout this page, demo is the namespace your application lives in. Substitute your own.
Step 1: install cmp-issuer¶
Create the namespace your certificates will live in, then install the controller and name that namespace as one it may read issuer credentials from:
kubectl create namespace demo
helm repo add cmp-issuer https://misiektoja.github.io/cmp-issuer/charts
helm repo update
helm install cmp-issuer cmp-issuer/cmp-issuer \
--namespace cmp-issuer-system \
--create-namespace \
--set 'credentialNamespaces={demo}'
This installs the CRDs, the RBAC and the controller. It also grants cert-manager permission to approve requests for this issuer type, which cert-manager needs before it will act on them at all.
The controller has no cluster-wide Secret access, so each namespace holding issuer credentials is
authorized on its own. credentialNamespaces creates that authorization, as a RoleBinding granting
get on Secrets in demo and nowhere else. Name every namespace your issuers live in, and create them
before you install.
Check the controller is running and the namespace is authorized:
kubectl -n cmp-issuer-system get pods
kubectl get rolebinding -n demo
NAME READY STATUS RESTARTS AGE
cmp-issuer-controller-manager-7d9f8c5b4-x2ktp 1/1 Running 0 30s
NAME ROLE AGE
cmp-issuer-credential-reader-rolebinding ClusterRole/cmp-issuer-credential-reader 30s
Namespaces you add later, other ways to grant that same access, cert-manager running under a different ServiceAccount, what happens to the CRDs when you uninstall and how to hand approval to approver-policy are all in Installation.
Step 2: store the credentials¶
Store the PasswordBasedMac credential your CMP administrator gave you:
kubectl create secret generic cmp-credentials \
--namespace demo \
--from-literal=reference='<reference>' \
--from-literal=secret='<shared-secret>'
Store the CA certificate that signs your server's CMP responses. This is the CMP trust anchor, and it is not the same thing as TLS trust:
kubectl create secret generic cmp-trust \
--namespace demo \
--from-file=ca.crt=/path/to/cmp-ca.crt
Step 3: create the issuer¶
kubectl apply -f - <<'EOF'
apiVersion: certmanager.misiektoja.github.io/v1alpha1
kind: CMPIssuer
metadata:
name: demo-issuer
namespace: demo
spec:
endpoint:
url: http://cmp.example.com:8080/pkix/
protocol:
version: 2
initialEnrollment: P10CR
recipient: CN=Example CA,O=Example
confirmation: Explicit
protection:
type: PasswordBasedMac
passwordBasedMac:
secretRef:
name: cmp-credentials
cmpTrust:
caSecretRef:
name: cmp-trust
key: ca.crt
EOF
Check that it became ready:
kubectl get cmpissuers -n demo
NAME READY
demo-issuer True
If it reports False, the message names what is missing:
kubectl describe cmpissuer demo-issuer -n demo
A message naming a Secret usually means step 2 was skipped or used a different name.
Step 4: request a certificate¶
kubectl apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: demo-tls
namespace: demo
spec:
secretName: demo-tls
commonName: workload.example.com
privateKey:
algorithm: RSA
size: 2048
issuerRef:
name: demo-issuer
kind: CMPIssuer
group: certmanager.misiektoja.github.io
EOF
Watch it complete:
kubectl get certificate demo-tls -n demo
NAME READY SECRET AGE
demo-tls True demo-tls 12s
READY True means the CMP exchange finished, the response was authenticated and the certificate was
stored.
The controller logs one line for that outcome, which is the quickest account of what the CA issued:
kubectl logs -n cmp-issuer-system deploy/cmp-issuer-controller-manager -c manager | grep "Issued certificate"
{"level":"info","msg":"Issued certificate","CertificateRequest":{"name":"demo-tls-1","namespace":"demo"},
"issuer":"CMPIssuer/demo/demo-issuer","subject":"CN=workload.example.com","serialNumber":"3b8f2a41",
"notAfter":"2026-08-02T09:12:31Z","keyType":"RSA","keySize":2048,"duration":"412ms", ...}
Step 5: look at what you got¶
kubectl get secret demo-tls -n demo -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -subject -issuer -dates
subject=CN=workload.example.com
issuer=CN=Example CA, O=Example
notBefore=...
notAfter=...
The Secret holds tls.crt and tls.key in the usual cert-manager layout, so any workload that already
consumes a cert-manager Secret can consume this one unchanged.
If it did not work¶
| What you see | What it means |
|---|---|
CertificateRequest stays pending with no conditions |
cert-manager is not permitted to approve these requests, usually because it runs under a different ServiceAccount than the chart default |
Issuer Ready False naming a Secret |
Step 2 was skipped, a name does not match, or demo was not named in credentialNamespaces |
Ready False naming response protection or trust |
cmpTrust does not hold the CA that signs your server's CMP responses |
Ready False naming the response sender |
recipient does not name the authority your server answers as |
| Connection refused or timeout | Wrong endpoint URL, or a NetworkPolicy blocks the controller |
Check progress at any time with:
kubectl get cmptransactions -n demo
kubectl describe certificaterequest -n demo
kubectl logs -n cmp-issuer-system deploy/cmp-issuer-controller-manager -c manager -f
Troubleshooting covers these in more detail.
Where to go next¶
- Enrollment for the full request lifecycle
- Message protection to move from a shared secret to a bootstrap certificate
- HTTP and HTTPS transport to move off plain HTTP
- CMPIssuer reference for every field
- Tested PKIs for notes specific to your CMP server