Skip to content

Instantly share code, notes, and snippets.

@jordotech
Last active May 26, 2026 02:20
Show Gist options
  • Select an option

  • Save jordotech/dede5ca7416fbdae981d4d2a33432a26 to your computer and use it in GitHub Desktop.

Select an option

Save jordotech/dede5ca7416fbdae981d4d2a33432a26 to your computer and use it in GitHub Desktop.
Archestra SSO/OIDC self-provisioned implementation — Terraform + null_resource + psql seed pattern

Archestra SSO/OIDC — Self-Provisioned Implementation Guide (Okta + Google Workspace)

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).

Prerequisites

  • Self-hosted Archestra deployment running on EKS (or any Kubernetes cluster)
  • Postgres database backing Archestra (identity_provider, user, member, organization tables)
  • 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 exec into the Archestra pod

IT Coordination — what to ask Okta admins for

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.com or https://<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.

Architecture

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.

Why the data-layer approach

  • The Archestra app reads identity_provider at 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=true env 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.

Step 1 — Get Okta credentials, store in SSM

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.)

Step 2 — Set the enterprise license flag

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).

Step 3 — Add the SSO seed Terraform

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
  }
}

Step 4 — Custom Okta auth server (if IT gave you one)

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.

Step 5 — Schema reference

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.

Step 6 — Apply and verify

terraform init
terraform plan
terraform apply

Verify 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:

  1. Browse to https://archestra.<your-domain> in an incognito window.
  2. Click "Sign in with SSO" (or enter <user>@yourcompany.com if Archestra uses domain auto-discovery).
  3. Should redirect to Okta → Okta should redirect to Google Workspace (because Okta is federated to Google) → consent screen → back to Archestra logged in.
  4. 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>';"

Caveats and Gotchas

  1. Schema drift on Archestra upgrades. Archestra image upgrades may rename or add columns to identity_provider. The INSERT ... WHERE NOT EXISTS will 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_provider for column changes.

  2. Single-tenant assumption. (SELECT id FROM "organization" LIMIT 1) assumes one org per Archestra instance. Multi-org deployments need an org-aware seed.

  3. 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 via ARCHESTRA_AUTH_ADMIN_EMAIL + ARCHESTRA_AUTH_ADMIN_PASSWORD env vars (see Archestra docs) before running the seed, OR adjust the seed to create the user inline.

  4. 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 to role='user', drop those two UPDATE statements. For Okta + Google Workspace: prefer group-based RBAC. Add a groups claim to the Okta ID token (Step 2 IT spec), then write a follow-up SQL UPDATE that promotes only users in the archestra-admins Okta group — but this requires Archestra to expose a way to map claims → roles, which may not exist in OSS Archestra.

  5. Password URL-decode. The pod-side shell extracts the Postgres password from ARCHESTRA_DATABASE_URL using sed + python3 urllib.parse.unquote. If your password contains single-quotes or characters that break the sed regex, set PGPASSWORD from a separate secret instead.

  6. Idempotency key is provider_id. Re-applying with a new client_secret will NOT update the existing row — only insert a new one if provider_id differs. To rotate credentials, either delete the existing row first or change the SQL to INSERT ... ON CONFLICT (provider_id) DO UPDATE.

  7. Trigger granularity. triggers = { issuer, client_id, domain } deliberately excludes clientSecret. Rotating only the secret will NOT re-trigger the seed. Add client_secret = data.aws_ssm_parameter.okta_oidc_client_secret.value to triggers if you want secret rotations to re-seed.

  8. License flag enforcement. ARCHESTRA_ENTERPRISE_LICENSE_ACTIVATED=true may 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.

  9. Local-exec dependency. Host running terraform apply needs kubectl, kubectl context configured for the cluster, and network access to the cluster API. CI runners need IAM permission to assume the cluster role.

  10. Okta-specific: token endpoint auth method. Some Okta orgs default new OIDC apps to client_secret_post instead of client_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 matches tokenEndpointAuthentication in oidc_config_json.

  11. 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.

  12. 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.

  13. Email claim mismatch. Archestra uses email from 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's user table on each login.

Adapting to non-Okta IdPs

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-configuration you can curl to find them)
  • tokenEndpointAuthenticationclient_secret_basic for most, client_secret_post for some (check IdP docs)
  • pkce: true → recommended for all flows; flip to false only if IdP doesn't support it
  • scopes["openid", "email", "profile"] is standard; add groups if you want group-based RBAC

Rollback

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_sso

Will 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.

License

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment