Skip to main content

Deploy Software Trust Manager support for customer-hosted Private CA

Uses a container-based deployment model and is distributed as a Docker image, enabling consistent and portable execution across environments. It supports deployment in standalone Docker setups as well as orchestration platforms such as Kubernetes and OpenShift, and can also be deployed using Docker Swarm for scalable container management.

Before configuring Software Trust Manager daemon, ensure that you have:

  • Kubernetes cluster access with kubectl configured (or OpenShift cluster with oc CLI)

  • Helm 3.x installed

  • Docker installed (to pull/load the daemon image)

  • Outbound HTTPS access to Software Trust Manager cloud

  • Internal network access from Software Trust Manager daemon to customer-hosted Private CA

  • Kubernetes TLS secret for daemon ingress/service TLS (or OpenShift Route with TLS)

  • DigiCert Software Trust Manager Cloud service user API key (account admin) and customer-hosted Private CA API key available as secure secrets

Note

config.jobs.enabled: true is required for asynchronous operations (signing jobs, revocation, and keypair discovery), so you shoul not disable it.

This procedure walks you through deploying Software Trust Manager to support customer-hosted Private CA.

  1. Obtain the daemon image

    Use one of the following options:

    # Option A: Pull from Docker Hub
    docker pull digicertinc/stm-daemon:<version>
    
    # Option B: Load from an offline image archive
    docker load -i stm-daemon_<version>.tar
  2. Create an external values file

    Create a file named stm-daemon-values.yaml outside the image with the required deployment settings:

    image:
      repository: digicertinc/stm-daemon
      tag: "<version>"
      pullPolicy: IfNotPresent
    
    service:
      type: LoadBalancer
      port: 8443
    
    ingress:
      enabled: true
      className: "nginx"
      hosts:
        - host: daemon.stm.cluster.local
          paths:
            - path: /
              pathType: Prefix
      tls:
        - secretName: stm-daemon-tls
          hosts:
            - daemon.stm.cluster.local
    
    # Optional: Use hostAliases only if custom DNS resolution is needed
    # hostAliases:
    #   - ip: "<customer-ca-ip>"
    #     hostnames:
    #       - "ca.one.digicert.com"
    
    config:
      stmCloud:
        host: "one.digicert.com"
        port: 443
        scheme: "https"
      caManager:
        host: "ca.one.digicert.com"
        port: 443
        scheme: "https"
        enablePartitionSupport: true
      server:
        port: 8443
        tlsEnabled: true
        logLevel: "debug"
      hsmRouting:
        enabled: true
      hierarchySync:
        enabled: true
        accountId: "<account-id>"
        intervalMinutes: 60
      partitionSync:
        enabled: true
        intervalMinutes: 60
      jobs:
        enabled: true
        pollIntervalSeconds: 20
      proxy:
        timeoutSeconds: 30
        
    secrets:
      create: true
      tlsCertSecret: "stm-daemon-tls"
      # DigiCert Software Trust Manager Cloud service user API key (account admin) for authenticating with DigiCert Software Trust Manager Cloud
      stmApiKey: "<stm-cloud-service-user-api-key>"
      caManagerApiKey: "<ca-manager-key>"

    Note

    Backend TLS verification is always enforced. Ensure the customer-hosted private CA presents a certificate trusted by the daemon.

    Here are the values in Values in stm-daemon-values.yaml file:

    Value

    Description

    image.repository

    Container image repository name

    image.tag

    Container image version tag to deploy

    image.pullPolicy

    Image pull behavior (IfNotPresent, Always, or Never)

    service.type

    Kubernetes service type (LoadBalancer, ClusterIP, or NodePort)

    service.port

    Service port exposed by Kubernetes for the daemon

    ingress.enabled

    Enables/disables ingress creation

    ingress.className

    Ingress controller class (for example nginx)

    ingress.hosts[].host

    The production hostname used by clients to reach Software Trust Manager daemon (for example stm-daemon.example.com).

    ingress.hosts[].paths[]

    URL paths routed to the daemon service

    ingress.tls[].secretName

    TLS secret containing tls.crt and tls.key for ingress host

    ingress.tls[].hosts[]

    Hosts covered by the ingress TLS certificate

    hostAliases[].ip

    IP mapped inside pod /etc/hosts

    hostAliases[].hostnames[]

    Hostnames resolved to that IP from inside daemon pod

    config.stmCloud.host

    Software Trust Manager cloud hostname the daemon connects to (one.digicert.com).

    config.stmCloud.port

    Software Trust Manager cloud port (usually 443)

    config.stmCloud.scheme

    Protocol for Software Trust Manager cloud (httpsrecommended)

    config.caManager.host

    Customer-hosted Private CA hostname or IP (for example ca.one.digicert.com).

    config.caManager.port

    Customer-hosted Private CA port

    config.caManager.scheme

    Protocol to customer-hosted Private CA (http or https)

    config.caManager.enablePartitionSupport

    Enables partition sync support

    config.server.port

    Daemon listener port inside container

    config.server.tlsEnabled

    Enables HTTPS listener on daemon

    config.server.logLevel

    Runtime logging level (debug, info, warn, error)

    config.hsmRouting.enabled

    Routes supported operations to customer-hosted Private CA CA/HSM path

    config.hierarchySync.enabled

    Enables CA hierarchy sync background job

    config.hierarchySync.accountId

    Account ID used for hierarchy sync scope

    config.hierarchySync.intervalMinutes

    Hierarchy sync interval

    config.partitionSync.enabled

    Enables HSM partition sync background job

    config.partitionSync.intervalMinutes

    Partition sync interval

    config.jobs.enabled

    Enables daemon background job polling/execution

    config.jobs.pollIntervalSeconds

    Job poll interval

    config.proxy.timeoutSeconds

    Backend request timeout

    secrets.create

    Creates Kubernetes secret from values in this file when true

    secrets.tlsCertSecret

    Existing TLS secret name mounted into daemon pod (tls.crt/tls.key)

    secrets.stmApiKey

    DigiCert Software Trust Manager Cloud service user API key (account admin) for authenticating with DigiCert Software Trust Manager Cloud (when secrets.create=true)

    secrets.caManagerApiKey

    Customer-hosted Private CA API key value (when secrets.create=true)

  3. Deploy with Helm chart folder

    Deploy when the Helm chart folder is available

    use the following command if you have Helm chart folder locally available:

    helm upgrade --install stm-daemon ./helm/stm-daemon \
      --namespace stm-daemon \
      --create-namespace \
      --values ./stm-daemon-values.yaml

    Deploy when the Helm chart folder is not available

    If the image is provided without the chart files, use a chart package supplied separately (for example stm-daemon-<chart-version>.tgz):

    helm upgrade --install stm-daemon ./stm-daemon-<chart-version>.tgz \
      --namespace stm-daemon \
      --create-namespace \
      --values ./stm-daemon-values.yaml

    Note

    Provide a TLS certificate issued by a trusted CA for the daemon hostname via the stm-daemon-tls secret referenced by secrets.tlsCertSecret. The certificate is preserved across upgrades

  4. Deploy on OpenShift

    OpenShift deployment uses the same Helm chart with OpenShift-specific configurations. Use oc CLI instead of kubectl.

    Prerequisites for OpenShift:

    1. OpenShift cluster access with oc CLI configured

    2. Helm 3.x installed

    3. Appropriate permissions to create projects and deploy workloads

    4. OpenShift route or ingress for external access

    Create OpenShift project

    oc new-project stm-daemon

    Configure OpenShift-specific values

    Create stm-daemon-openshift-values.yaml with OpenShift route configuration:

    image:
      repository: digicertinc/stm-daemon
      tag: "<version>"
      pullPolicy: IfNotPresent
    
    # Use ClusterIP service with OpenShift Route
    service:
      type: ClusterIP
      port: 8443
    
    # OpenShift Route configuration (alternative to Ingress)
    route:
      enabled: true
      host: daemon.stm.apps.cluster.local
      tls:
        termination: passthrough
        insecureEdgeTerminationPolicy: Redirect
    
    # If using Ingress instead of Route
    ingress:
      enabled: false
    
    # Optional: hostAliases for custom DNS resolution
    # hostAliases:
    #   - ip: "<customer-ca-ip>"
    #     hostnames:
    #       - "ca.one.digicert.com"
    
    config:
      stmCloud:
        host: "one.digicert.com"
        port: 443
        scheme: "https"
      caManager:
        host: "ca.one.digicert.com"
        port: 443
        scheme: "https"
        enablePartitionSupport: true
      server:
        port: 8443
        tlsEnabled: true
        logLevel: "info"
      hsmRouting:
        enabled: true
      hierarchySync:
        enabled: true
        accountId: "<account-id>"
        intervalMinutes: 60
      partitionSync:
        enabled: true
        intervalMinutes: 60
      jobs:
        enabled: true
        pollIntervalSeconds: 20
      proxy:
        timeoutSeconds: 30
        skipBackendTlsVerify: false
    
    secrets:
      create: true
      tlsCertSecret: "stm-daemon-tls"
      # DigiCert Software Trust Manager Cloud service user API key (account admin) for authenticating with DigiCert Software Trust Manager Cloud
      stmApiKey: "<stm-cloud-service-user-api-key>"
      caManagerApiKey: "<ca-manager-key>"
    
    # OpenShift security context (if needed)
    securityContext:
      runAsNonRoot: true
      allowPrivilegeEscalation: false
      capabilities:
        drop:
          - ALL

    Create ConfigMap

    Create ConfigMap mounted at /run/machine-id for registrattion.

    oc create configmap stm-daemon-machine-id \
      --from-literal=machine-id=$(uuidgen | tr -d '-') \
      -n stm-daemon

    Deploy using Helm on OpenShift

    helm upgrade --install stm-daemon ./helm/stm-daemon \
      --namespace stm-daemon \
      --values ./stm-daemon-openshift-values.yaml
      --set-json 'extraVolumes=[{"name":"machine-id","configMap":{"name":"stm-daemon-machine-id"}}]'

    Deploy using packaged chart

    You must reuse the same UUID while performing upgrades.

    helm upgrade --install stm-daemon ./stm-daemon-<chart-version>.tgz \
      --namespace stm-daemon \
      --values ./stm-daemon-openshift-values.yaml

    Verify OpenShift deployment

    # Check deployment status
    oc get pods,svc,route -n stm-daemon
    
    # View route URL
    oc get route stm-daemon -n stm-daemon -o jsonpath='{.spec.host}'
    
    # Check logs
    oc logs -n stm-daemon -l app.kubernetes.io/name=stm-daemon --tail=100

    OpenShift route vs ingress

    OpenShift supports both routes (native) and ingress controllers:

    - Route (recommended for OpenShift): Native OpenShift resource with integrated load balancing

    - Ingress: Standard Kubernetes resource, requires ingress controller

    For route configuration, set route.enabled: true and ingress.enabled: false in your values file.

    Security Context Constraints (SCC)

    If your OpenShift cluster has strict SCC policies, you may need to adjust the security context or bind a custom SCC:

    For clusters that requires restricted security context:

    podSecurityContext:
      runAsNonRoot: true
      runAsUser: null      # OpenShift assigns UID from the project range
      fsGroup: null        # OpenShift assigns GID
      seccompProfile:
        type: RuntimeDefault

    For clusters that require an explicit binding:

    oc adm policy add-scc-to-user <custom-scc> -z stm-daemon -n stm-daemon
  5. Update client_tools to call Software Trust Manager daemon

    After deployment, configure smctl to call Software Trust Manager daemon instead of Software Trust Manager cloud directly.

    For Linux/macOS shell:

    export SM_HOST="https://daemon.stm.cluster.local"
    export SM_API_KEY="<user-api-key>"

    For Windows PowerShell:

    $env:SM_HOST="https://daemon.stm.cluster.local"
    $env:SM_API_KEY="<user-api-key>"

    Run a read-only command to validate keypair listing via daemon:

    smctl kp ls

    Run a healthcheck through smctl:

    smctl healthcheck

