Skip to main content

Kubernetes / Helm

The edgepki-node Helm chart runs Mesh Nodes as a StatefulSet. Each replica gets its own persistent volume for identity and state, a stable DNS name through a headless Service, and the same hardened pod settings: non-root (UID 65532), read-only root filesystem, all capabilities dropped, RuntimeDefault seccomp, and no service-account token unless the Kubernetes CSR signer is enabled.

What the chart creates​

ObjectName (release mesh)Notes
StatefulSetmesh-edgepki-nodereplicaCount replicas (default 3), container port 9443, readiness and liveness probes on GET /healthz over HTTPS
Servicemesh-edgepki-nodeHeadless (clusterIP: None), port 9443
ConfigMapmesh-edgepki-nodeGenerated config.json, mounted at /etc/edgepki
PodDisruptionBudgetmesh-edgepki-nodeminAvailable: 2 by default
PersistentVolumeClaimstate-mesh-edgepki-node-NReadWriteOnce, 1Gi by default, mounted at /var/lib/edgepki
ServiceAccount, Lease, ClusterRole, bindingsmesh-edgepki-node-csr-signerOnly when kubernetesCsr.enabled=true

Each pod advertises its own local API URL, https://<pod>.mesh-edgepki-node.<namespace>.svc:9443, so ACME clients and other workloads can address a specific replica.

Before you install​

  1. Get the chart. Download sectigo-edge-helm-charts.tgz from ConsoleDownloads & install (it contains the edgepki-node and sharppki-signer charts) and extract it.
  2. Verify the image. The release's container-images.json records the signed image digest. Verify it with cosign (below) and deploy by digest with image.digest; when a digest is set, the chart ignores the tag.
  3. Create a pull Secret for the private registry, scoped to the namespace and limited to package read access, and reference it with imagePullSecrets. Never put registry credentials in values.
  4. Choose a hardware key provider. config.nodeKeyProvider is required: tpm2 (expose /dev/tpmrm0 through a reviewed TPM device plugin) or pkcs11 (the vendor module must be in the image; put the PIN in the credential Secret under pkcs11-pin).
  5. Create an enrollment package in ConsoleMesh nodes with the Kubernetes / Helm runtime and download the signed package plus the policy trust and registration trust files from the same dialog.
  6. Create the trust Secrets (below).

Verify the image digest​

workstation (bash)
subject="$(jq -r .node container-images.json)"
cosign verify \
  --certificate-identity-regexp '^https://github.com/hassard0/sharppki/.github/workflows/release.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  "$subject"

Use the sha256:… part of the verified subject as image.digest. Enforce the same repository, workflow, tag and issuer policy in your cluster admission controller.

Create the Secrets​

The chart mounts five Secrets by default. Create them in the target namespace before the first install:

Secret (default name)KeysContents
edgepki-node-credentials (existingSecret)tls.crt, tls.keyLocal API server certificate and key
ca.crtRoot CA for the Sectigo Edge server connection (tls_root_ca_file)
client.crt, client.keyOrganization-issued outbound mTLS identity
enrollment-tokenOne-time enrollment token (read from the environment, never written to the ConfigMap)
pkcs11-pinOnly when nodeKeyProvider=pkcs11
edgepki-workload-trustca.crtCA bundle for your workload trust domain only
edgepki-policy-trustpolicy-trust.jsonPolicy trust file from the enrollment dialog
edgepki-registration-trustregistration-trust.jsonRegistration trust file from the enrollment dialog
edgepki-acme-eabeab-credentials.jsonACME external-account credentials. Start with an empty store: {"version":1,"credentials":[]}

Optional Secrets: edgepki-otlp-audit (ca.pem, client.pem, client-key.pem) for audit export, edgepki-est-ca-bundle (ca-bundle.pem) for EST, and a dependency-probe Secret for active dependency discovery.

The one-time token is the enrollment_token value inside the signed package's payload. Write it to a private file and load it with --from-file, so the token never appears in a command-line argument:

