# Add to an existing installation
Add the advanced SSO stack to a Materialize deployment you already run with the Terraform modules.

> For the complete documentation index, see [llms.txt](https://preview.materialize.com/materialize/40269/llms.txt).


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](https://github.com/MaterializeInc/materialize-terraform-self-managed),
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](https://preview.materialize.com/materialize/40269/markdown-docs/self-managed-deployments/sso/advanced/prerequisites/index.md).

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

   ```sql
   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;
   ```

1. Build a connection string for each database. URL-encode the password, for
   example with Terraform's `urlencode()`:

   ```hcl
   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`:

   ```hcl
   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](https://github.com/MaterializeInc/materialize-terraform-self-managed/tree/main/aws/examples/enterprise),
   [Azure](https://github.com/MaterializeInc/materialize-terraform-self-managed/tree/main/azure/examples/enterprise),
   [GCP](https://github.com/MaterializeInc/materialize-terraform-self-managed/tree/main/gcp/examples/enterprise)).
   To enable SAML, also set `enable_polis`, `polis_fqdn`, and `polis_dsn = local.ory_polis_dsn`;
   see [Configure identity providers](https://preview.materialize.com/materialize/40269/markdown-docs/self-managed-deployments/sso/advanced/identity-providers/index.md).

1. Apply:

   ```bash
   terraform init -upgrade
   terraform apply
   ```

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

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

   ```bash
   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:

   ```hcl
   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](https://preview.materialize.com/materialize/40269/markdown-docs/security/self-managed/sso-migration/index.md).

1. Apply:

   ```bash
   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`:

```bash
kubectl -n cert-manager get secret <name_prefix>-root-ca -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt
```


```bash
# 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](https://preview.materialize.com/materialize/40269/markdown-docs/self-managed-deployments/sso/advanced/identity-providers/index.md).
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

- [Configure identity providers](https://preview.materialize.com/materialize/40269/markdown-docs/self-managed-deployments/sso/advanced/identity-providers/index.md)
- [Enable role mapping](https://preview.materialize.com/materialize/40269/markdown-docs/self-managed-deployments/sso/advanced/role-mapping/index.md)