If your customer-hosted HSM already contains a keypair created outside Software Trust Manager, you can discover it directly from the Software Trust Manager online portal and surface it on the Software Trust Manager cloud. Once discovered, the key is available for signing and certificate operations. The discovered key is imported with the CUSTOMER_HOSTED_PRIVATE_CA_DEFAULT_USAGE usage type.

Prerequsites:

  • The Software Trust Manager daemon is deployed and healthy (see Get started).

  • config.jobs.enabled: true and config.partitionSync.enabled: true in your values file.

  • The daemon can reach the Customer-hosted Private CA (CA Manager).

  • Your user has the GENERATE_SM_KEYPAIR or MANAGE_SM_KEYPAIR permission.

  1. Initiate discovery

    On the Keypair list page in the Software Trust Manager online portal, select Discover HSM keypair, then provide the requested details along with either the public key of the key on the HSM or its label.

  2. Search on the customer-hosted HSM

    Software Trust Manager cloud and the Software Trust Manager daemon search for the specified key on the Customer-hosted HSM using the customer-hosted Private CA. The daemon processes the request on the interval configured by config.jobs.pollIntervalSeconds.

  3. Verify the discovered key

    Once found, the key is surfaced on STM cloud and appears on the keypair list page. You can also confirm it through the daemon:

    smctl kp ls

    The discovered key appears with usage type CUSTOMER_HOSTED_PRIVATE_CA_DEFAULT_USAGE.

    Note

    • Search by public key or label. Provide either the public key of the HSM key or its label to locate it.

    • Duplicates are skipped. A key that already exists in the account (matching public key or alias) is not re-imported.

    • Key not found. If no matching key exists on the HSM, discovery returns without importing anything; verify the public key/label and the target partition.

  1. Verify workload health

    Kubernetes:

    kubectl get pods,svc,ingress -n stm-daemon

    OpenShift:

    oc get pods,svc,route -n stm-daemon
  2. Verify daemon health endpoint

    Kubernetes:

    kubectl port-forward -n stm-daemon svc/stm-daemon 8443:8443
    curl -k https://localhost:8443/health

    OpenShift:

    oc port-forward -n stm-daemon svc/stm-daemon 8443:8443
    curl -k https://localhost:8443/health
  3. Configure the client to use the daemon endpoint

    Configure the client to use the daemon endpoint and run a read-only command.

  4. Check runtime logs

    Kubernetes:

    # Recent logs
    kubectl logs -n stm-daemon -l app.kubernetes.io/name=stm-daemon --tail=200
    
    # Stream logs continuously
    kubectl logs -n stm-daemon -l app.kubernetes.io/name=stm-daemon -f

    OpenShift:

    # Recent logs
    oc logs -n stm-daemon -l app.kubernetes.io/name=stm-daemon --tail=200
    
    # Stream logs continuously
    oc logs -n stm-daemon -l app.kubernetes.io/name=stm-daemon -f

