Add to an existing installation

View as Markdown

If Materialize is already running, you can add the advanced SSO stack without redeploying it. This guide assumes you manage Materialize with the Materialize Terraform modules, including the materialize-instance module.

Materialize is switched over in a separate apply at the end, so you can check the stack before any user signs in through it:

  1. Create the Ory databases.
  2. Add the ory-stack module and check that it’s healthy. Materialize keeps using its current authentication.
  3. Point Materialize at Hydra.

Before you begin

Complete the prerequisites.

Step 1: Create the Ory databases

Kratos and Hydra each need their own PostgreSQL database, and Polis needs one too if you plan to enable SAML. They can share a PostgreSQL server, either your existing one or a dedicated instance, as long as the Kubernetes cluster can reach it. The enterprise examples use a managed instance (Cloud SQL, Azure Database for PostgreSQL flexible server, or RDS) in the same network as the cluster.

  1. Create a user and the databases. Each component runs its own schema migrations on startup, so the user must own its databases:

    CREATE USER oryadmin WITH PASSWORD '<password>';
    CREATE DATABASE kratos OWNER oryadmin;
    CREATE DATABASE hydra OWNER oryadmin;
    -- Only if you plan to enable SAML:
    CREATE DATABASE polis OWNER oryadmin;
    
  2. Build a connection string for each database. URL-encode the password, for example with Terraform’s urlencode():

    locals {
      ory_kratos_dsn = "postgres://oryadmin:${urlencode(var.ory_db_password)}@<host>:5432/kratos?sslmode=require"
      ory_hydra_dsn  = "postgres://oryadmin:${urlencode(var.ory_db_password)}@<host>:5432/hydra?sslmode=require"
      # uselibpqcompat=true keeps sslmode=require at libpq semantics (encrypt,
      # don't verify), which Polis's driver needs against managed servers.
      ory_polis_dsn  = "postgres://oryadmin:${urlencode(var.ory_db_password)}@<host>:5432/polis?sslmode=require&uselibpqcompat=true"
    }
    

Step 2: Add the Ory stack

  1. Add the ory-stack module next to your existing modules, substituting your own hostnames and ClusterIssuer:

    module "ory" {
      source = "github.com/MaterializeInc/materialize-terraform-self-managed//kubernetes/modules/ory-stack?ref=<RELEASE_TAG>"
    
      namespace = "ory"
    
      hydra_fqdn  = "hydra.example.com"
      kratos_fqdn = "kratos.example.com"
      ui_fqdn     = "auth.example.com"
    
      kratos_dsn = local.ory_kratos_dsn
      hydra_dsn  = local.ory_hydra_dsn
    
      # Use the ory_oel_image_tag default from the enterprise example at the same release.
      oel_image_tag   = "<ORY_IMAGE_TAG>"
      license_key_jwt = var.license_key
    
      cert_issuer_ref = {
        name = "letsencrypt-prod"
        kind = "ClusterIssuer"
      }
      # true for an in-cluster self-signed issuer, false for a public ACME issuer
      cert_issuer_signs_cluster_local = false
    
      # Registers the Materialize Console as an OAuth2 client in Hydra.
      materialize_namespace    = "materialize-environment"
      materialize_console_fqdn = "console.example.com"
    }
    
    output "ory_lb_addresses" {
      value = module.ory.lb_addresses
    }
    

    The load balancer settings differ by cloud. Copy lb_annotations, and on AWS lb_load_balancer_class and lb_external_traffic_policy, from the module "ory" block in the enterprise example for your cloud (AWS, Azure, GCP). To enable SAML, also set enable_polis, polis_fqdn, and polis_dsn = local.ory_polis_dsn; see Configure identity providers.

  2. Apply:

    terraform init -upgrade
    terraform apply
    
  3. Create DNS records pointing the Hydra, Kratos, and selfservice UI hostnames at the addresses in terraform output ory_lb_addresses: an A record for an IP (Azure, GCP) or a CNAME for a hostname (AWS).

  4. Check that Hydra serves its discovery document, with an issuer that matches hydra_fqdn:

    curl -fsSL https://hydra.example.com/.well-known/openid-configuration | jq .issuer
    

Step 3: Point Materialize at Hydra

  1. In your existing materialize-instance module, switch authentication to OIDC and set the OIDC parameters from the ory-stack outputs:

    module "materialize_instance" {
      # ... your existing settings ...
    
      authenticator_kind = "Oidc"
      # Password for the mz_system admin user, kept as a fallback under OIDC.
      external_login_password_mz_system = var.external_login_password_mz_system
    
      # With network policies enabled, lets Materialize reach Hydra for its signing keys.
      ory_namespace = "ory"
    
      system_parameters = {
        oidc_issuer                  = module.ory.hydra_external_url
        oidc_audience                = jsonencode([module.ory.oauth2_client_id])
        oidc_authentication_claim    = "email"
        console_oidc_client_id       = module.ory.oauth2_client_id
        console_oidc_scopes          = "openid email"
        # Optional: grant roles from IdP groups (see Enable role mapping).
        oidc_group_role_sync_enabled = "true"
      }
    
      # Set a new UUID so environmentd restarts with the new parameters.
      force_rollout = "<NEW_UUID>"
    }
    

    Users keep their existing SQL roles as long as the email claim matches the role names. If your users currently sign in with passwords, see Migrate to SSO.

  2. Apply:

    terraform apply
    

Step 4: Verify sign-in

Smoke-test each browser-facing endpoint. These commands assume a publicly trusted issuer (cert_issuer_ref set). With the default self-signed issuer, fetch its CA first and pass --cacert ca.crt to each curl:

kubectl -n cert-manager get secret <name_prefix>-root-ca -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt
# Hydra OIDC discovery (issuer should match ory_hydra_fqdn)
curl -fsSL https://hydra.example.com/.well-known/openid-configuration | jq .issuer

# Kratos health
curl -fsSL https://kratos.example.com/health/ready

# Selfservice UI health
curl -fsSL https://auth.example.com/health/alive

# Polis health (only when enable_polis = true)
curl -fsSL https://polis.example.com/api/health

# Materialize console (expect HTTP 200)
curl -fsSL -o /dev/null -w "%{http_code}\n" https://console.example.com

Then sign in end to end, which is what proves SSO works:

  1. Open https://console.example.com. You are redirected to the selfservice UI at auth.example.com, with one button per upstream_identity_providers entry and per saml_providers entry. Each button’s text comes from that entry’s label; see Configure identity providers.
  2. Sign in through one of them. You should land back in the Console as that user.
  3. Run SELECT current_user;. It should return the user’s email.

Next steps

Back to top ↑