Skip to main content

Kubernetes CSR

A Mesh Node running in your cluster can act as an outbound-only custom signer for the stable certificates.k8s.io/v1 CertificateSigningRequest (CSR) API. Workloads create CSRs with your custom signerName; after an independent approval, the node issues them through Sectigo Edge policy and writes the certificate into status.certificate.

The node is a signer, not an approver. It never adds the Approved=True condition and never reads Kubernetes Secrets.

How it works​

  1. A workload creates a CSR with the configured spec.signerName and the exact managed label.
  2. An administrator or a policy controller validates the request and approves it (Approved=True).
  3. The node polls the API with a projected, automatically rotated service-account token and validates the object, the API-supplied requester identity, the namespace allowlist, signer, label, usages, lifetime, PKCS#10 signature, DNS SANs, subject and CA constraints.
  4. It derives a requester such as spiffe://acme-corp.example/kubernetes/prod-ca/payments/api (trust domain, cluster ID, namespace, service account), evaluates your signed local policy, and submits the transaction through the normal approval quorum and protected signer.
  5. Before writing status.certificate, it checks that the leaf has the CSR public key, the exact DNS SAN set, no CA capability, no extra SAN types and no validity expansion.
  6. One replica holds a pre-created coordination Lease and signs; others are hot standbys and take over when the lease expires.
  7. The issued result is persisted (mode 0600) before the status update, so a conflict or restart replays the same certificate instead of issuing another.

Invalid approved requests get a Failed=True condition with a short machine reason. Transient API, authorization, quorum or signer failures stay retryable and mark the integration Degraded.

Requirements​

  • The Mesh Node deployed with the Helm chart in the cluster whose CSRs it signs.
  • A custom signer name in domain/name form; the domain must not be kubernetes.io or end in .kubernetes.io.
  • An independent approval process (an administrator or a controller).
  • spiffe://<workload trust domain>/kubernetes/<cluster id>/ must be covered by allowed_spiffe_prefixes — the node refuses to start otherwise.

Enable the signer with Helm​

workstation (bash)
helm upgrade mesh ./edgepki-node --namespace edgepki --reuse-values \
  --set kubernetesCsr.enabled=true \
  --set kubernetesCsr.clusterId=prod-ca \
  --set kubernetesCsr.signerName=issuer.sharppki.com/workload \
  --set 'kubernetesCsr.allowedNamespaces={payments,orders}'
ValueDefaultconfig.json key and limits
kubernetesCsr.enabledfalsekubernetes_csr_enabled
kubernetesCsr.adapterReferencelocal/kubernetes-inclusterkubernetes_adapter_reference (local/…)
kubernetesCsr.apiServerUrlhttps://kubernetes.default.svckubernetes_api_server_url (HTTPS)
kubernetesCsr.signerNameissuer.sharppki.com/workloadkubernetes_signer_name
kubernetesCsr.managedLabelsharppki.com/managed=truekubernetes_managed_label (one key=value)
kubernetesCsr.clusterIdreplace-mekubernetes_cluster_id (DNS label)
kubernetesCsr.allowedNamespaces["default"]kubernetes_allowed_namespaces (at least one)
kubernetesCsr.allowedUsagesall fourkubernetes_allowed_usages: digital signature, key encipherment, server auth, client auth
kubernetesCsr.maxRequestsPerPoll20kubernetes_max_requests_per_poll (1–100)
kubernetesCsr.requestTimeoutSeconds10kubernetes_request_timeout_seconds (2–30)
kubernetesCsr.leaseDurationSeconds30kubernetes_lease_duration_seconds (15–120)

The chart also sets the CA and token files from a projected volume (one-hour token), the lease namespace to the release namespace and the lease name to <release>-edgepki-node-csr-signer.

What the chart grants​

A dedicated ServiceAccount, a pre-created Lease and an exact-signer ClusterRole allowing: list CSRs, update the CSR status subresource, the sign verb on your custom signer name only, and get/update on that one Lease. It cannot create leases, approve CSRs or read Secrets.

Cluster-wide read boundary

