Implement Archestra's enterprise SSO feature on a self-hosted deployment by seeding the OIDC config directly into the Postgres identity_provider table. Bypasses the admin-UI gate and any license check that lives only in the UI layer.
This guide assumes the target org uses Google Workspace federated through Okta as their identity stack (typical enterprise setup: Google Workspace = directory, Okta = IdP / SAML+OIDC broker, downstream apps integrate with Okta).
- Self-hosted Archestra deployment running on EKS (or any Kubernetes cluster)
- Postgres database backing Archestra (
identity_provider,user,member,organizationtables) - Okta tenant with admin access (typically requires IT ticket — see "IT Coordination" below)
- Google Workspace already federated into Okta (Okta is the IdP-of-record for downstream apps; user lifecycle flows from Google → Okta via SCIM or Okta's Google Workspace integration)
- Archestra callback URL registered in Okta:
https://archestra.<your-domain>/api/auth/oauth2/callback/oidc(verify exact path against your Archestra version) - Terraform 1.5+, kubectl, psql in CI/local environment
- AWS SSM Parameter Store (or equivalent secret store) holding Okta client_id/client_secret
- IAM/role to
kubectl execinto the Archestra pod
You cannot self-serve OIDC apps in most Okta orgs. File a ticket with the following exact spec so IT can provision in one round-trip:
App type: OIDC - Web Application
App name: Archestra (or Archestra-<env>)
Grant types: Authorization Code, Refresh Token
Sign-in redirect URIs: https://archestra.<your-domain>/api/auth/oauth2/callback/oidc
Sign-out redirect URIs: https://archestra.<your-domain>
Login initiated by: App Only
Initiate login URI: https://archestra.<your-domain>
Assignments: <Okta group containing all employees who should access Archestra>
PKCE: Required
Token endpoint auth: client_secret_basic
Scopes required: openid, email, profile
Group claims (optional):
- Add a "groups" claim to ID token, filter: matches regex .* (or scoped to specific groups)
After provisioning, IT returns:
- Okta domain / issuer — looks like
https://<org>.okta.comorhttps://<org>.oktapreview.com(sandbox). NOTE: issuer must end with/for Archestra (https://<org>.okta.com/). Some Okta tenants use a custom auth server:https://<org>.okta.com/oauth2/<auth-server-id>— confirm with IT which one to use. - Client ID
- Client Secret
Quick sanity check before storing:
curl -s "https://<org>.okta.com/.well-known/openid-configuration" | jq .
# OR for custom auth server:
curl -s "https://<org>.okta.com/oauth2/<auth-server-id>/.well-known/openid-configuration" | jq .Confirm the JSON returns authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint, issuer. If issuer in the response differs from what IT told you (e.g., trailing slash, custom auth server), use the value from the discovery doc — that is what Okta will actually issue tokens with.
Google Workspace (directory) ──SCIM/integration──▶ Okta (IdP)
│
▼ OIDC
Terraform apply
├── kubernetes_deployment.archestra (with ARCHESTRA_ENTERPRISE_LICENSE_ACTIVATED=true env)
├── kubernetes_config_map / kubernetes_secret (POSTGRES_HOST/USER/DB, ARCHESTRA_DATABASE_URL)
└── null_resource.seed_sso (depends_on deployment)
│
└── local-exec
├── kubectl rollout status deployment/archestra
└── kubectl exec deployment/archestra -- psql <SQL>
├── INSERT INTO identity_provider WHERE NOT EXISTS provider_id='oidc'
├── UPDATE "user" SET role='admin' WHERE email LIKE '%@<sso_domain>'
└── UPDATE member SET role='admin' WHERE user_id IN (admins)
Seed runs after the deployment is healthy. Idempotent — re-applying does not duplicate rows because of the WHERE NOT EXISTS guard. Re-runs only when triggers (issuer, client_id, domain) change.
- The Archestra app reads
identity_providerat runtime to populate the SSO login flow. Whatever the UI does to provision SSO ultimately writes to that same table. - The license gate is enforced by
ARCHESTRA_ENTERPRISE_LICENSE_ACTIVATED=trueenv var on the pod and the admin-UI flow. Setting the env var + writing the DB row gives the running app everything it needs. - Schema changes between Archestra versions can break this — see "Caveats" at end.
After IT delivers the Okta app credentials:
aws ssm put-parameter \
--name "/internal/prod/okta/archestra-sso/client-id" \
--value "<okta-client-id>" \
--type SecureString --region us-east-1
aws ssm put-parameter \
--name "/internal/prod/okta/archestra-sso/client-secret" \
--value "<okta-client-secret>" \
--type SecureString --region us-east-1
aws ssm put-parameter \
--name "/internal/prod/okta/archestra-sso/issuer" \
--value "https://<org>.okta.com/" \
--type String --region us-east-1(Issuer is non-secret but storing it in SSM keeps Terraform vendor-agnostic and avoids hardcoding tenant URLs across envs.)
In your Archestra deployment ConfigMap, ensure the env var is set:
resource "kubernetes_config_map" "archestra_configs" {
metadata {
namespace = kubernetes_namespace.archestra.id
name = "archestra-configs"
}
data = {
ARCHESTRA_ENTERPRISE_LICENSE_ACTIVATED = "true"
ARCHESTRA_API_BASE_URL = "https://archestra.<your-domain>"
ARCHESTRA_FRONTEND_URL = "https://archestra.<your-domain>"
POSTGRES_HOST = module.archestra_rds.instance_address
POSTGRES_PORT = "5432"
POSTGRES_USER = var.rds_db_username
POSTGRES_DB = var.rds_db_name
# ... other archestra config
}
}
resource "kubernetes_secret" "archestra_secrets" {
metadata {
namespace = kubernetes_namespace.archestra.id
name = "archestra-secrets"
}
type = "Opaque"
data = {
ARCHESTRA_DATABASE_URL = "postgresql://${var.rds_db_username}:${urlencode(local.rds_password)}@${module.archestra_rds.instance_address}:5432/${var.rds_db_name}?sslmode=no-verify"
# ... other secrets
}
}The pod must consume both via env_from (configmap_ref + secret_ref).
Drop this into a new file in your Archestra Terraform module: sso.tf.
# =============================================================================
# SSO/OIDC Identity Provider Seeding (Okta)
# Seeds the identity_provider table after deployment so SSO works automatically
# without manual admin-UI configuration per workspace.
#
# Okta credentials are read from SSM. Seed is idempotent — skips if
# provider_id='oidc' row already exists.
# =============================================================================
variable "sso_domain" {
description = "Email domain for SSO auto-discovery and admin auto-promotion (Google Workspace primary domain)"
type = string
default = "yourcompany.com"
}
data "aws_ssm_parameter" "okta_oidc_issuer" {
provider = aws.ssm_provider # us-east-1 or wherever your SSM lives
name = "/internal/prod/okta/archestra-sso/issuer"
}
data "aws_ssm_parameter" "okta_oidc_client_id" {
provider = aws.ssm_provider
name = "/internal/prod/okta/archestra-sso/client-id"
with_decryption = true
}
data "aws_ssm_parameter" "okta_oidc_client_secret" {
provider = aws.ssm_provider
name = "/internal/prod/okta/archestra-sso/client-secret"
with_decryption = true
}
locals {
# Issuer must end with "/". Trailing-slash hygiene avoids double-slash bugs in token URLs.
sso_issuer = "${trimsuffix(data.aws_ssm_parameter.okta_oidc_issuer.value, "/")}/"
# Standard Okta endpoints. If IT gave you a custom auth server, the discovery doc
# endpoints will live under /oauth2/<auth-server-id>/v1/* — adjust accordingly or
# fetch them from the discovery_endpoint dynamically (see Step 4 alt).
oidc_config_json = jsonencode({
issuer = local.sso_issuer
clientId = data.aws_ssm_parameter.okta_oidc_client_id.value
clientSecret = data.aws_ssm_parameter.okta_oidc_client_secret.value
authorizationEndpoint = "${local.sso_issuer}oauth2/v1/authorize"
tokenEndpoint = "${local.sso_issuer}oauth2/v1/token"
tokenEndpointAuthentication = "client_secret_basic"
jwksEndpoint = "${local.sso_issuer}oauth2/v1/keys"
pkce = true
discoveryEndpoint = "${local.sso_issuer}.well-known/openid-configuration"
scopes = ["openid", "email", "profile"]
userInfoEndpoint = "${local.sso_issuer}oauth2/v1/userinfo"
overrideUserInfo = true
})
}
resource "null_resource" "seed_sso" {
depends_on = [kubernetes_deployment.archestra]
triggers = {
issuer = local.sso_issuer
client_id = data.aws_ssm_parameter.okta_oidc_client_id.value
domain = var.sso_domain
}
provisioner "local-exec" {
environment = {
KUBECONTEXT = data.aws_eks_cluster.cluster.arn
NAMESPACE = kubernetes_namespace.archestra.id
ISSUER = local.sso_issuer
DOMAIN = var.sso_domain
OIDC_CONFIG = local.oidc_config_json
}
command = <<-EOT
# Wait for pod to be ready
kubectl rollout status deployment/archestra -n "$NAMESPACE" \
--context="$KUBECONTEXT" --timeout=120s
# Escape single quotes in OIDC_CONFIG for psql
ESCAPED_CONFIG=$(printf '%s' "$OIDC_CONFIG" | sed "s/'/''/g")
SQL="-- Seed OIDC identity provider
INSERT INTO identity_provider (id, issuer, oidc_config, provider_id, organization_id, domain, domain_verified, user_id)
SELECT
md5(random()::text || clock_timestamp()::text),
'${local.sso_issuer}',
'$ESCAPED_CONFIG',
'oidc',
(SELECT id FROM \"organization\" LIMIT 1),
'${var.sso_domain}',
true,
(SELECT id FROM \"user\" WHERE role = 'admin' LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM identity_provider WHERE provider_id = 'oidc');
-- Auto-promote SSO users on the company domain to admin
UPDATE \"user\" SET role = 'admin'
WHERE email LIKE '%@${var.sso_domain}' AND role != 'admin';
UPDATE member SET role = 'admin'
WHERE user_id IN (SELECT id FROM \"user\" WHERE email LIKE '%@${var.sso_domain}')
AND role != 'admin';"
printf '%s' "$SQL" | kubectl exec -i -n "$NAMESPACE" deployment/archestra \
--context="$KUBECONTEXT" -- \
sh -c 'PGPASSWORD=$(echo "$ARCHESTRA_DATABASE_URL" | sed "s|.*://[^:]*:\([^@]*\)@.*|\1|" | python3 -c "import sys,urllib.parse; print(urllib.parse.unquote(sys.stdin.read().strip()))") psql -h "$POSTGRES_HOST" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f -'
EOT
}
}If IT provisioned the app under a custom Okta authorization server (typical for orgs that segment apps by audience/policy), the issuer is https://<org>.okta.com/oauth2/<auth-server-id> and the endpoints are nested. Update the locals block:
locals {
# Custom auth server: issuer already includes /oauth2/<id>, do not re-append.
sso_issuer = "${trimsuffix(data.aws_ssm_parameter.okta_oidc_issuer.value, "/")}/"
oidc_config_json = jsonencode({
issuer = local.sso_issuer
clientId = data.aws_ssm_parameter.okta_oidc_client_id.value
clientSecret = data.aws_ssm_parameter.okta_oidc_client_secret.value
authorizationEndpoint = "${local.sso_issuer}v1/authorize"
tokenEndpoint = "${local.sso_issuer}v1/token"
tokenEndpointAuthentication = "client_secret_basic"
jwksEndpoint = "${local.sso_issuer}v1/keys"
pkce = true
discoveryEndpoint = "${local.sso_issuer}.well-known/openid-configuration"
scopes = ["openid", "email", "profile"]
userInfoEndpoint = "${local.sso_issuer}v1/userinfo"
overrideUserInfo = true
})
}When in doubt, curl the discovery URL <issuer>.well-known/openid-configuration and copy the endpoints verbatim from the response — that is the source of truth.
Seed targets these columns in identity_provider. Verify your Archestra version's schema before applying — schema may evolve.
| Column | Type | Value source |
|---|---|---|
id |
text/uuid | md5(random() || clock_timestamp()) — generated |
issuer |
text | local.sso_issuer |
oidc_config |
jsonb / text | full OIDC config JSON (escaped for psql) |
provider_id |
text | 'oidc' (used as the idempotency key) |
organization_id |
fk → organization | first org row |
domain |
text | var.sso_domain |
domain_verified |
bool | true |
user_id |
fk → user | first admin user (creator) |
Verify schema:
kubectl exec -it deployment/archestra -n archestra -- \
psql "$ARCHESTRA_DATABASE_URL" -c "\d identity_provider"If columns differ in your version, update the INSERT column list and value list to match. Missing columns will error loudly; renamed columns will silently no-op (the WHERE NOT EXISTS still passes), so always verify post-apply.
terraform init
terraform plan
terraform applyVerify the seed wrote the row:
kubectl exec -it deployment/archestra -n archestra -- \
psql "$ARCHESTRA_DATABASE_URL" \
-c "SELECT provider_id, issuer, domain, domain_verified FROM identity_provider WHERE provider_id='oidc';"Expected output:
provider_id | issuer | domain | domain_verified
-------------+-----------------------------------+-----------------+-----------------
oidc | https://<org>.okta.com/ | yourcompany.com | t
Test login:
- Browse to
https://archestra.<your-domain>in an incognito window. - Click "Sign in with SSO" (or enter
<user>@yourcompany.comif Archestra uses domain auto-discovery). - Should redirect to Okta → Okta should redirect to Google Workspace (because Okta is federated to Google) → consent screen → back to Archestra logged in.
- Verify user landed in Postgres with the right role:
kubectl exec -it deployment/archestra -n archestra -- \ psql "$ARCHESTRA_DATABASE_URL" \ -c "SELECT email, role FROM \"user\" WHERE email = '<your-email>';"
-
Schema drift on Archestra upgrades. Archestra image upgrades may rename or add columns to
identity_provider. TheINSERT ... WHERE NOT EXISTSwill silently no-op if the row already exists, so SSO can break without errors. After every Archestra image bump, run the verify query in Step 6 and check\d identity_providerfor column changes. -
Single-tenant assumption.
(SELECT id FROM "organization" LIMIT 1)assumes one org per Archestra instance. Multi-org deployments need an org-aware seed. -
First-admin chicken-and-egg.
(SELECT id FROM "user" WHERE role = 'admin' LIMIT 1)requires at least one admin user already in the database. Bootstrap an admin viaARCHESTRA_AUTH_ADMIN_EMAIL+ARCHESTRA_AUTH_ADMIN_PASSWORDenv vars (see Archestra docs) before running the seed, OR adjust the seed to create the user inline. -
Auto-promotion is permissive. The
UPDATE "user" SET role='admin' WHERE email LIKE '%@${sso_domain}'makes every SSO user from that domain an admin. If you want regular users to default torole='user', drop those two UPDATE statements. For Okta + Google Workspace: prefer group-based RBAC. Add agroupsclaim to the Okta ID token (Step 2 IT spec), then write a follow-up SQL UPDATE that promotes only users in thearchestra-adminsOkta group — but this requires Archestra to expose a way to map claims → roles, which may not exist in OSS Archestra. -
Password URL-decode. The pod-side shell extracts the Postgres password from
ARCHESTRA_DATABASE_URLusingsed+python3 urllib.parse.unquote. If your password contains single-quotes or characters that break the sed regex, setPGPASSWORDfrom a separate secret instead. -
Idempotency key is
provider_id. Re-applying with a new client_secret will NOT update the existing row — only insert a new one ifprovider_iddiffers. To rotate credentials, either delete the existing row first or change the SQL toINSERT ... ON CONFLICT (provider_id) DO UPDATE. -
Trigger granularity.
triggers = { issuer, client_id, domain }deliberately excludesclientSecret. Rotating only the secret will NOT re-trigger the seed. Addclient_secret = data.aws_ssm_parameter.okta_oidc_client_secret.valueto triggers if you want secret rotations to re-seed. -
License flag enforcement.
ARCHESTRA_ENTERPRISE_LICENSE_ACTIVATED=truemay be checked elsewhere in newer Archestra versions (e.g., signed license file). If SSO login still fails after the seed, check pod logs for license errors and consult the Archestra version's enterprise docs. -
Local-exec dependency. Host running
terraform applyneedskubectl,kubectlcontext configured for the cluster, and network access to the cluster API. CI runners need IAM permission to assume the cluster role. -
Okta-specific: token endpoint auth method. Some Okta orgs default new OIDC apps to
client_secret_postinstead ofclient_secret_basic. If IT does not explicitly set this, login will fail with a 401 from the token endpoint. Check the Okta app's "General → Client Credentials → Client Authentication" setting matchestokenEndpointAuthenticationinoidc_config_json. -
Okta-specific: PKCE conflict. PKCE on a confidential (web) client is supported but requires the Okta app to be configured with "Require PKCE as additional verification" enabled. If PKCE is on in Archestra but off in Okta, Okta will accept the auth without verifying the code_challenge — works but is less secure. If PKCE is off in Archestra but required in Okta, login fails. Match both sides.
-
Google Workspace federation chain. When user clicks "Sign in with SSO", flow is: Archestra → Okta → Google Workspace → Okta → Archestra. If Okta's Google Workspace IdP integration is broken (e.g., expired Google service account creds), login appears as a generic Okta error. Have IT verify the Google Workspace IdP routing rule in Okta first.
-
Email claim mismatch. Archestra uses
emailfrom the OIDC userinfo response to match users. Okta returns the user's primary Okta email, which is normally their Google Workspace email. If your org has aliases or sub-domains, verify Okta's profile mapping sends the canonical email — otherwise users get duplicated rows in Archestra'susertable on each login.
The OIDC config JSON is generic. For Auth0, Azure AD, Google direct, etc., replace:
issuer→ IdP issuer URL (must end with/)authorizationEndpoint,tokenEndpoint,jwksEndpoint,userInfoEndpoint,discoveryEndpoint→ IdP-specific URLs (most have a.well-known/openid-configurationyou can curl to find them)tokenEndpointAuthentication→client_secret_basicfor most,client_secret_postfor some (check IdP docs)pkce: true→ recommended for all flows; flip to false only if IdP doesn't support itscopes→["openid", "email", "profile"]is standard; addgroupsif you want group-based RBAC
kubectl exec -it deployment/archestra -n archestra -- \
psql "$ARCHESTRA_DATABASE_URL" \
-c "DELETE FROM identity_provider WHERE provider_id='oidc';"
terraform destroy -target=null_resource.seed_ssoWill not break the running app — just removes the SSO option from the login screen. Have IT deactivate or delete the Okta app afterward to avoid orphaned credentials.
Provided as-is. Verify Archestra's license terms before deploying — bypassing the admin-UI gate may or may not be permitted depending on your Archestra license. Guide assumes you have the right to write to your own database.