workstation (bash)
kubectl create namespace edgepki
printf '%s' '{"version":1,"credentials":[]}' > eab-credentials.json
kubectl -n edgepki create secret generic edgepki-node-credentials \
  --from-file=tls.crt=server.crt --from-file=tls.key=server.key \
  --from-file=ca.crt=edge-server-ca.crt \
  --from-file=client.crt=edge-client.crt --from-file=client.key=edge-client.key \
  --from-file=enrollment-token=./enrollment-token
secret/edgepki-node-credentials created
kubectl -n edgepki create secret generic edgepki-workload-trust --from-file=ca.crt=workload-ca.crt
secret/edgepki-workload-trust created
kubectl -n edgepki create secret generic edgepki-policy-trust --from-file=policy-trust.json=policy-trust.json
secret/edgepki-policy-trust created
kubectl -n edgepki create secret generic edgepki-registration-trust --from-file=registration-trust.json=registration-trust.json
secret/edgepki-registration-trust created
kubectl -n edgepki create secret generic edgepki-acme-eab --from-file=eab-credentials.json=eab-credentials.json
secret/edgepki-acme-eab created
One token enrolls one node

An enrollment token is single-use and bound to the exact node name you entered in the console. Set config.nodeName to that same name. The chart passes the credential Secret to every replica, so only one replica can consume a given token. Plan how each replica will be enrolled before scaling beyond one replica, and confirm the procedure with Sectigo support if you need several enrolled replicas from one release.

Install​

workstation (bash)
tar -xzf sectigo-edge-helm-charts.tgz
helm upgrade --install mesh ./edgepki-node \
  --namespace edgepki \
  --set config.tenantId=acme-corp \
  --set config.nodeName=k8s-prod-01 \
  --set config.workloadTrustDomain=acme-corp.example \
  --set 'config.allowedSpiffePrefixes={spiffe://acme-corp.example/}' \
  --set 'config.allowedDnsSuffixes={.acme-corp.example}' \
  --set config.nodeKeyProvider=tpm2 \
  --set image.digest=sha256:4b1f0c9e7d2a6b8e3f5c1a9d0e7b2c4f6a8d1e3b5c7f9a0d2e4b6c8f1a3d5e7b \
  --set 'imagePullSecrets[0].name=sectigo-edge-registry'
Release "mesh" does not exist. Installing it now.
NAME: mesh
NAMESPACE: edgepki
STATUS: deployed
REVISION: 1

Tenant, names, trust domain and digest above are examples; use the values from your enrollment package. Then watch the pods become ready:

workstation (bash)
kubectl -n edgepki get pods -l app.kubernetes.io/instance=mesh
NAME                  READY   STATUS    RESTARTS   AGE
mesh-edgepki-node-0   1/1     Running   0          2m
kubectl -n edgepki logs mesh-edgepki-node-0 --tail=20

Values reference​

Core (config.*)​

ValueDefaultMaps to config.json
config.tenantIdreplace-metenant_id
config.nodeNamekubernetesnode_name
config.edgeUrlhttps://api.sharppki.comedge_url
config.localApiUrlhttps://sharppki-node.sharppki.svc.cluster.local:9443local_api_url (overridden per pod by SECTIGO_EDGE_LOCAL_API_URL)
config.workloadTrustDomainreplace-me (required)workload_trust_domain
config.allowedSpiffePrefixes["spiffe://replace-me/"]allowed_spiffe_prefixes
config.trustEventSubscriberSpiffePrefixes[]trust_event_subscriber_spiffe_prefixes
config.localUiEnabledfalselocal_ui_enabled
config.localUiAllowedSpiffePrefixes[]local_ui_allowed_spiffe_prefixes
config.allowedDnsSuffixes[".example.com"]allowed_dns_suffixes
config.maxValiditySeconds604800max_validity_seconds
config.pollIntervalSeconds5poll_interval_seconds
config.policyBundleMaxTtlSeconds300policy_bundle_max_ttl_seconds
config.registrationGrantMaxTtlSeconds300registration_grant_max_ttl_seconds
config.requirePqcTransporttruerequire_pqc_transport
config.nodeKeyProvider"" (required)node_key_provider (tpm2 or pkcs11)
config.allowSoftwareNodeKeyfalseallow_software_node_key
config.pkcs11ModulePath, pkcs11TokenLabel, pkcs11TokenSerial, pkcs11KeyIdHex, pkcs11KeyLabel, pkcs11MaxSessionsempty, 8pkcs11_* keys

