End-to-end guide for deploying the EQTY Lab Governance Platform on Kubernetes with Auth0 as the identity provider, AWS S3 for object storage, and AWS KMS for key management.
- Overview
- Prerequisites
- Infrastructure Setup
- Domain & TLS Configuration
- Configuring Auth0
- Generating Configuration with govctl
- Running Auth0 Bootstrap
- Creating Kubernetes Secrets
- Configuring values.yaml
- Deploying the Governance Platform
- Post-Install Setup & Verification
The Governance Platform consists of four microservices deployed via a single Helm umbrella chart (governance-platform), backed by a PostgreSQL database, and integrated with Auth0 for identity and access management.
flowchart TD
A[👥 Users] --> PE[🌍 Public Endpoint]
PE --> K8s
subgraph K8s[☸️ Kubernetes Cluster]
I[🚦 Ingress - NGINX + TLS] --> GS[🖥️ Governance Studio]
GS --> GSV[⚙️ Governance Service]
GS --> AUTH[🔐 Auth Service]
GS --> INT[🛡️ Integrity Service]
GSV -.-> PDF[📄 EQTY PDFGen - optional]
PDF -.-> AUTH
GSV --> DB[🗄️ PostgreSQL]
AUTH --> DB
INT --> DB
end
subgraph EXT[🧩 External Dependencies]
AUTH0[🔑 Auth0 - IdP]
OS[📦 AWS S3]
KM[🗝️ AWS KMS]
end
K8s --> EXT
| Service | Language | Description | Ingress Path |
|---|---|---|---|
| auth-service | Go | Authentication, authorization, token exchange | /authService/ |
| eqty-pdfgen | Python | Optional manifest → PDF/ZIP rendering | Internal only |
| governance-service | Go | Backend API, workflow engine, worker | /governanceService/ |
| governance-studio | React | Web UI for governance workflows | / |
| integrity-service | Rust | Verifiable credentials and lineage tracking | /integrityService/ |
| PostgreSQL | — | Shared database (Bitnami Helm chart) | Internal only |
All four application services are exposed through a single domain via NGINX Ingress with path-based routing. PostgreSQL is internal to the cluster.
These components live outside the governance-platform Helm chart and must be provisioned separately before deploying.
| Dependency | Purpose | Required? |
|---|---|---|
| Auth0 | Identity provider — manages users, applications, OAuth flows | Yes |
| AWS S3 | Artifact and document storage | Yes |
| AWS KMS | DID signing key management for verifiable credentials | Yes |
| DNS | A-record or CNAME pointing your domain to the cluster ingress | Yes |
| TLS Certificates | cert-manager with a ClusterIssuer/Issuer, or pre-provisioned certs | Yes |
The deployment uses an umbrella chart pattern. You deploy a single chart (governance-platform) which pulls in all subcharts as dependencies:
charts/
├── governance-platform/ # Umbrella chart — deploy this
│ ├── Chart.yaml # Declares subchart dependencies
│ ├── values.yaml # Default values for all services
│ ├── templates/ # Shared resources (secrets, config)
│ └── examples/ # Ready-to-use values files
│ ├── values-auth0.yaml # Auth0 deployment example
│ ├── values-entra.yaml # Microsoft Entra ID deployment example
│ ├── values-keycloak.yaml # Keycloak deployment example
│ └── secrets-sample.yaml # Secrets template
├── auth-service/ # Authentication subchart
├── governance-service/ # Backend API subchart
├── governance-studio/ # Frontend subchart
├── integrity-service/ # Credentials/lineage subchart
└── auth0-bootstrap/ # Auth0 tenant configuration (standalone)
The auth0-bootstrap chart is deployed separately — it runs a one-time Kubernetes Job that creates applications, a resource server (the Governance Platform API), custom scopes, client grants, the platform admin user, and two Auth0 Actions that enrich tokens with organization, role, and service-account claims.
The Auth0 bootstrap creates three applications and one resource server (API) in your Auth0 tenant:
| Resource | Type | Purpose |
|---|---|---|
Governance Platform Frontend |
Single Page Application (SPA) | Browser-based authentication for governance-studio using PKCE auth code flow (token_endpoint_auth_method: none) |
Governance Platform Backend |
Machine-to-Machine (M2M) | client_credentials grant; token validation, Auth0 Management API user lookups |
Governance Worker |
Machine-to-Machine (M2M) | client_credentials grant; automated governance workflow execution |
Governance Platform API |
Resource server | Audience for backend/worker tokens; declares the custom scopes (governance:declarations:create, etc.) |
The end-to-end deployment follows this order:
1. Provision infrastructure (AWS S3, AWS KMS, DNS, TLS)
│
2. Configure Auth0 (create bootstrap M2M application)
│
3. Generate configuration with govctl (bootstrap, secrets, values files)
│
4. Run auth0-bootstrap (creates applications, API, scopes, client grants, admin user, Actions in Auth0)
│
5. Create Kubernetes secrets (uses backend / worker client credentials from bootstrap logs)
│
6. Configure values.yaml
│
7. Deploy governance-platform (Helm umbrella chart)
│
├── PostgreSQL starts, initializes databases
├── governance-service starts, runs migrations
├── auth-service, integrity-service, governance-studio start
├── Post-install hook creates organization + admin user in DB (if enabled)
│
8. Post-install verification
Key ordering note: The
auth0-bootstrapchart must be run before deploying the governance-platform, because the platform services need valid OAuth client credentials at startup. The governance-platform chart includes a Helm post-install hook that automatically creates the organization and platform-admin user in the database after deployment, resolving the Auth0user_idvia the Management API.
| Tool | Minimum Version | Purpose |
|---|---|---|
| kubectl | 1.29+ | Kubernetes cluster management |
| Helm | 4.0+ | Chart deployment |
| aws | 2.0+ | AWS CLI (for S3 and KMS setup) |
| jq | 1.6+ | JSON processing (used by helper scripts) |
| openssl | — | Generating random secrets |
No IdP-specific CLI is required. Auth0 is configured entirely via its Management API by the
auth0-bootstrapKubernetes Job.
- Kubernetes 1.29+ with RBAC enabled
- NGINX Ingress Controller installed and configured as the default ingress class (see
scripts/nginx.sh) - cert-manager installed with a ClusterIssuer or Issuer configured for TLS (see
scripts/cert-issuer.sh) - Sufficient resources for the platform (recommended minimums):
| Component | CPU Request | Memory Request | Storage |
|---|---|---|---|
| auth-service | 250m | 256Mi | — |
| eqty-pdfgen | 100m | 256Mi | — |
| governance-service | 250m | 256Mi | — |
| governance-studio | 100m | 128Mi | — |
| integrity-service | 250m | 256Mi | — |
| PostgreSQL | 500m | 1Gi | 10Gi PVC |
An Auth0 tenant with the following:
- Tenant domain (e.g.,
your-tenant.us.auth0.com) - A Machine-to-Machine application in the Auth0 Dashboard, authorized for the Auth0 Management API with the following scopes:
read:clients,create:clients,update:clientsread:resource_servers,create:resource_servers,update:resource_serversread:client_grants,create:client_grants,update:client_grantsread:users,create:users,update:usersread:actions,create:actions,update:actions,delete:actions
- A platform admin email — the user will be created in the configured Auth0 database connection (default:
Username-Password-Authentication) - Network connectivity from the Kubernetes cluster to
https://<your-tenant>.auth0.com
You will need:
- Auth0 tenant domain — found in the Auth0 Dashboard under Settings → Tenant Settings
- Bootstrap M2M client ID and client secret — from your Management API M2M application's Settings tab
- Platform admin email — your choice; the bootstrap job will create this user in Auth0
Platform images are hosted on GitHub Container Registry (GHCR). You need:
- A GitHub Personal Access Token (PAT) with
read:packagesscope - Or access to a mirror registry containing the platform images
Provision the following before deployment:
- AWS S3 — bucket(s) + IAM user/role with read/write access for governance artifacts and integrity store
- AWS KMS — IAM user/role with
kms:CreateKey,kms:Sign,kms:Verify,kms:DescribeKey,kms:GetPublicKey,kms:CreateAlias,kms:ScheduleKeyDeletionpermissions
A domain name (or subdomain) that you control, with the ability to create A-records or CNAMEs pointing to your cluster's ingress controller external IP.
The platform uses a single domain with path-based routing:
| URL Path | Service |
|---|---|
https://governance.your-domain.com/authService/ |
auth-service |
https://governance.your-domain.com/governanceService/ |
governance-service (API) |
https://governance.your-domain.com/ |
governance-studio (UI) |
https://governance.your-domain.com/integrityService/ |
integrity-service |
No separate IdP domain is needed — Auth0 is a cloud-hosted service at https://<your-tenant>.auth0.com.
Before proceeding, confirm:
- Kubernetes cluster is running and
kubectlis configured - NGINX Ingress Controller is installed
- cert-manager is installed with a working Issuer/ClusterIssuer
- Auth0 tenant is accessible
- Bootstrap M2M application is created and authorized for the Management API with the required scopes
- Platform admin email is chosen
- AWS S3 buckets are provisioned
- AWS KMS IAM user/role is provisioned with signing permissions
- DNS domain is available and you can create records
- GitHub PAT with
read:packagesscope is available - Helm 4.0+ and kubectl 1.29+ are installed locally
- AWS CLI (
aws) is installed locally
Provision the following AWS resources before deploying. A running Kubernetes cluster with kubectl configured is assumed.
Terraform alternative: These resources can also be provisioned using Terraform instead of the CLI commands below.
Export these once so that every command in this guide is copy-paste-safe:
export NS=governance # Kubernetes namespace
export DOMAIN=governance.your-domain.com # Platform domain
export REGION=us-east-1 # AWS region
export ARTIFACTS_BUCKET=governance-artifacts # S3 bucket for governance artifacts
export INTEGRITY_BUCKET=integrity-store # S3 bucket for integrity store
export AUTH0_DOMAIN=your-tenant.us.auth0.com # Auth0 tenant domain (no scheme, no trailing slash)Create two S3 buckets and an IAM user:
# Create buckets
aws s3 mb s3://$ARTIFACTS_BUCKET --region $REGION
aws s3 mb s3://$INTEGRITY_BUCKET --region $REGION
# Create IAM user with programmatic access
aws iam create-user --user-name governance-storage
aws iam attach-user-policy --user-name governance-storage \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess # Or a scoped policy
# Create access key (needed for secrets later)
aws iam create-access-key --user-name governance-storageYou'll need these values for your values.yaml:
| Value | governance-service field | integrity-service field |
|---|---|---|
| Region | awsS3Region |
integrityAppBlobStoreAwsRegion |
| Artifacts bucket | awsS3BucketName |
— |
| Integrity bucket | — | integrityAppBlobStoreAwsBucket |
| Integrity folder (optional) | — | integrityAppBlobStoreAwsFolder |
The auth-service uses AWS KMS for DID signing key management. It dynamically creates per-user signing keys.
Create an IAM user or role with KMS permissions for DID signing:
# Create IAM policy for KMS access
aws iam create-policy \
--policy-name governance-kms-signing \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"kms:CreateKey",
"kms:CreateAlias",
"kms:DeleteAlias",
"kms:DescribeKey",
"kms:GetPublicKey",
"kms:ListAliases",
"kms:ListKeys",
"kms:ScheduleKeyDeletion",
"kms:Sign",
"kms:Verify",
"kms:TagResource"
],
"Resource": "*"
}]
}'
# Create IAM user and attach policy
aws iam create-user --user-name governance-kms-user
aws iam attach-user-policy \
--user-name governance-kms-user \
--policy-arn arn:aws:iam::YOUR_ACCOUNT_ID:policy/governance-kms-signing
# Create access keys
aws iam create-access-key --user-name governance-kms-userYou'll need these values for your values.yaml and secrets.yaml:
| Value | Field |
|---|---|
| Region | auth-service.config.keyManagement.aws_kms.region |
| Access Key ID | Secret: platform-aws-kms → access-key-id |
| Secret Access Key | Secret: platform-aws-kms → secret-access-key |
| Session Token | Secret: platform-aws-kms → session-token (optional) |
After completing this section, you should have:
| Resource | What You Need for Later |
|---|---|
| AWS S3 | Region, access key ID, secret access key, 2 bucket names |
| AWS KMS | Region, access key ID, secret access key |
These values will be used in Section 8 (Creating Secrets) and Section 9 (Configuring values.yaml).
If not already installed, use the provided helper script:
./scripts/nginx.shThis installs the ingress-nginx Helm chart into the ingress-nginx namespace.
The platform requires one domain for the governance services. No separate IdP domain is needed — Auth0 is hosted at https://<your-tenant>.auth0.com.
Create a DNS record pointing to your NGINX Ingress Controller's external IP:
# Find your ingress controller's external IP or hostname in the EXTERNAL-IP column
# Note: On EKS this will be a hostname (e.g., xxx.elb.amazonaws.com) rather than an IP
kubectl get svc -n ingress-nginx ingress-nginx-controllerThen create an A-record (or CNAME record if using an EKS load balancer hostname):
| Record | Type | Value |
|---|---|---|
governance.your-domain.com |
A or CNAME | <ingress-external-ip-or-hostname> |
EKS Note: EKS load balancers expose a hostname rather than an IP address. Use a CNAME record instead of an A-record.
The platform uses cert-manager to automatically provision TLS certificates from Let's Encrypt.
If not already installed, use the provided helper script:
./scripts/cert-issuer.shBy default this installs cert-manager into the ingress-nginx namespace. The recommended practice is to install it into its own cert-manager namespace:
./scripts/cert-issuer.sh --namespace cert-managercert-manager supports two issuer types:
- Issuer — namespace-scoped. Can only issue certificates for ingress resources within the same namespace. Use the
cert-manager.io/issuerannotation in your ingress. - ClusterIssuer — cluster-wide. Can issue certificates for ingress resources in any namespace. Use the
cert-manager.io/cluster-issuerannotation in your ingress.
The example values files use a namespace-scoped Issuer with the cert-manager.io/issuer annotation. If you prefer a ClusterIssuer (e.g., to share one issuer across multiple namespaces), adjust the kind and ingress annotations accordingly.
Option A: Namespace-scoped Issuer (used by example values)
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: letsencrypt-prod
namespace: governance
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: <email address>
privateKeySecretRef:
name: letsencrypt-production
solvers:
- http01:
ingress:
ingressClassName: nginx
EOFIngress annotation: cert-manager.io/issuer: "letsencrypt-prod"
Option B: ClusterIssuer
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: <email address>
privateKeySecretRef:
name: letsencrypt-production
solvers:
- http01:
ingress:
ingressClassName: nginx
EOFIngress annotation: cert-manager.io/cluster-issuer: "letsencrypt-prod"
Replace <email address> with your actual email address. This email is used by Let's Encrypt for certificate expiration notifications.
Note: The Issuer name (
letsencrypt-prod) must match the corresponding annotation in your ingress configuration. If you switch from Issuer to ClusterIssuer, update allcert-manager.io/issuerannotations tocert-manager.io/cluster-issuerin your values file.
Each service's ingress is configured with:
- A
cert-manager.io/issuerannotation that references the Issuer - A
tlsblock specifying the TLS secret name and hostname
For example, from values-auth0.yaml:
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/issuer: "letsencrypt-prod"
hosts:
- host: governance.your-domain.com
paths:
- path: "/authService(/|$)(.*)"
pathType: ImplementationSpecific
tls:
- secretName: prod-tls-secret
hosts:
- governance.your-domain.comcert-manager watches for ingress resources with the cert-manager.io/issuer annotation and automatically requests and renews certificates. The certificate is stored in the Kubernetes secret specified by secretName (e.g., prod-tls-secret).
All four services share the same TLS secret name and hostname since they run on the same domain with different paths.
After DNS propagation:
# Verify DNS resolution
dig $DOMAIN
# After deploying (Section 10), verify TLS certificate
kubectl get certificate -n $NSExpected certificate status when ready:
NAME READY SECRET AGE
prod-tls-secret True prod-tls-secret 2m
Tip: If
READYshowsFalse, runkubectl describe certificate -n $NSand check theEventssection for details. Common causes: DNS not yet propagated, Let's Encrypt rate limits, or incorrect Issuer configuration.
The Governance Platform requires applications and an API in Auth0 to handle authentication. The auth0-bootstrap chart automates this, but first you need to create a Machine-to-Machine application that the bootstrap job will use to call the Auth0 Management API.
If not already created:
kubectl create namespace $NSThe bootstrap job needs an M2M application with permission to create and configure applications, APIs, scopes, client grants, users, and Actions via the Auth0 Management API. This is a dedicated secret (auth0-management) separate from platform-auth0, which stores the application credentials used by the platform services at runtime.
- In the Auth0 Dashboard, go to Applications → Applications → Create Application.
- Choose Machine to Machine Applications and name it
Governance Bootstrap M2M. - Authorize it for the Auth0 Management API and grant the scopes below.
- Copy the Client ID and Client Secret from the application's Settings tab.
| Scope | Purpose |
|---|---|
read:clients, create:clients, update:clients |
Create and update SPA / M2M applications |
read:resource_servers, create:resource_servers, update:resource_servers |
Create and update the Governance Platform API |
read:client_grants, create:client_grants, update:client_grants |
Grant M2M clients access to APIs |
read:users, create:users, update:users |
Create the platform admin user and test users |
read:actions, create:actions, update:actions, delete:actions |
Create, update, deploy, and bind Auth0 Actions |
Two secrets are required before the bootstrap can run:
# 1. Bootstrap M2M credentials + the shared bearer token used by the post-login action
kubectl create secret generic auth0-management \
--from-literal=client-id=YOUR_MGMT_CLIENT_ID \
--from-literal=client-secret=YOUR_MGMT_CLIENT_SECRET \
--from-literal=auth-service-api-secret="$(openssl rand -base64 32)" \
--namespace $NS
# 2. Initial password for the platform admin user that the bootstrap will create in Auth0
kubectl create secret generic platform-admin \
--from-literal=password="$(openssl rand -base64 16)" \
--namespace $NSImportant:
auth0-managementis the bootstrap secret — it holds the Management API M2M credentials used by the auth0-bootstrap job. It is separate fromplatform-auth0(created later in Section 8), which will hold the backend application credentials produced by the bootstrap job.
About
auth-service-api-secret: This is a shared bearer token presented by the post-login Auth0 Action when it calls the auth-service claims-enrichment endpoint. The same value must later be placed into theplatform-auth-servicesecret asapi-secret(see Section 8) so that auth-service verifies the bearer correctly. Auth0 Action secrets are only written at action create/update time — rotating the token requires re-running the bootstrap job.
Confirm the secret was created and that the credentials can mint a Management API token:
# Verify the Kubernetes secret exists
kubectl get secret auth0-management -n $NS -o jsonpath='{.data}' | jq 'keys'
# Pull the credentials out and exchange them for a Management API token
MGMT_ID=$(kubectl get secret auth0-management -n $NS -o jsonpath='{.data.client-id}' | base64 -d)
MGMT_SECRET=$(kubectl get secret auth0-management -n $NS -o jsonpath='{.data.client-secret}' | base64 -d)
curl -s -X POST "https://$AUTH0_DOMAIN/oauth/token" \
-H "Content-Type: application/json" \
-d "{
\"grant_type\": \"client_credentials\",
\"client_id\": \"$MGMT_ID\",
\"client_secret\": \"$MGMT_SECRET\",
\"audience\": \"https://$AUTH0_DOMAIN/api/v2/\"
}" | jq '{token_type, expires_in}'A response with token_type: "Bearer" confirms the bootstrap M2M is correctly configured.
With the bootstrap M2M created and both secrets stored in Kubernetes, proceed to Section 6 to generate your deployment configuration files, or skip ahead to Section 7 if you prefer to configure files manually.
The govctl CLI tool generates the configuration files needed for the remaining deployment steps — bootstrap values, Helm values, and secrets. This is the recommended approach, as it produces a consistent, minimal configuration based on your environment.
Note: This tool generates the minimum viable configuration to get up and running. For advanced or service-specific options, refer to the individual chart READMEs under
charts/.
Requires Python 3.10+. From the govctl/ directory:
# With uv (recommended)
uv pip install -e .
# Or with pip
python3 -m venv env && source env/bin/activate
pip install -e .Verify the installation:
govctl --helpThe interactive wizard walks you through cloud provider, domain, environment, auth provider, and registry configuration:
govctl initFor non-interactive usage (all flags required):
govctl init -I \
--cloud aws \
--domain $DOMAIN \
--environment staging \
--auth auth0| Flag | Short | Description |
|---|---|---|
--cloud |
-c |
Cloud provider (gcp, aws, azure) |
--domain |
-d |
Deployment domain |
--environment |
-e |
Environment name |
--auth |
-a |
Auth provider (auth0, keycloak, entra) |
--output |
-o |
Output directory (default: output) |
--interactive/--no-interactive |
-i/-I |
Toggle interactive mode |
govctl produces the following files in the output directory:
| File | Contents | Used In |
|---|---|---|
bootstrap-{env}.yaml |
Auth0 tenant domain, API identifier, frontend callbacks, scopes | Section 7 — Running Auth0 Bootstrap |
secrets-{env}.yaml |
Secret values (some auto-generated, some to fill in) | Section 8 — Creating Kubernetes Secrets |
values-{env}.yaml |
Helm values for all platform services | Section 9 — Configuring values.yaml |
After generating your files:
- Review
bootstrap-{env}.yamlandvalues-{env}.yamlfor correctness - Fill in any remaining placeholder values in
secrets-{env}.yaml(marked with# REQUIREDcomments) - Continue to Section 7 to run the Auth0 bootstrap using your generated bootstrap file
Skipping govctl: If you prefer to configure files manually, you can start from the example values files in
charts/governance-platform/examples/andcharts/auth0-bootstrap/examples/instead. The subsequent sections cover both approaches.
The auth0-bootstrap chart runs a Kubernetes Job that configures Auth0 via the Management API. It creates applications, the resource server (Governance Platform API), custom scopes, client grants, the platform admin user, and two Auth0 Actions (post-login token enrichment and client-credentials-exchange service-account enrichment).
If you generated files with govctl in Section 6, use your
bootstrap-{env}.yamland skip to Run the Bootstrap.
Start from the example values file and customize it for your environment:
cp charts/auth0-bootstrap/examples/values.yaml bootstrap-values.yamlEdit bootstrap-values.yaml with your Auth0 tenant domain, API identifier, frontend URLs, and admin email:
auth0:
domain: "your-tenant.us.auth0.com"
api:
name: "Governance Platform API"
identifier: "https://governance.your-domain.com"
tokenLifetime: 86400
allowOfflineAccess: false
applications:
frontend:
name: "Governance Platform Frontend"
callbacks:
- "https://governance.your-domain.com/callback"
- "http://localhost:5173/callback"
logoutUrls:
- "https://governance.your-domain.com"
- "http://localhost:5173"
webOrigins:
- "https://governance.your-domain.com"
- "http://localhost:5173"
actions:
enabled: true
postLogin:
enabled: true
authService:
urlProduction: "https://governance.your-domain.com/authService"
clientCredentialsExchange:
enabled: true
users:
admin:
enabled: true
email: "admin@your-domain.com"
firstName: "Platform"
lastName: "Admin"
connection: "Username-Password-Authentication"./scripts/auth0/bootstrap-auth0.sh -f /path/to/bootstrap-values.yaml -n $NSThe script validates prerequisites (the auth0-management and platform-admin secrets must exist), loads the Auth0 action JavaScript sources from scripts/auth0/actions/ into a ConfigMap named auth0-actions-source, runs the Helm chart, monitors the job to completion, and displays the results with next steps.
Pass --skip-actions to skip the ConfigMap creation and the Action deployment steps (the flag also sets actions.enabled=false for the Helm install).
If invoking Helm directly, you must first create the auth0-actions-source ConfigMap that the bootstrap job mounts:
kubectl create configmap auth0-actions-source \
--from-file=scripts/auth0/actions/ \
-n $NSThen run the Helm install:
helm upgrade --install auth0-bootstrap ./charts/auth0-bootstrap \
--namespace $NS \
--values /path/to/bootstrap-values.yaml \
--wait \
--timeout 10mMonitor the job:
# Watch job status
kubectl get jobs -l app.kubernetes.io/name=auth0-bootstrap -n $NS -w
# View logs
kubectl logs -l app.kubernetes.io/name=auth0-bootstrap -n $NS -fExpected job status when complete:
NAME COMPLETIONS DURATION AGE
auth0-bootstrap 1/1 45s 1m
| Resource | Details |
|---|---|
| Frontend application (SPA) | Public client; token_endpoint_auth_method: none; PKCE auth code flow; oidc_conformant: true; grants: authorization_code, implicit, refresh_token |
| Backend application (M2M) | Confidential client; token_endpoint_auth_method: client_secret_post; client_credentials grant; client secret printed once in job logs |
| Worker application (M2M) | Confidential client; client_credentials grant; client secret printed once in job logs |
| Governance Platform API | Resource server identified by your auth0.api.identifier (e.g., https://governance.your-domain.com) with custom scopes |
| Custom scopes | governance:declarations:create, integrity:statements:create, read:organizations, write:organizations, read:projects, write:projects, read:evaluations, write:evaluations |
| Backend client grant on platform API | Backend M2M granted the configured apiScopes |
| Backend client grant on Management API | Backend M2M granted limited Management API scopes (read:users, update:users, create:users, read:roles, create:role_members) so the platform can perform user lookups |
| Worker client grant on platform API | Worker M2M granted the configured apiScopes |
| Platform admin user | Created in the configured database connection with the password from the platform-admin secret (when users.admin.enabled: true) |
| Post-login Action | Enriches user tokens via the auth-service claims-enrichment endpoint; bound to the post-login trigger |
| Client-credentials-exchange Action | Enriches M2M tokens with service-account user claims; bound to the credentials-exchange trigger |
The bootstrap script executes in a specific order due to dependencies:
- Authenticate against the Auth0 Management API using the bootstrap M2M credentials
- Create the Governance Platform API (resource server) with the configured custom scopes
- Create the frontend SPA application with callbacks, logout URLs, and CORS origins
- Create the backend M2M application (client secret printed once in logs)
- Create the worker M2M application (client secret printed once in logs)
- Grant the backend M2M client the configured
apiScopeson the Governance Platform API - Grant the backend M2M client the configured
managementApiScopeson the Auth0 Management API - Grant the worker M2M client the configured
apiScopeson the Governance Platform API - Create the platform admin user (if
users.admin.enabledistrue) - Create test users (if
users.testUsers.enabledistrue) - Create / update / deploy the post-login Action and bind it to the
post-logintrigger (ifactions.postLogin.enabledistrue) - Create / update / deploy the client-credentials-exchange Action and bind it to the
credentials-exchangetrigger (ifactions.clientCredentialsExchange.enabledistrue)
The backend and worker client secrets are auto-generated by Auth0 during bootstrap and only emitted once. You must retrieve them from the job logs to create the platform's Kubernetes secrets in the next step.
# View the complete bootstrap logs (secrets are printed at the end)
kubectl logs -l app.kubernetes.io/name=auth0-bootstrap -n $NSThe logs will contain output like:
=== SUMMARY ===
Frontend Client ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Backend Client ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Backend Client Secret: <secret-value>
Worker Client ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Worker Client Secret: <secret-value>
Save these values — you'll need them in Section 8 to create the
platform-auth0andplatform-governance-workerKubernetes secrets.
If the applications already existed (idempotent re-run), no new client secrets are printed. Rotate them manually in the Auth0 Dashboard under Applications → [app] → Settings → Rotate if needed.
# Verify the OpenID Connect discovery endpoint for your tenant
curl -s "https://$AUTH0_DOMAIN/.well-known/openid-configuration" | jq '.issuer'
# Expected output: "https://$AUTH0_DOMAIN/" (note the trailing slash — Auth0 issuers always have one)
# Verify the platform API resource server exists
MGMT_TOKEN=$(curl -s -X POST "https://$AUTH0_DOMAIN/oauth/token" \
-H "Content-Type: application/json" \
-d "{\"grant_type\":\"client_credentials\",\"client_id\":\"$MGMT_ID\",\"client_secret\":\"$MGMT_SECRET\",\"audience\":\"https://$AUTH0_DOMAIN/api/v2/\"}" \
| jq -r .access_token)
curl -s -H "Authorization: Bearer $MGMT_TOKEN" \
"https://$AUTH0_DOMAIN/api/v2/resource-servers" \
| jq '.[] | select(.name == "Governance Platform API") | {name, identifier}'| Issue | Solution |
|---|---|
Job fails with Failed to get Management API token |
Verify the auth0-management secret credentials; confirm the M2M is authorized for the Auth0 Management API; ensure auth0.domain matches your tenant exactly (no https://, no trailing slash) |
| Application already exists | The job is idempotent — it skips existing applications by name. To start over, delete the application from the Auth0 Dashboard |
| Client secret not shown in logs | Client secrets are only shown once during creation. If the app already existed, rotate the secret manually via Applications → [app] → Settings → Rotate |
Failed to create API / HTTP 422 |
An API with the same identifier may exist with conflicting settings. Confirm auth0.api.identifier is unique. The job updates scopes on an existing API but does not change signing algorithm, token lifetime, or offline-access policy |
Failed to create user / HTTP 400 (password strength) |
Auth0 password policies apply. Ensure the platform-admin secret password meets your tenant's policy (configure under Authentication → Database → [Connection] → Password Policy) |
| Timeout or deadline exceeded | Increase bootstrap.activeDeadlineSeconds; check connectivity from the cluster to https://<tenant>.auth0.com |
| Wrong connection name | Default users.admin.connection is Username-Password-Authentication. If you've renamed or deleted it, set users.admin.connection to the actual connection name |
Action create / deploy fails with HTTP 403 insufficient_scope |
Add read:actions, create:actions, update:actions, delete:actions to the bootstrap M2M's Management API authorization; re-run the bootstrap job |
| Action source ConfigMap not found | When deploying the chart by hand, create the ConfigMap first: kubectl create configmap auth0-actions-source --from-file=scripts/auth0/actions/ -n $NS. Pass --skip-actions to bypass |
Post-login action runs but event.secrets.AUTH_SERVICE_API_SECRET is undefined |
The auth-service-api-secret key was missing from auth0-management when the bootstrap ran. Patch the secret and re-run the bootstrap to re-deploy the action with the value |
The governance-platform chart requires several Kubernetes secrets to be available at deploy time. There are three ways to create them — choose one approach and follow only that subsection.
Already created in Section 5:
auth0-managementandplatform-admin. The remaining secrets below complete the set required to deploy the platform.
| Approach | Best For | What You Do |
|---|---|---|
| Option A — kubectl | Environments without file-based secrets management | Run kubectl create secret commands yourself. Secrets live outside of Helm and persist across helm uninstall / helm install cycles. |
| Option B — Helm-managed secrets | Teams with encrypted secrets workflows (SOPS, sealed-secrets, etc.) | Fill in a secrets values file and pass it to helm install. Helm creates the Secret objects for you. Keeps everything declarative. |
| Option C — govctl | Any environment (generates files for Option B) | Run govctl init to auto-generate random values; fill in provider credentials; then use the output as a Helm values file (same as Option B). |
Important: Do not mix approaches. If you use Option B or C (Helm-managed), do not also create the same secrets with
kubectl— Helm will fail if the Secret objects already exist. Conversely, if you use Option A (kubectl), leaveglobal.secrets.createat its default value offalse.
| Secret Name | Used By | Keys |
|---|---|---|
auth0-management |
auth0-bootstrap job + post-login Action | client-id, client-secret, auth-service-api-secret |
platform-admin |
auth0-bootstrap job | password |
platform-database |
governance-service, auth-service, integrity-service | username, password |
platform-auth0 |
auth-service, governance-service | client-id, client-secret, mgmt-client-id, mgmt-client-secret |
platform-auth-service |
auth-service | api-secret, jwt-secret |
platform-governance-worker |
governance-service worker | encryption-key, client-id, client-secret |
platform-aws-s3 |
governance-service, integrity-service | access-key-id, secret-access-key |
platform-aws-kms |
auth-service | access-key-id, secret-access-key, session-token (optional) |
platform-image-pull-secret |
All services | Docker registry credentials |
Note on
platform-auth0: themgmt-client-id/mgmt-client-secretkeys are typically the same asclient-id/client-secret(the backend application). This works because the backend M2M is granted both the platform API scopes and a limited set of Management API scopes (read:users,update:users, etc.) during bootstrap, so the same client can perform user lookups directly. If you prefer a dedicated Management API client, create another M2M application in Auth0 and use those credentials for themgmt-*keys.
Create each secret manually. Secrets are managed outside of Helm, so they persist across helm uninstall / helm install cycles.
Run these commands in order, replacing placeholder values with your actual credentials.
kubectl create secret generic platform-database \
--from-literal=username=postgres \
--from-literal=password="$(openssl rand -hex 32)" \
--namespace $NSUse the backend application's client ID and client secret retrieved from the bootstrap logs in Section 7:
kubectl create secret generic platform-auth0 \
--from-literal=client-id=YOUR_BACKEND_CLIENT_ID \
--from-literal=client-secret=YOUR_BACKEND_CLIENT_SECRET \
--from-literal=mgmt-client-id=YOUR_BACKEND_CLIENT_ID \
--from-literal=mgmt-client-secret=YOUR_BACKEND_CLIENT_SECRET \
--namespace $NSThe api-secret here must equal the auth-service-api-secret you put into the auth0-management secret in Section 5. The post-login Auth0 Action sends that value as a bearer token to the auth-service claims-enrichment endpoint, and auth-service verifies it against this api-secret. Pull the value back out of auth0-management so the two stay in sync:
API_SECRET=$(kubectl get secret auth0-management -n $NS -o jsonpath='{.data.auth-service-api-secret}' | base64 -d)
kubectl create secret generic platform-auth-service \
--from-literal=api-secret="$API_SECRET" \
--from-literal=jwt-secret="$(openssl rand -base64 32)" \
--namespace $NSUse the worker application's client ID and client secret from the bootstrap logs in Section 7:
kubectl create secret generic platform-governance-worker \
--from-literal=encryption-key="$(openssl rand -base64 32)" \
--from-literal=client-id=YOUR_WORKER_CLIENT_ID \
--from-literal=client-secret=YOUR_WORKER_CLIENT_SECRET \
--namespace $NSSkip this secret if you use IAM role access (IRSA). When
awsS3UseIamRole/integrityAppBlobStoreAwsUseIamRoleare enabled (see Service configuration), the services authenticate through the IAM role attached to their service account and this secret is not required.
kubectl create secret generic platform-aws-s3 \
--from-literal=access-key-id=YOUR_AWS_ACCESS_KEY_ID \
--from-literal=secret-access-key=YOUR_AWS_SECRET_ACCESS_KEY \
--namespace $NSkubectl create secret generic platform-aws-kms \
--from-literal=access-key-id=YOUR_AWS_ACCESS_KEY_ID \
--from-literal=secret-access-key=YOUR_AWS_SECRET_ACCESS_KEY \
--namespace $NS
# Optionally add: --from-literal=session-token=YOUR_AWS_SESSION_TOKENkubectl create secret docker-registry platform-image-pull-secret \
--docker-server=ghcr.io \
--docker-username=YOUR_GITHUB_USERNAME \
--docker-password=YOUR_GITHUB_PAT \
--docker-email=YOUR_EMAIL \
--namespace $NSAfter creating all secrets, skip ahead to Verify Secrets.
Instead of creating secrets with kubectl, you can declare secret values in a YAML file and let Helm create the Secret objects during helm install.
- Copy the sample secrets file to a secure location outside your repo:
cp charts/governance-platform/examples/secrets-sample.yaml my-secrets.yaml-
Open
my-secrets.yamland:- Ensure
global.secrets.createis set totrue - Set
global.secrets.auth.providertoauth0 - Uncomment the
auth0block underglobal.secrets.authand fill in the backend client ID and client secret from Section 7 (reuse them for themgmt-*keys) - Fill in all
REPLACE_WITH_*values for AWS S3 and AWS KMS - Generate random values where indicated (e.g.,
openssl rand -base64 32for encryption keys) - For
auth.apiSecret, reuse theauth-service-api-secretvalue from theauth0-managementsecret so the post-login Action can authenticate to auth-service
- Ensure
-
When deploying in Section 10, pass both your secrets file and values file to Helm:
helm upgrade --install governance-platform ./charts/governance-platform \
--namespace $NS \
--values my-secrets.yaml \
--values my-values.yaml \
--wait --timeout 15mWarning: Never commit
my-secrets.yamlto version control. Add it to.gitignore.
If you ran govctl init in Section 6, it generated a secrets-{env}.yaml file with random values already filled in for database password, API secrets, JWT secret, and encryption keys.
-
Open
secrets-{env}.yamland fill in the remaining values marked with# REQUIREDcomments: -
Replace the auto-generated
apiSecretfor auth-service with the value already stored inauth0-managementso the post-login Action's bearer token matches what auth-service verifies. -
The generated file has
global.secrets.create: true, so Helm will create the secrets for you. When deploying in Section 10, pass it alongside your values file:
helm upgrade --install governance-platform ./charts/governance-platform \
--namespace $NS \
--values secrets-staging.yaml \
--values values-staging.yaml \
--wait --timeout 15mIf you created secrets with kubectl (Option A), verify they exist before proceeding:
# List all platform secrets
kubectl get secrets -n $NS | grep -E 'platform-|auth0-'
# Verify a specific secret has the expected keys
kubectl get secret platform-auth0 -n $NS -o jsonpath='{.data}' | jq 'keys'
# Confirm the shared bearer token matches between auth0-management and platform-auth-service
diff \
<(kubectl get secret auth0-management -n $NS -o jsonpath='{.data.auth-service-api-secret}') \
<(kubectl get secret platform-auth-service -n $NS -o jsonpath='{.data.api-secret}') \
&& echo "OK: api-secret matches auth0-management.auth-service-api-secret"If you used Option B or C, Helm creates the secrets during helm install — skip this step and continue to Section 9.
The governance-platform Helm chart is configured through a single values file. Start from the Auth0 example and customize it for your environment.
You can either copy the example values file manually or use govctl to generate both values and secrets files interactively:
# Option A: Copy the example and customize manually
cp charts/governance-platform/examples/values-auth0.yaml my-values.yaml
# Option B: Use govctl to generate values and secrets
govctl initIf using govctl, it will generate a values-{env}.yaml and secrets-{env}.yaml pre-configured for your cloud provider, domain, and auth provider. See the govctl README for details.
If starting from the example file, values-auth0.yaml has all four services pre-configured for Auth0 with placeholder values you need to replace.
Set the domain at the top of your values file:
global:
domain: "governance.your-domain.com"
environmentType: "production" # Options: development, staging, productionThe global.secrets.create setting controls how secrets are provided. Leave it at false (default) if you created secrets with kubectl (Section 8, Option A). Set it to true only if you are using Helm-managed secrets via a secrets file (Section 8, Option B or Option C).
The auth-service handles authentication and authorization. Key configuration areas:
auth-service:
config:
# Identity Provider — must match your Auth0 setup
idp:
provider: "auth0"
issuer: "https://your-tenant.us.auth0.com/" # NOTE: trailing slash is required
skipIssuerVerification: false
auth0:
domain: "your-tenant.us.auth0.com"
managementAudience: "https://your-tenant.us.auth0.com/api/v2/"
apiIdentifier: "https://governance.your-domain.com" # Must match auth0.api.identifier from the bootstrap
# Key Management — AWS KMS for DID signing keys
keyManagement:
provider: "aws_kms"
aws_kms:
region: "us-east-1"| Field | Description | Where to Get It |
|---|---|---|
idp.issuer |
Auth0 issuer URL (trailing slash required) | https://<your-tenant>.auth0.com/ |
idp.auth0.domain |
Auth0 tenant domain | Auth0 Dashboard → Settings → Tenant Settings |
idp.auth0.managementAudience |
Auth0 Management API audience | Always https://<your-tenant>.auth0.com/api/v2/ |
idp.auth0.apiIdentifier |
Identifier of the Governance Platform API resource server | Whatever you set as auth0.api.identifier in bootstrap values |
keyManagement.aws_kms.region |
AWS KMS region | Your AWS region (e.g., us-east-1) |
The governance-service is the main backend API. Configure storage:
governance-service:
config:
# Storage — AWS S3
storageProvider: "aws_s3"
awsS3Region: "us-east-1"
awsS3BucketName: "governance-artifacts"
# awsS3UseIamRole: true # use an IAM role (IRSA) instead of static keys; see note belowNote: Unlike the Entra variant, Auth0 deployments do not require any IdP-specific fields on governance-service (no
entraTenantId). All Auth0 configuration lives underauth-service.config.idp.auth0andgovernance-studio.config.auth0*.
The frontend application. Configure Auth0 connection and feature flags:
governance-studio:
config:
# Auth0
auth0Domain: "your-tenant.us.auth0.com"
auth0ClientId: "your-frontend-spa-client-id" # Frontend application client ID from bootstrap logs
auth0Audience: "https://your-tenant.us.auth0.com/api/v2/"
# Feature flags
features:
governance: true # Governance workflows
lineage: true # Lineage trackingImportant:
auth0ClientIdis the Frontend SPA client ID from Section 7 — not the backend M2M. The studio runs in the browser and uses the PKCE auth code flow against the public SPA client.
The integrity-service handles verifiable credentials. Configure its AWS S3 storage:
integrity-service:
config:
integrityAppBlobStoreType: "aws_s3"
integrityAppBlobStoreAwsRegion: "us-east-1"
integrityAppBlobStoreAwsBucket: "integrity-store"
integrityAppBlobStoreAwsFolder: "" # Optional subfolder
# integrityAppBlobStoreAwsUseIamRole: true # use an IAM role (IRSA) instead of static keys; see note belowUsing IAM roles (IRSA) instead of static credentials: If your pods authenticate to S3 through an IAM role (EKS IRSA, or an instance profile) rather than an access key, set
awsS3UseIamRole: true(governance-service) andintegrityAppBlobStoreAwsUseIamRole: true(integrity-service). The credential env vars are then omitted, the AWS SDK uses the role's default credential chain, and theplatform-aws-s3secret is not required. Annotate each service's account with the role ARN:governance-service: serviceAccount: create: true annotations: eks.amazonaws.com/role-arn: arn:aws:iam::<account-id>:role/<governance-s3-role> integrity-service: serviceAccount: create: true annotations: eks.amazonaws.com/role-arn: arn:aws:iam::<account-id>:role/<integrity-s3-role>
Each service needs an ingress block. All four services share the same domain with path-based routing, but annotations vary per service. If you used govctl or started from values-auth0.yaml, the ingress is already configured correctly.
Key differences between services:
| Service | Path Pattern | Notes |
|---|---|---|
| auth-service | /authService(/|$)(.*) |
Regex rewrite + extra buffer size annotations (proxy-buffer-size, client-header-buffer-size, large-client-header-buffers) |
| governance-service | /governanceService(/|$)(.*) |
Regex rewrite to /$2 |
| governance-studio | / (pathType: Prefix) |
No regex or rewrite annotations |
| integrity-service | /integrityService(/|$)(.*) |
Regex rewrite + proxy-body-size: "0" (unlimited) |
Note: All four services must use the same
tls.secretName(e.g.,prod-tls-secret). cert-manager creates this secret automatically when it provisions the TLS certificate.
The Bitnami PostgreSQL chart is included as a dependency. Configure storage and resources:
postgresql:
enabled: true
primary:
persistence:
enabled: true
size: 10Gi
storageClass: "gp3" # EKS default StorageClass
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 2GiThe database password is pulled from the platform-database secret created in Section 8.
The governance-platform chart includes a Helm post-install/post-upgrade hook that automatically creates the organization and platform-admin user in the database after deployment. The admin user is looked up via the Auth0 Management API using their email (the auth0-bootstrap job has already created this user in Auth0). Enable it in your values file:
auth0:
createOrganization: true
organizationName: "governance"
displayName: "Governance Studio"
createPlatformAdmin: true
platformAdminEmail: "admin@your-domain.com" # Must match the user created by auth0-bootstrap
domain: "your-tenant.us.auth0.com" # Auth0 tenant domain — used to call the Management API for user lookup| Field | Description | Where to Get It |
|---|---|---|
createOrganization |
Enable organization creation in the database | Set to true |
organizationName |
Organization name (used as the internal identifier) | Your choice (e.g., governance) |
displayName |
Human-readable organization display name | Your choice |
createPlatformAdmin |
Enable platform-admin user creation in the database | Set to true |
platformAdminEmail |
Email of the platform admin user in Auth0 | Must match users.admin.email from the auth0-bootstrap values |
domain |
Auth0 tenant domain used for Management API user lookup | Your Auth0 tenant domain (e.g., your-tenant.us.auth0.com) |
The hook runs as a Kubernetes Job after Helm install/upgrade. It waits for database migrations to complete, looks up the platform admin's Auth0 user_id by email via the Management API, then creates (or updates) the organization and admin user records. The hook is idempotent — it's safe to run on every upgrade.
Important:
platformAdminEmailmust match an existing user in your Auth0 tenant. The hook will fail if the user cannot be found via the Management API. If you setusers.admin.enabled: falsein the auth0-bootstrap values, create the admin user manually in the Auth0 Dashboard (Authentication → Database → Users) before deploying.
Before deploying, verify your values file has:
-
global.domainset to your actual domain -
auth-service.config.idp.providerset toauth0 -
auth-service.config.idp.issuerset tohttps://<your-tenant>.auth0.com/(with trailing slash) -
auth-service.config.idp.auth0.domainset to your Auth0 tenant domain -
auth-service.config.idp.auth0.managementAudienceset tohttps://<your-tenant>.auth0.com/api/v2/ -
auth-service.config.idp.auth0.apiIdentifierset to match the bootstrapauth0.api.identifier -
auth-service.config.keyManagement.providerset toaws_kms -
auth-service.config.keyManagement.aws_kms.regionset -
governance-service.config.storageProviderset toaws_s3 -
governance-service.config.awsS3RegionandawsS3BucketNameset -
governance-studio.config.auth0Domainset to your Auth0 tenant domain -
governance-studio.config.auth0ClientIdset to the Frontend SPA client ID -
governance-studio.config.auth0Audienceset tohttps://<your-tenant>.auth0.com/api/v2/ -
integrity-service.config.integrityAppBlobStoreTypeset toaws_s3 -
integrity-service.config.integrityAppBlobStoreAwsRegionandintegrityAppBlobStoreAwsBucketset - All ingress
hostfields set to your domain - All ingress
tlsblocks using the samesecretName -
auth0.createOrganizationset totrue -
auth0.platformAdminEmailmatches the bootstrap admin email -
auth0.domainset to your Auth0 tenant domain
Before installing, pull the subchart dependencies:
helm dependency update ./charts/governance-platformThis downloads the Bitnami PostgreSQL chart and links the local subcharts (auth-service, governance-service, governance-studio, integrity-service).
If you created secrets with kubectl (Section 8, Option A):
helm upgrade --install governance-platform ./charts/governance-platform \
--namespace $NS \
--create-namespace \
--values /path/to/my-values.yaml \
--wait \
--timeout 15mIf you are using Helm-managed secrets (Section 8, Option B or C): pass the secrets file before the values file so that values can override if needed:
helm upgrade --install governance-platform ./charts/governance-platform \
--namespace $NS \
--create-namespace \
--values /path/to/my-secrets.yaml \
--values /path/to/my-values.yaml \
--wait \
--timeout 15mThe Helm install proceeds in this order:
- PostgreSQL starts and initializes the
governancedatabase - governance-service starts, runs database migrations on startup
- auth-service and integrity-service start (depend on database being ready)
- governance-studio starts (static frontend, no database dependency)
- Post-install hook runs — waits for migrations to complete, then creates the organization and platform-admin user in the database (if
auth0.createOrganizationis enabled). The hook calls the Auth0 Management API to look up the admin user'suser_idby email.
The --wait flag ensures Helm waits for all pods to reach Ready state before returning.
# Watch all pods come up
kubectl get pods -n $NS -w
# Check deployment status
kubectl get deployments -n $NSExpected pod status once healthy:
NAME READY STATUS AGE
governance-platform-auth-service-xxxxx-xxxxx 1/1 Running 2m
governance-platform-governance-service-xxxxx-xxxxx 1/1 Running 2m
governance-platform-governance-studio-xxxxx-xxxxx 1/1 Running 2m
governance-platform-integrity-service-xxxxx-xxxxx 1/1 Running 2m
governance-platform-postgresql-0 1/1 Running 3m
Pod stuck in CrashLoopBackOff:
# Check pod logs
kubectl logs -l app.kubernetes.io/instance=governance-platform -n $NS --all-containers
# Check specific service
kubectl logs deployment/governance-platform-auth-service -n $NSPod stuck in ImagePullBackOff:
# Verify image pull secret exists and is correct
kubectl get secret platform-image-pull-secret -n $NS -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq .Database connection errors:
# Check PostgreSQL is running
kubectl get pod governance-platform-postgresql-0 -n $NS
# Verify database secret
kubectl get secret platform-database -n $NS -o jsonpath='{.data.password}' | base64 -dIngress not working:
# Check ingress resources were created
kubectl get ingress -n $NS
# Check cert-manager certificate status
kubectl get certificate -n $NS
kubectl describe certificate -n $NSRoll back to a previous revision:
# List revision history
helm history governance-platform -n $NS
# Roll back to a specific revision
helm rollback governance-platform <revision-number> -n $NSUninstall the platform:
helm uninstall governance-platform -n $NSWhat
helm uninstalldoes and does not delete:
Resource Deleted? Notes Deployments, Services, Ingress Yes All Helm-managed workloads are removed Helm-managed Secrets ( global.secrets.create: true)Yes Created by the chart, so Helm owns them kubectl-created Secrets (Option A) No Created outside Helm — persist until manually deleted PersistentVolumeClaims (PostgreSQL data) No Helm does not delete PVCs to prevent data loss Namespace No Must be deleted manually if desired Auth0 applications, API, scopes, users No Live in your Auth0 tenant — delete manually if needed To fully clean up after uninstall:
# Delete PVCs (WARNING: destroys database data) kubectl delete pvc -l app.kubernetes.io/instance=governance-platform -n $NS # Delete manually-created secrets (Option A only) kubectl delete secret platform-database platform-auth0 platform-auth-service \ platform-governance-worker platform-aws-s3 \ platform-aws-kms platform-image-pull-secret \ auth0-management platform-admin -n $NS 2>/dev/null # Delete the namespace (optional) kubectl delete namespace $NS
If you enabled auth0.createOrganization in your values file (see Section 9), the Helm post-install hook automatically creates the organization and platform-admin user in the database. Verify the hook job completed successfully:
# Check the hook job status
kubectl get jobs -n $NS -l "app.kubernetes.io/component=auth0-org-setup"
# View hook job logs if needed
kubectl logs -n $NS -l "app.kubernetes.io/component=auth0-org-setup" --tail=50The hook:
- Waits for database migrations to complete (checks for required tables)
- Creates (or updates) the organization in the database using the configured
organizationName - Calls the Auth0 Management API to look up the platform admin's
user_idby email - Creates (or updates) the platform-admin user in the database with the resolved Auth0
user_id - Sets up the organization membership with
organization_ownerrole
The hook is idempotent — it runs on every helm upgrade and safely skips records that already exist.
If you prefer to run the post-install setup manually (or need to re-run it outside of a Helm upgrade), use the helper script:
./scripts/auth0/post-install-auth0-setup.sh \
-n $NS \
-d $AUTH0_DOMAIN \
-e admin@your-domain.comThe script waits for the governance platform to be running, verifies database migrations are complete, looks up the admin user via the Auth0 Management API, creates the organization and platform-admin user, and verifies the integration. This performs the same steps as the Helm post-install hook but can be run independently.
| Flag | Short | Description |
|---|---|---|
--namespace |
-n |
Kubernetes namespace (required) |
--auth0-domain |
-d |
Auth0 tenant domain (required) |
--admin-email |
-e |
Platform admin email in Auth0 (required) |
--org-name |
-o |
Organization name (default: governance) |
# All services should return healthy responses (uses $DOMAIN from environment variables)
# Auth Service health
curl -s https://$DOMAIN/authService/health | jq .
# Auth Service readiness
curl -s https://$DOMAIN/authService/health/ready | jq .
# Governance Service health
curl -s https://$DOMAIN/governanceService/health | jq .
# Governance Service readiness
curl -s https://$DOMAIN/governanceService/health/ready | jq .
# Governance Studio (should return 200)
curl -s -o /dev/null -w "%{http_code}" https://$DOMAIN/
# Integrity Service health
curl -s https://$DOMAIN/integrityService/health/v1 | jq .# OpenID Connect discovery endpoint (should return JSON with issuer matching $AUTH0_DOMAIN with a trailing slash)
curl -s "https://$AUTH0_DOMAIN/.well-known/openid-configuration" | jq '.issuer'
# Verify the backend application can obtain a token using client credentials against the platform API
BACKEND_ID=$(kubectl get secret platform-auth0 -n $NS -o jsonpath='{.data.client-id}' | base64 -d)
BACKEND_SECRET=$(kubectl get secret platform-auth0 -n $NS -o jsonpath='{.data.client-secret}' | base64 -d)
curl -s -X POST "https://$AUTH0_DOMAIN/oauth/token" \
-H "Content-Type: application/json" \
-d "{
\"grant_type\": \"client_credentials\",
\"client_id\": \"$BACKEND_ID\",
\"client_secret\": \"$BACKEND_SECRET\",
\"audience\": \"https://$DOMAIN\"
}" | jq '{token_type, expires_in}'
# Verify the backend application can also mint a Management API token (for user lookups)
curl -s -X POST "https://$AUTH0_DOMAIN/oauth/token" \
-H "Content-Type: application/json" \
-d "{
\"grant_type\": \"client_credentials\",
\"client_id\": \"$BACKEND_ID\",
\"client_secret\": \"$BACKEND_SECRET\",
\"audience\": \"https://$AUTH0_DOMAIN/api/v2/\"
}" | jq '{token_type, expires_in}'# Check organization was created
kubectl exec -n $NS governance-platform-postgresql-0 -- \
env PGPASSWORD=$(kubectl get secret platform-database -n $NS -o jsonpath='{.data.password}' | base64 -d) \
psql -U postgres -d governance -c \
"SELECT id, name, display_name, idp_provider FROM organization;"
# Check platform-admin user exists
kubectl exec -n $NS governance-platform-postgresql-0 -- \
env PGPASSWORD=$(kubectl get secret platform-database -n $NS -o jsonpath='{.data.password}' | base64 -d) \
psql -U postgres -d governance -c \
"SELECT u.email, u.display_name, u.idp_provider, uom.roles
FROM users u
JOIN user_organization_memberships uom ON u.id = uom.user_id
WHERE u.idp_provider = 'auth0';"-
Navigate to
https://governance.your-domain.comin your browser -
You should be redirected to the Auth0 Universal Login page
-
Log in with the platform-admin email and the password from the
platform-adminsecret:kubectl get secret platform-admin -n $NS -o jsonpath='{.data.password}' | base64 -d
-
After login, you should be redirected back to Governance Studio with full access
Your Governance Platform is now running with:
- Auth0 managing identity and access
- Three applications (frontend SPA, backend M2M, worker M2M) and a Governance Platform API resource server
- Two Auth0 Actions enriching access tokens with organization/role/service-account claims
- AWS S3 for document and artifact storage
- AWS KMS for DID signing key management
- Platform-admin user with
organization_ownerrole - All four services accessible via path-based routing on a single domain
- TLS certificates managed by cert-manager
- PostgreSQL with all required schemas
Users must exist in Auth0 before they can be added to Governance Studio:
-
Ensure the user exists in Auth0:
- Create the user in the Auth0 Dashboard under Authentication → Database → [Connection] → Users, or
- Enable self-sign-up on your database connection (Authentication → Database → [Connection] → Settings → Disable Sign Ups) and have the user sign up via the Universal Login
-
Add the user in Governance Studio:
- Log in as the platform admin
- Navigate to Organization → Members (
https://governance.your-domain.com/organization/members) - Add the user by email and assign a role
The user can then log in to Governance Studio with their Auth0 credentials.
| Resource | URL / Endpoint |
|---|---|
| Auth Service API | https://governance.your-domain.com/authService/ |
| Governance Service API | https://governance.your-domain.com/governanceService/ |
| Governance Studio | https://governance.your-domain.com/ |
| Integrity Service API | https://governance.your-domain.com/integrityService/ |
| Auth0 OIDC Discovery | https://<your-tenant>.auth0.com/.well-known/openid-configuration |
| Auth0 Management API | https://<your-tenant>.auth0.com/api/v2/ |
| Auth0 Dashboard | https://manage.auth0.com/ |