CSR objects are cluster-scoped, so Kubernetes RBAC cannot restrict list to a namespace or signer. The node enforces the namespace allowlist itself before issuance. Use a dedicated Mesh Node release for this adapter and review this boundary.

Configure in the console​

  1. Open ConsoleIntegrations and select Configure on Kubernetes CSR.
  2. Select Enable this integration and the node(s) of that release.
  3. Select the Certificate / identity profile used for CSR issuance (required; an empty profile reports profile_not_configured).
  4. Keep the Local adapter reference local/kubernetes-incluster or enter the value of kubernetesCsr.adapterReference.
  5. Select Save desired state.

The cloud schema never accepts a bearer token, CA private key or PKCS#11 PIN.

Policy requirements​

Issuance is evaluated against the selected profile: the derived requester must fall inside the profile's SPIFFE prefixes, every DNS SAN inside its DNS suffixes, and algorithm and lifetime inside its limits. The node's own allowed_spiffe_prefixes, allowed_dns_suffixes and max_validity_seconds also apply. Kubernetes manages renewal: workloads submit a new CSR before expiry; there is no dual-slot activation for this adapter.

Request a certificate (workload side)​

apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: payments-api
labels:
sharppki.com/managed: "true"
spec:
signerName: issuer.sharppki.com/workload
expirationSeconds: 3600
request: <base64-pem-pkcs10>
usages:
- digital signature
- key encipherment
- server auth
workstation (bash)
kubectl apply -f payments-api-csr.yaml
certificatesigningrequest.certificates.k8s.io/payments-api created
kubectl certificate approve payments-api
certificatesigningrequest.certificates.k8s.io/payments-api approved
kubectl get csr payments-api
NAME           AGE   SIGNERNAME                     REQUESTOR                                    REQUESTEDDURATION   CONDITION
payments-api   41s   issuer.sharppki.com/workload   system:serviceaccount:payments:api           1h                  Approved,Issued
kubectl get csr payments-api -o jsonpath='{.status.certificate}' | base64 -d > payments-api.pem

Trust anchors are distributed separately; they are never embedded in the CSR.

Verify​

workstation (bash)
kubectl -n edgepki port-forward pod/mesh-edgepki-node-0 9443:9443 &
sectigo-edge -ca local-api-ca.pem -cert workload-svid.pem -key workload-svid-key.pem integrations
{
"integrations": [
  {
    "kind": "kubernetes_csr",
    "adapter_reference": "local/kubernetes-incluster",
    "signer_name": "issuer.sharppki.com/workload",
    "health": "healthy",
    "last_reconciled_at": "2026-10-01T15:40:02Z",
    "observed": 3,
    "issued": 2,
    "rejected": 1,
    "pending": 0,
    "role": "leader"
  }
],
"node_id": "node_7c1e4b2a-93d5-4f61-8a0e-2b6d9f3c5e17"
}

Example output. The same information is available to agents through the read-only MCP tool edge.integrations. Before production, qualify in a real cluster: RBAC denial, token rotation, API conflicts, lease failover, replica races, signer outage, policy denial and restart recovery.

Troubleshooting​

CodeMeaningWhat to do
kubernetes_csr_not_configuredThe assigned node does not have the signer enabledEnable it in Helm
adapter_reference_invalidConsole reference differs from kubernetesCsr.adapterReferenceMake them identical
profile_not_configuredNo profile selected in the consoleSelect a profile
kubernetes_authorization_failedThe API returned 401/403Check the chart's RBAC objects and the projected token
kubernetes_resource_conflictThe API returned 409 while updating statusUsually transient; the node replays the persisted certificate
kubernetes_api_unavailableThe API server could not be reachedCheck apiServerUrl and network policy
kubernetes_lease_lostThis replica lost the coordination lease during workAnother replica takes over; investigate if it repeats
issuer_unavailableQuorum or signer temporarily unavailableRetries automatically; check node health and quorum
issuer_response_invalidThe issued certificate failed the post-issuance checksContact support with the transaction ID
state_persistence_failedThe node could not persist the result to its volumeCheck the PVC
CSR shows FailedThe approved request violated signer rules (namespace, usages, SANs, lifetime, CA flag, signature)Fix the request and submit a new CSR