The chart always sets listen_address to 0.0.0.0:9443, state_directory to /var/lib/edgepki, acme_external_account_required to true, cloud_fallback_enabled to true and insecure_development to false.

Image, storage and scheduling​

ValueDefaultNotes
replicaCount3One hardware identity per replica
image.repositoryghcr.io/hassard0/sectigo-edge-node
image.tag0.1.26Ignored when image.digest is set
image.digest""Prefer the signed digest from container-images.json
imagePullSecrets[]Namespace-scoped pull Secret
storage.size / storage.storageClassName1Gi / ""Per-replica state volume
service.port9443
resourcesrequests 50m/64Mi, limits 500m/256Mi
podDisruptionBudget.minAvailable2
existingSecret, existingWorkloadTrustSecret, existingPolicyTrustSecret, existingRegistrationTrustSecret, existingAcmeEabSecretsee Secrets tableRequired Secret names

Optional features​

ValueDefaultFeature
otlpAudit.enabled, .exporterId, .endpoint, .existingSecret, .batchSize, .requestTimeoutSeconds, .pollIntervalSecondsfalse, primary-siem, "", edgepki-otlp-audit, 32, 10, 5Signed audit export to an OTLP /v1/logs endpoint
dependencyDiscovery.enabled, .probeIntervalSeconds, .observationTtlSeconds, .probeTimeoutSeconds, .probes, .existingSecretfalse, 60, 300, 10, [], ""Verified dependency discovery; probe files must be under /var/run/edgepki/dependency
spire.enabled, .serverApiSocket, .parentId, .socketHostPathfalse, /run/spire/server/private/api.sock, "", ""Direct SPIRE Entry API reconciliation — see SPIFFE / SPIRE
kubernetesCsr.*disabledCustom CSR signer — see Kubernetes CSR
est.enabled, .adapterReference, .existingCaBundleSecretfalse, local/est-builtin, edgepki-est-ca-bundleEST listener — see EST

The NGINX, Apache, Java PKCS#12, HashiCorp Vault and Microsoft ADCS adapters are not exposed as chart values.

Accessing a replica​

The Service is headless and there is no Ingress. For operator access, port-forward to a specific pod:

workstation (bash)
kubectl -n edgepki port-forward pod/mesh-edgepki-node-0 9443:9443
Forwarding from 127.0.0.1:9443 -> 9443

Then use the sectigo-edge CLI against https://127.0.0.1:9443 (the local API certificate must be valid for the name you connect to, or use -url with the pod DNS name from inside the cluster).

danger

Never expose the node through a public Ingress, and never terminate client TLS at a proxy in front of it. Workload and operator identities are client certificates that must reach the node end to end.

Upgrades and namespace considerations​

  • Upgrade by verifying the new image digest and running helm upgrade with the new image.digest. State and identity live on each replica's volume and are preserved. See Upgrades.
  • After the first enrollment, remove enrollment-token from the credential Secret; an enrolled node no longer needs it.
  • Update ACME credentials by replacing edgepki-acme-eab from a file; the node re-reads it for every new account, so no restart is needed.
  • Kubernetes CSR objects are cluster-scoped. If you enable the CSR signer, use a dedicated Mesh Node release and review its cluster-wide list permission.
  • Every peer pod can read a shared Secret. Where credential confidentiality between failure domains matters, deploy separately scoped releases with their own Secrets.