diff --git a/src/components/NavigationDocs.jsx b/src/components/NavigationDocs.jsx index ca7f055f..9fe51754 100644 --- a/src/components/NavigationDocs.jsx +++ b/src/components/NavigationDocs.jsx @@ -644,6 +644,10 @@ export const docsNavigation = [ title: 'Enable Reverse Proxy', href: '/selfhosted/migration/enable-reverse-proxy', }, + { + title: 'External IdP to Embedded IdP', + href: '/selfhosted/migration/external-to-embedded-idp', + }, ], }, ], diff --git a/src/pages/selfhosted/migration/combined-container.mdx b/src/pages/selfhosted/migration/combined-container.mdx index 6295627c..e5d21cd3 100644 --- a/src/pages/selfhosted/migration/combined-container.mdx +++ b/src/pages/selfhosted/migration/combined-container.mdx @@ -14,7 +14,7 @@ This guide walks you through migrating a pre-v0.65.0 NetBird self-hosted deploym -The migration script **exits with an error** if it detects an external identity provider (Auth0, Keycloak, Okta, Zitadel, Google Workspace, Microsoft Entra ID, etc.). If you are using an external IdP, do not run this script. Instead, follow the [self-hosting quickstart](/selfhosted/selfhosted-quickstart) for a fresh installation. +The migration script **exits with an error** if it detects an external identity provider (Auth0, Keycloak, Okta, Zitadel, Google Workspace, Microsoft Entra ID, etc.). If you are using an external IdP, first follow the [External IdP to Embedded IdP migration guide](/selfhosted/migration/external-to-embedded-idp) to switch to the embedded Dex IdP, then return here to complete the combined container migration. ## Overview of changes diff --git a/src/pages/selfhosted/migration/external-to-embedded-idp.mdx b/src/pages/selfhosted/migration/external-to-embedded-idp.mdx new file mode 100644 index 00000000..f8e223c3 --- /dev/null +++ b/src/pages/selfhosted/migration/external-to-embedded-idp.mdx @@ -0,0 +1,425 @@ +import {Note, Warning, Success} from "@/components/mdx" + +export const description = 'Migrate a self-hosted NetBird deployment from an external identity provider to the embedded IdP introduced in v0.62.0.' + +# Migration Guide: External IdP to Embedded IdP + +This guide walks through migrating a self-hosted NetBird deployment from an external identity provider to the embedded IdP introduced in v0.62.0. + + +**Who is this guide for?** This migration guide is for users who: +- Have an existing self-hosted deployment using an **external IdP** +- Want to move to the **embedded Dex-based IdP** for a simpler, self-contained authentication setup + + +## Overview + + + +Migrating to the embedded IdP also unlocks the [Combined Container Setup migration](/selfhosted/migration/combined-container), which consolidates management, signal, relay, and STUN into a single container. If you plan to simplify your deployment, complete this IdP migration first, then follow the combined container guide. + + + +The migration tool does two things: +1. **Re-encodes user IDs** in the database to include the external connector ID, so Dex can route returning users to the correct external provider. +2. **Generates a new `management.json`** that replaces `IdpManagerConfig` with `EmbeddedIdP` and updates OAuth2 endpoints to the embedded Dex issuer. + +After migration, existing users keep logging in through the same external provider — Dex acts as a broker in front of it. No passwords or credentials change. + +--- + +## Before You Begin + + +This guide assumes you're using Docker and everything is run in the same bash session, if you're using a different setup, adjust the commands accordingly. + + +### Prerequisites + +| Requirement | Details | +|-------------|---------| +| NetBird version | v0.67.2 or later | +| Config access | You can read and write `management.json` | +| Server downtime | The management server **must be stopped** during migration | +| Backups | Back up your database and config before starting | + + +### Flags and Environment variables + +| Flag | Environment variable | Description | Expected format | +|------|----------------------|-------------| --------------- | +| `--domain` | `NETBIRD_DOMAIN` | Domain for both dashboard and API | example.com | +| `--dashboard-url` | `NETBIRD_DASHBOARD_URL` | Dashboard domain (will override --domain) | example.com or example.com:33073 or https://example.com | +| `--api-url` | `NETBIRD_API_URL` | API domain (will override --domain) | example.com or example.com:33073 or https://example.com | +| `--config` | `NETBIRD_CONFIG_PATH` | Path to management.json (required) | /path/to/management.json | +| `--datadir` | `NETBIRD_DATA_DIR` | Override data directory from config (store.db path will be derived from this) | /path/to/datadir | +| `--idp-seed-info` | `NETBIRD_IDP_SEED_INFO` | Base64-encoded connector JSON | base64-encoded JSON string | +| `--dry-run` | `NETBIRD_DRY_RUN` | Preview changes without writing | true or false | +| `--force` | `NETBIRD_FORCE` | Skip confirmation prompt | true or false | +| `--skip-config` | `NETBIRD_SKIP_CONFIG` | Skip config generation (DB migration only) | true or false | +| `--skip-populate-user-info` | `NETBIRD_SKIP_POPULATE_USER_INFO` | Skip populating user info (user id migration only) | true or false | +| `--log-level` | `NETBIRD_LOG_LEVEL` | Log level: debug, info, warn, error (default "info") | debug, info, warn, error | + + + +When to use --domain vs --dashboard-domain vs --api-domain: +- If you have a single domain for both dashboard and API, use --domain +- If you don't have a reverse proxy in front of dashboard and API, make sure you use the domain + port combination for each. + For example, `--dashboard-domain demo.netbird.io` and `--api-domain demo.netbird.io:33073` + +--- + +## Step 1: Prepare your Management Server + +Make sure your management server is on the latest version, otherwise management will not be able to properly parse the new `management.json` file generated by this migration tool. + +```bash +docker compose pull +docker compose up -d management +``` + + +Before starting the migration, it's also a good idea to log out of the dashboard, as you might get a "stale" token from the old IdP which can cause 401 errors. + +--- + +## Step 2: Get the Migration Tool + +**Option A — Download a pre-built binary:** + +```bash +# Replace VERSION with the release tag, and adjust the architecture as needed +curl -L -o netbird-idp-migrate.tar.gz \ + https://github.com/netbirdio/netbird/releases/download/v0.67.2/netbird-idp-migrate_0.67.2_linux_amd64.tar.gz +tar xzf netbird-idp-migrate.tar.gz +chmod +x netbird-idp-migrate +``` + +Available architectures: `linux_amd64`, `linux_arm64`, `linux_arm`. + +**Option B — Build from source** (requires Go 1.25+ and a C compiler for CGO/SQLite): + +```bash +go build -o netbird-idp-migrate ./tools/idp-migrate/ +``` + +Copy the binary to the management server host if you built it elsewhere. + +--- + +## Step 3: Prepare Your Provider + +The new embedded IdP made the generation of the OIDC connector config easier, that's why we recommend generating a new application for your provider. + +When following the guides to generate the application, make sure you store the client ID and client Secret somewhere safe. You'll need them later. + +To spare details in this guide, you can use the following guides to create the OIDC connector configuration for your provider: +- Auth0 +- Azure AD +- Keycloak +- Okta +- Authentik +- PocketID +- Google Workspace +- JumpCloud +- Zitadel + + +### Creating the IdP Seed info + +With the client id and client secret from the previous step, you can create the `idp-seed-info` for the tool, which will be used to generate the OIDC connector config. + +1. We'll create a new file "connector.json" with the following contents, make sure you remember where you save it: +```json +{ + "type": "oidc", + "name": "My Provider", + "id": "my-provider", + "config": { + "issuer": "https://idp.example.com", + "clientID": "my-client-id", + "clientSecret": "my-client-secret" + } +} +``` + + +Using Zitadel as an example, the JSON should have the following values: +- "issuer": is the root domain of your zitadel instance, make sure you don't have any trailing slashes (e.g. https://zitadel.example.com) +- "clientID" and "clientSecret": are the values you copy when creating the OAuth app + + + +2. Encode and store it in the `NETBIRD_IDP_SEED_INFO` environment variable: + +```bash +export NETBIRD_IDP_SEED_INFO=$(base64 < connector.json | tr -d '\n') +``` + +--- + +## Step 4: Stop the Management Server + +```bash +docker compose stop management +``` + +--- + +## Step 5: Back Up Your Data + +The tool creates `management.json.bak` automatically, but always make your own backups. + + +Do not skip this step. The migration modifies user IDs in the database. A manual backup is your only recovery path if something goes wrong. + + +**Docker Compose (SQLite in a named volume):** + +```bash +# Identify the volume name +VOLUME_NAME=$(docker volume ls --format '{{ .Name }}' | grep -Ei 'management|mgmt') +echo "Volume: $VOLUME_NAME" + +# Get the host path +export NETBIRD_DATA_DIR=$(docker volume inspect "$VOLUME_NAME" --format '{{ .Mountpoint }}') +echo "Path: $NETBIRD_DATA_DIR" + +# (SQLite only) Verify store.db exists, then back up +sudo ls "$NETBIRD_DATA_DIR/store.db" +sudo cp "$NETBIRD_DATA_DIR/store.db" "$NETBIRD_DATA_DIR/store.db.bak" + +# Verify management.json exists, the path will vary based on your setup, then back up +export NETBIRD_CONFIG_PATH="/management.json" +cat "$NETBIRD_CONFIG_PATH" + +cp "$NETBIRD_CONFIG_PATH" "$NETBIRD_CONFIG_PATH.bak" +``` +--- + +## Step 6: Run the Migration + +### Validate required env vars / flags + +```bash +echo $NETBIRD_CONFIG_PATH +echo $NETBIRD_DATA_DIR +echo $NETBIRD_IDP_SEED_INFO | base64 -d +``` + +You should expect to see an output similar to this: +``` +/etc/netbird/management.json +/var/lib/docker/volumes/management_data/_data +{ + "type": "oidc", + "name": "my-provider", + "id": "my-provider", + "config": { + "issuer": "https://idp.example.com", + "clientID": "my-client-id", + "clientSecret": "my-client-secret", + } +} +``` + +### (PostgreSQL only) Verify that the database env var is set + +The postgres store engine requires the postgres container to expose the port over the host +so that the migration tool can connect to it. You can set the env var in your shell: + +```bash +# This should match the same env var content that is passed to the management server +export NB_STORE_ENGINE_POSTGRES_DSN="host=localhost port=5432 user=postgres password=postgres dbname=netbird sslmode=disable" +``` + + + +If you don't see the expected output, please make sure you followed the steps in this guide, or that you use +the correct flags while running the tool. + +### Dry run (always do this first) + +Assuming that you've followed the steps in this guide, you should be able to run the tool with the following command: + +```bash +./netbird-idp-migrate --domain mgmt.example.com --dry-run +``` + + +If the env vars are not set, you can use the flags listed at the [Flags and Environment variables](#flags-and-environment-variables) section. + + + +After running the dry run command you should see output like: + +``` +INFO resolved connector: type=oidc, id=auth0, name=auth0 +INFO found 12 total users: 12 pending migration, 0 already migrated +INFO [DRY RUN] would migrate user abc123 -> CgZhYmMxMjMSB3ppdGFkZWw (account: acct-1) +... +INFO [DRY RUN] migration summary: 12 users would be migrated, 0 already migrated +INFO derived domain for embedded IdP: mgmt.example.com +INFO [DRY RUN] new management.json would be: +{ ... } +``` + +Verify before proceeding: + +- Connector type and ID match your provider. +- User count matches what you expect. +- Generated config has the correct domain and endpoints. + +### Execute the migration + +Run the same command without `--dry-run`: + +```bash +./netbird-idp-migrate --domain mgmt.example.com +``` + +The tool will show a summary and prompt for confirmation: + +``` +About to migrate 12 users. This cannot be easily undone. Continue? [y/N] +``` + +Type `y` and press Enter. + +### Review the new config + +Your `management.json` should be significantly smaller now, and the OIDC connector config should be present in `StaticConnectors`. +Make sure you verify the following: +- `IdpManagerConfig` is **removed**. +- `EmbeddedIdP` is present with `"Enabled": true` and your connector in `StaticConnectors`. + +--- + +## Step 7: Post-Migration Configuration + +### Update your reverse proxy + +The embedded Dex IdP is served under `/oauth2/`. Your reverse proxy must route this path to the management server. + +**Caddy** — add to your `Caddyfile` inside the site block for your management domain: + +``` +reverse_proxy /oauth2/* management:80 +``` + +Place it alongside existing `/api/*` and `/management.ManagementService/*` routes, then reload: + +```bash +docker compose restart caddy +``` + +**Nginx:** + +```nginx +location /oauth2/ { + proxy_pass http://management:80; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} +``` + +Reload nginx after adding the route. + +**Traefik:** Add a route matching the `/oauth2/` path prefix, forwarding to the management service. + +**Verify the route works:** + +```bash +curl -s https:///oauth2/.well-known/openid-configuration | head -5 +``` + +Expected: a JSON response with `"issuer": "https:///oauth2"`. + +### Update dashboard environment + +Your dashboard should also be updated to use the embedded IdP configuration, for this, make sure you update the +`dashboard.env` or environment variables (check docker compose file for reference). + +Your env vars should have the following values: +```bash +AUTH_AUDIENCE=netbird-dashboard +AUTH_CLIENT_ID=netbird-dashboard +AUTH_AUTHORITY=https:///oauth2 +AUTH_SUPPORTED_SCOPES=openid profile email groups +AUTH_REDIRECT_URI=/nb-auth +AUTH_SILENT_REDIRECT_URI=/nb-silent-auth +``` + +If you're using docker, make sure to recreate the dashboard container to apply the new environment variables with `docker compose up -d dashboard` + +--- + +## Step 8: Start and Verify + +### Start the management server + +```bash +docker compose up -d management +``` + +### Verify everything works + +1. **OIDC discovery:** Open `https:///oauth2/.well-known/openid-configuration` — it should return valid JSON. +2. **Dashboard login:** Log in to the dashboard — you should be redirected through your external IdP as before. +3. **Data integrity:** Check that peers are visible and policies are intact. + + +Use an incognito/private browser window or clear cookies for your first login. Stale tokens from the old IdP will fail validation. + + +--- + +## Troubleshooting + +### "store does not support migration operations" + +The store implementation is missing the required `ListUsers`/`UpdateUserID` methods. Upgrade to v0.67.2+ binaries. + +### "could not open activity store" + +This is a **warning**, not an error. If `events.db` doesn't exist (e.g., fresh install), activity event migration is skipped. User ID migration in the main database still proceeds normally. + +### "no connector configuration found" + +No IdP configuration was detected. Provide it explicitly with `--idp-seed-info`, or set the `IDP_SEED_INFO` env var. + +### "Errors.App.NotFound" from Zitadel after migration + +The dashboard is still redirecting to Zitadel's `/oauth/v2/` endpoint instead of the management server's `/oauth2` endpoint. Set `AUTH_AUTHORITY=https:///oauth2` in your dashboard environment — see [Update dashboard environment](#update-dashboard-environment). + +### OIDC discovery returns 404 + +The `/oauth2/` path is not being routed to the management server. Add a reverse proxy route — see [Update your reverse proxy](#update-your-reverse-proxy). + +### "jumpcloud does not have a supported Dex connector type" + +JumpCloud has no native Dex connector. Configure a generic OIDC connector manually with `--idp-seed-info` — see [Other Providers](#other-providers-auth0-azure-ad-keycloak-okta-authentik-pocketid-google-jumpcloud-etc) in Step 3. + +### "failed to create embedded IDP service: cannot disable local authentication..." + +The embedded IdP didn't support `StaticConnectors` in this config version. Upgrade to v0.67.2+ which includes this fix. + +### Partial failure / re-running + +The migration is **idempotent**. Already-migrated users are detected and skipped. If the tool fails partway through, fix the underlying issue and re-run — it picks up where it left off. + +--- + +## Rolling Back + +If something goes wrong after migration: + +1. **Stop** the management server: `docker compose stop management` +2. **Restore the database:** + - SQLite (Docker volume): `sudo cp $NETBIRD_DATA_DIR/store.db.bak $NETBIRD_DATA_DIR/store.db` + - PostgreSQL: restore from your `pg_dump` backup +3. **Restore the config:** `cp $NETBIRD_CONFIG_PATH.bak $NETBIRD_CONFIG_PATH` +4. **Revert** any reverse proxy or dashboard env changes. +5. **Start** the management server: `docker compose up -d management`