Deploy SPIRE
This page provides details on how to install the Cofide Connect Control plane’s SPIRE server, which issues SPIFFE identities to the Connect API components, enabling them to communicate with each other and to authenticate clients using mTLS.
Unlike a standard Cofide SPIRE deployment where the Connect API acts as the data source, this server uses a SQL datastore directly.
This avoids a circular dependency — the control plane components require identities before they can connect to the Connect API.
As a result, identities are configured via Kubernetes custom resources (ClusterSPIFFEIDs) rather than through Connect’s attestation policies.
Example configuration is provided for deployments on GKE and EKS. Other deployment environments are possible - refer to the SPIRE helm chart documentation and the SPIFFE installation guide for full configuration options, or contact us for guidance on your particular use case.
Installation
Section titled “Installation”Deploy the SPIRE CRDs and SPIRE chart in the cluster.
The chart follows a three-namespace model, which is the recommended approach when running the Connect control plane in the same cluster as the SPIRE server that grants it identity.
spire-mgmt is used as the Helm management namespace, keeping release metadata separate from the runtime components.
The SPIRE server runs in spire-server under a restricted Pod Security Standard (PSS) policy, while the SPIRE agent and CSI driver run in spire-system under the privileged PSS policy, as they require host-level access for workload attestation.
helm repo add cofide https://charts.cofide.dev --force-updatehelm install spire-crds cofide/spire-crds \ --namespace spire-mgmt \ --create-namespace \ --version 0.5.0-cofide.1 \ --wait \ --timeout 60shelm install spire cofide/spire \ --values values.yaml \ --namespace spire-mgmt \ --create-namespace \ --version 0.28.3-cofide.3 \ --wait \ --timeout 120sThe sections below document each part of the values.yaml file.
Each section can be combined into a single values.yaml to produce a complete configuration.
Global settings
Section titled “Global settings”The global.spire block sets properties shared across all components deployed by the chart.
global: spire: namespaces: create: true recommendations: enabled: true clusterName: <your-cluster-name> strictMode: true trustDomain: connect.example.cofide.dev jwtIssuer: https://oidc-discovery.example.cofide.dev caSubject: country: UK organization: Example commonName: example.cofide.devnamespaces.create: true creates all namespaces required by the chart.
Individual namespace creation can also be controlled with per-namespace flags.
recommendations.enabled: true applies recommended settings for production deployments.
clusterName labels this cluster within the trust domain.
strictMode: true enforces production-safe configuration defaults and is recommended for all deployments.
trustDomain is the SPIFFE trust domain for the Connect control plane and uniquely identifies all workloads running within it.
It is formatted as a domain name but does not need to be resolvable.
It should be unique to your organisation to avoid collisions with other deployments, and should not change after workloads start receiving SVIDs.
jwtIssuer is the public URL of the OIDC discovery endpoint and must be reachable by any system that needs to verify JWT-SVIDs issued by this SPIRE server.
The hostname must match the one used in the OIDC discovery provider’s service annotations and TLS certificate configured below.
caSubject sets the subject fields embedded in the CA certificate.
Replace these with your organisation’s details.
OIDC discovery provider
Section titled “OIDC discovery provider”The OIDC discovery provider exposes the SPIFFE OIDC discovery document, enabling external systems to verify JWT-SVIDs.
spiffe-oidc-discovery-provider: autoscaling: enabled: true minReplicas: 2 maxReplicas: 3 namespaceOverride: connectminReplicas: 2 configures a minimum of two replicas for availability.
maxReplicas: 3 allows the HPA to scale up to three replicas under load, while ensuring at least two are always running.
namespaceOverride: connect deploys the OIDC discovery provider into the connect namespace alongside the Connect control plane components.
Service exposure
Section titled “Service exposure”The service is exposed as an external LoadBalancer.
The annotations use GKE’s load balancer controller and external-dns to provision the load balancer and create a DNS record pointing to it.
spiffe-oidc-discovery-provider: service: type: LoadBalancer annotations: networking.gke.io/load-balancer-type: External external-dns.alpha.kubernetes.io/hostname: oidc-discovery.example.cofide.devThe service is exposed as an external LoadBalancer.
The annotations use the AWS Load Balancer Controller to provision an internet-facing Network Load Balancer, and external-dns to create a DNS record pointing to it.
spiffe-oidc-discovery-provider: service: type: LoadBalancer annotations: service.beta.kubernetes.io/aws-load-balancer-type: external service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: tcp external-dns.alpha.kubernetes.io/hostname: oidc-discovery.example.cofide.devtls.spire.enabled controls whether SPIRE manages the TLS certificate for the discovery endpoint.
Set it to false when using cert-manager or an existing Kubernetes secret.
cert-manager issues and renews the TLS certificate for the discovery endpoint.
issuer.create: false means the ClusterIssuer is expected to already exist.
spiffe-oidc-discovery-provider: tls: spire: enabled: false certManager: enabled: true issuer: create: false certificate: issuerRef: kind: ClusterIssuer name: <your-cluster-issuer> dnsNames: - oidc-discovery.example.cofide.devSPIRE uses the OIDC discovery provider’s X.509-SVID for TLS.
spiffe-oidc-discovery-provider: tls: spire: enabled: trueAn existing Kubernetes TLS secret is used for the discovery endpoint.
The secret must contain tls.crt and tls.key fields.
spiffe-oidc-discovery-provider: tls: spire: enabled: false externalSecret: enabled: true secretName: <your-tls-secret>SPIRE server
Section titled “SPIRE server”Cloud provider authentication
Section titled “Cloud provider authentication”The GCP examples use Workload Identity Federation with service account impersonation to grant the SPIRE pod access to GCP services.
Add the following annotation to the SPIRE server service account in the combined values.yaml:
spire-server: serviceAccount: annotations: iam.gke.io/gcp-service-account: <spire-server-gcp-service-account>The AWS examples assume EKS Pod Identity is used to grant the SPIRE pod access to AWS services. If using IRSA instead, add the IAM role annotation to the service account:
spire-server: serviceAccount: annotations: eks.amazonaws.com/role-arn: <your-iam-role-arn>spire-server: kind: statefulset replicaCount: 2 persistence: storageClass: <your-storage-class>kind: statefulset is used here to provide each SPIRE server pod with persistent storage.
replicaCount: 2 is the minimum for a highly available deployment, providing redundancy in the event of a pod failure.
The persistence block configures the persistent volume attached to each SPIRE server pod.
It is used to store the KMS key identifier file, which allows each pod to locate its signing key in KMS after a restart without regenerating new key material.
This is most important when pods may be replaced frequently.
If pod replacements are infrequent, the pod name can be used as the key identifier value instead, avoiding the need for persistent storage — though a replaced pod with a new name will generate new key material.
Both approaches are described in Key manager below.
High-throughput storage is not required.
Set storageClass to a storage class available in your cluster.
Datastore
Section titled “Datastore”SPIRE stores registration entries, bundles, and attestation records in a SQL database.
CloudSQL is accessed via the Cloud SQL Auth Proxy running as a sidecar container.
The proxy listens on 127.0.0.1:5432 and handles authentication using the GCP service account bound to the pod.
--auto-iam-authn enables IAM database authentication, which means password: unused is intentional (it is included because it is a required field).
sslmode: disable is intentional - the proxy handles TLS between the pod and GCP, so the local loopback connection from SPIRE to the proxy does not need it.
--psc routes the connection through Private Service Connect, keeping traffic on Google’s private network.
Remove this flag if you are not using Private Service Connect.
--structured-logs outputs proxy logs in JSON format, making them easier to ingest into a log aggregation pipeline.
The proxy runs as a non-root user with all capabilities dropped.
spire-server: dataStore: sql: databaseType: postgres databaseName: <your-database-name> host: 127.0.0.1 port: 5432 username: <spire-server-gcp-service-account-without-gserviceaccount-com> password: unused options: - sslmode: disable extraContainers: - name: cloud-sql-proxy image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.22.0 args: - --auto-iam-authn - --port=5432 - --psc - --structured-logs - <your-cloudsql-connection-name> securityContext: runAsNonRoot: true runAsUser: 65532 runAsGroup: 65532 allowPrivilegeEscalation: false capabilities: drop: - ALL seccompProfile: type: RuntimeDefaultThe Cloud SQL proxy version above is the latest at the time of writing. Check the Cloud SQL Auth Proxy releases for the latest version before deploying.
AWS RDS is accessed directly without a sidecar proxy.
Using aws_postgres as the database type enables IAM authentication, so no password is required in the connection string.
sslmode=verify-full is required because RDS IAM authentication enforces TLS, and verify-full ensures the server certificate is validated against the RDS CA bundle.
spire-server: dataStore: sql: databaseType: aws_postgres region: <your-aws-region> connectionString: "dbname=spire user=<your-db-user> host=<your-rds-endpoint> port=5432 sslmode=verify-full"Key manager
Section titled “Key manager”The key manager controls where SPIRE stores the private keys used to sign SVIDs.
GCP KMS stores SPIRE’s signing keys as versioned asymmetric keys.
key_ring is the full GCP resource path to the KMS key ring.
disk.enabled: false disables the default disk-based key manager so that GCP KMS is used exclusively.
SPIRE needs a stable identifier per server instance to look up the correct KMS key after a pod restart. Two approaches are supported.
Key identifier file stores the identifier on the persistent volume attached to each StatefulSet pod:
spire-server: keyManager: disk: enabled: false unsupportedBuiltInPlugins: keyManager: gcp_kms: plugin_data: key_ring: <your-gcp-key-ring-identifier> key_identifier_file: /run/spire/data/key_idKey identifier value uses a string as the identifier, avoiding the need to store the key ID on the persistent volume. Use the Kubernetes Downward API to inject the pod name as an environment variable so that each replica uses a distinct, stable identifier. StatefulSet pod names are stable across restarts, so each pod consistently maps to the same KMS key. If pods are frequently cycled with new names (for example, in a Deployment), each new name causes a new KMS key to be created; SPIRE has mechanisms to identify and remove keys that are no longer in use, but this may result in higher key churn. The value must contain only lowercase letters, numbers, underscores, and dashes, and must not exceed 63 characters — StatefulSet pod names satisfy this constraint.
spire-server: extraEnv: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name keyManager: disk: enabled: false unsupportedBuiltInPlugins: keyManager: gcp_kms: plugin_data: key_ring: <your-gcp-key-ring-identifier> key_identifier_value: $POD_NAMEAWS KMS stores SPIRE’s signing keys.
disk.enabled: false disables the default disk-based key manager so that AWS KMS is used exclusively.
SPIRE needs a stable identifier per server instance to look up the correct KMS key after a pod restart. Two approaches are supported.
Key identifier file stores the identifier on the persistent volume attached to each StatefulSet pod:
spire-server: keyManager: awsKMS: enabled: true keyIdentifierFile: enabled: true region: <your-aws-region> disk: enabled: falseKey identifier value uses a string as the identifier, avoiding the need to store the key ID on the persistent volume. Use the Kubernetes Downward API to inject the pod name as an environment variable so that each replica uses a distinct, stable identifier. StatefulSet pod names are stable across restarts, so each pod consistently maps to the same KMS key. If pods are frequently cycled with new names (for example, in a Deployment), each new name causes a new KMS key to be created; SPIRE has mechanisms to identify and remove keys that are no longer in use, but this may result in higher key churn.
spire-server: extraEnv: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name keyManager: awsKMS: enabled: true keyIdentifierValue: enabled: true identifier: $POD_NAME region: <your-aws-region> disk: enabled: falseUpstream authority
Section titled “Upstream authority”By default, SPIRE acts as its own root CA. An upstream authority can optionally be configured to chain SPIRE’s CA to an external root, in which case SPIRE obtains a signed intermediate CA certificate from the upstream and uses that to issue SVIDs to workloads. SVIDs issued by this SPIRE server will chain up to the upstream root CA, so any relying party that already trusts the upstream CA will also trust these SVIDs without additional trust configuration.
GCP Certificate Authority Service acts as the root CA.
root_cert_spec identifies which CA pool to use and which CA within that pool via a label selector (label_key and label_value).
spire-server: unsupportedBuiltInPlugins: upstreamAuthority: gcp_cas: plugin_data: root_cert_spec: project_name: <your-gcp-project-name> region_name: <your-gcp-region> ca_pool: <your-ca-pool> label_key: <your-ca-label-key> label_value: <your-ca-label-value>AWS Private CA acts as the root CA.
certificateAuthorityARN is the ARN of the PCA certificate authority to use.
signingAlgorithm controls the algorithm used to sign the intermediate CA certificate issued to SPIRE.
Refer to the AWS Private CA documentation for the full list of supported algorithms.
spire-server: upstreamAuthority: awsPCA: enabled: true certificateAuthorityARN: <your-pca-arn> region: <your-aws-region> signingAlgorithm: SHA512WITHRSANode attestor
Section titled “Node attestor”The node attestor is how SPIRE verifies the identity of the Kubernetes node that a workload is running on before issuing an SVID.
spire-server: nodeAttestor: k8sPSAT: audience: - spire-serverk8sPSAT (Kubernetes Projected Service Account Tokens) is the recommended attestor for Kubernetes deployments.
audience ensures the projected service account tokens are restricted to the SPIRE server, preventing them from being used to authenticate with other services in the cluster.
Identities
Section titled “Identities”ClusterSPIFFEIDs define which pods receive SPIFFE identities and what form those identities take.
They are managed by the SPIRE controller manager.
This configuration issues SVIDs to the Connect control plane components so that they can communicate with each other and to authenticate clients using mTLS.
See the introduction above for why ClusterSPIFFEIDs are used here rather than Connect’s attestation policies.
spire-server: controllerManager: identities: clusterSPIFFEIDs: default: enabled: false namespace-connect: spiffeIDTemplate: 'spiffe://{{ .TrustDomain }}/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}' namespaceSelector: matchLabels: kubernetes.io/metadata.name: connectdefault.enabled: false disables the catch-all ClusterSPIFFEID that would otherwise issue identities to every pod in the cluster.
Explicit identities are defined instead.
namespace-connect issues SPIFFE IDs to all pods in the connect namespace, i.e. the Connect control plane components.
spiffeIDTemplate uses Go template syntax to produce SPIFFE IDs in the form spiffe://{trust-domain}/ns/{namespace}/sa/{service-account}, giving each component a distinct identity based on its Kubernetes service account.
Telemetry
Section titled “Telemetry”spire-server: telemetry: prometheus: enabled: trueThis enables Prometheus metrics scraping on the SPIRE server pod and is recommended for production deployments.
© 2026 Cofide Limited. All rights reserved.