Note

Configure the daemon with a TLS certificate issued by a trusted CA (or one recognized by the client trust store) for the production daemon hostname. Ensure the daemon hostname is DNS-resolvable from the client.

Error

Solution

Pod does not start

Verify config.caManager.host, secret values, and TLS secret name in your external values file.

Helm render fails when HSM routing is enabled

Provide customer-hosted Private CA host and customer-hosted Private CA key secret wiring.

Health check fails

Validate ingress/service routing and certificate chain trust

Sync does not run

Verify hierarchySync.enabled, partitionSync.enabled, and customer-hosted Private CA reachability

OpenShift: Pod security issues

Check Security Context Constraints (SCC). May need to adjust securityContext in values or bind custom SCC to service account.

OpenShift: Route not accessible

Verify route host is DNS-resolvable and TLS termination is set to passthrough for end-to-end TLS.

Discovery does not complete

Verify obs.enabled: truej, that the daemon pod is healthy, and daemon-to-Customer-hosted-CA connectivity.

OpenShift: Pod stuck in CreateContainerConfigError

Security context constraints violation; keep runAsUser/fsGroup as null or bind the nonroot security context constraint.

Helm install fails: TLS secret cannot be imported

A manually created stm-daemon-tls secret lacks Helm ownership metadata; let the chart create it, or add the Helm managed-by label/annotations.

Discovery does not complete

Confirm config.jobs.enabled: true and the daemon pod is healthy.

Key not found

Verify the public key/label is correct and the key exists on the Customer-hosted HSM/partition.

Key not visible after discovery

Verify the daemon can reach the Customer-hosted Private CA, and confirm the user permission and account.

Kubernetes:

To uninstall the Software Trust Manager daemon, use the following command:

helm uninstall stm-daemon -n stm-daemon

Optional cleanup:

kubectl delete namespace stm-daemon

OpenShift:

To uninstall the Software Trust Manager daemon, use the following command:

helm uninstall stm-daemon -n stm-daemon

Optional cleanup:

oc delete project stm-daemon