From 8cf44f6e2c64f90684e13ef7967b25d9bb261356 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 11 Apr 2026 18:11:21 +0200 Subject: [PATCH] Migrate config to cameleer.saas.* naming convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move all SaaS configuration properties under the cameleer.saas.* namespace with all-lowercase dot-separated names and mechanical env var mapping. Aligns with the server (cameleer.server.*) and agent (cameleer.agent.*) conventions. Changes: - Move cameleer.identity.* → cameleer.saas.identity.* - Move cameleer.provisioning.* → cameleer.saas.provisioning.* - Move cameleer.certs.* → cameleer.saas.certs.* - Rename kebab-case properties to concatenated lowercase - Update all env vars to CAMELEER_SAAS_* mechanical mapping - Update DockerTenantProvisioner to pass CAMELEER_SERVER_* env vars to provisioned server containers (matching server's new convention) - Spring JWT config now derives from SaaS properties via cross-reference - Clean up orphaned properties in application-local.yml - Update docker-compose.yml, docker-compose.dev.yml, .env.example - Update CLAUDE.md, HOWTO.md, architecture.md, user-manual.md Co-Authored-By: Claude Opus 4.6 (1M context) --- .env.example | 29 +++++------ CLAUDE.md | 51 ++++++++++++++----- HOWTO.md | 10 ++-- docker-compose.dev.yml | 12 ++--- docker-compose.yml | 12 ++--- docs/architecture.md | 32 ++++++------ docs/user-manual.md | 20 ++++---- .../saas/config/PublicConfigController.java | 4 +- .../cameleer/saas/config/SecurityConfig.java | 2 +- .../cameleer/saas/identity/LogtoConfig.java | 8 +-- .../CertificateManagerAutoConfig.java | 2 +- .../provisioning/DockerTenantProvisioner.java | 34 ++++++------- .../provisioning/ProvisioningProperties.java | 2 +- src/main/resources/application-local.yml | 10 ++-- src/main/resources/application.yml | 47 +++++++++-------- 15 files changed, 147 insertions(+), 128 deletions(-) diff --git a/.env.example b/.env.example index 53650da..762582f 100644 --- a/.env.example +++ b/.env.example @@ -9,24 +9,19 @@ POSTGRES_USER=cameleer POSTGRES_PASSWORD=change_me_in_production POSTGRES_DB=cameleer_saas -# Logto Identity Provider +# Public domain (used by Traefik, Logto, and SaaS provisioning) +PUBLIC_HOST=localhost +PUBLIC_PROTOCOL=https + +# Logto Identity Provider (infrastructure — used by logto-bootstrap init container) LOGTO_ENDPOINT=http://logto:3001 -LOGTO_PUBLIC_ENDPOINT=http://localhost:3001 -LOGTO_ISSUER_URI=http://localhost:3001/oidc -LOGTO_JWK_SET_URI=http://logto:3001/oidc/jwks LOGTO_DB_PASSWORD=change_me_in_production -LOGTO_M2M_CLIENT_ID= -LOGTO_M2M_CLIENT_SECRET= -LOGTO_SPA_CLIENT_ID= -# Ed25519 Keys (mount PEM files) -CAMELEER_JWT_PRIVATE_KEY_PATH=/etc/cameleer/keys/ed25519.key -CAMELEER_JWT_PUBLIC_KEY_PATH=/etc/cameleer/keys/ed25519.pub +# SaaS Identity (Logto M2M credentials — usually auto-provisioned by bootstrap) +CAMELEER_SAAS_IDENTITY_M2MCLIENTID= +CAMELEER_SAAS_IDENTITY_M2MCLIENTSECRET= +CAMELEER_SAAS_IDENTITY_SPACLIENTID= -# Domain (for Traefik TLS) -DOMAIN=localhost - -CAMELEER_AUTH_TOKEN=change_me_bootstrap_token -CAMELEER_CONTAINER_MEMORY_LIMIT=512m -CAMELEER_CONTAINER_CPU_SHARES=512 -CAMELEER_TENANT_SLUG=default +# SaaS Provisioning +CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=gitea.siegeln.net/cameleer/cameleer3-server:latest +CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=gitea.siegeln.net/cameleer/cameleer3-server-ui:latest diff --git a/CLAUDE.md b/CLAUDE.md index 535c5d9..deb6f1b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -98,7 +98,7 @@ The SaaS platform is a **vendor management plane**. It does not proxy requests t ### Routing (single-domain, path-based via Traefik) -All services on one hostname. Two env vars control everything: `PUBLIC_HOST` + `PUBLIC_PROTOCOL`. +All services on one hostname. Infrastructure containers (Traefik, Logto) use `PUBLIC_HOST` + `PUBLIC_PROTOCOL` env vars directly. The SaaS app reads these via `CAMELEER_SAAS_PROVISIONING_PUBLICHOST` / `CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL` (Spring Boot properties `cameleer.saas.provisioning.publichost` / `cameleer.saas.provisioning.publicprotocol`). | Path | Target | Notes | |------|--------|-------| @@ -175,17 +175,20 @@ These env vars are injected into provisioned per-tenant server containers: | Env var | Value | Purpose | |---------|-------|---------| -| `CAMELEER_OIDC_ISSUER_URI` | `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/oidc` | Token issuer claim validation | -| `CAMELEER_OIDC_JWK_SET_URI` | `http://logto:3001/oidc/jwks` | Docker-internal JWK fetch | -| `CAMELEER_OIDC_TLS_SKIP_VERIFY` | `true` (conditional) | Skip cert verify for OIDC discovery; only set when no `/certs/ca.pem` exists. When ca.pem exists, the server's `docker-entrypoint.sh` imports it into the JVM truststore instead. | -| `CAMELEER_CORS_ALLOWED_ORIGINS` | `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}` | Allow browser requests through Traefik | -| `CAMELEER_RUNTIME_ENABLED` | `true` | Enable Docker orchestration | -| `CAMELEER_SERVER_URL` | `http://cameleer3-server-{slug}:8081` | Per-tenant server URL (DNS alias on tenant network) | -| `CAMELEER_ROUTING_DOMAIN` | `${PUBLIC_HOST}` | Domain for Traefik routing labels | -| `CAMELEER_ROUTING_MODE` | `path` | `path` or `subdomain` routing | -| `CAMELEER_JAR_STORAGE_PATH` | `/data/jars` | Directory for uploaded JARs | -| `CAMELEER_DOCKER_NETWORK` | `cameleer-tenant-{slug}` | Primary network for deployed app containers | -| `CAMELEER_JAR_DOCKER_VOLUME` | `cameleer-jars-{slug}` | Docker volume name for JAR sharing between server and deployed containers | +| `CAMELEER_SERVER_SECURITY_OIDCISSUERURI` | `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/oidc` | Token issuer claim validation | +| `CAMELEER_SERVER_SECURITY_OIDCJWKSETURI` | `http://logto:3001/oidc/jwks` | Docker-internal JWK fetch | +| `CAMELEER_SERVER_SECURITY_OIDCTLSSKIPVERIFY` | `true` (conditional) | Skip cert verify for OIDC discovery; only set when no `/certs/ca.pem` exists. When ca.pem exists, the server's `docker-entrypoint.sh` imports it into the JVM truststore instead. | +| `CAMELEER_SERVER_SECURITY_OIDCAUDIENCE` | `https://api.cameleer.local` | JWT audience validation for OIDC tokens | +| `CAMELEER_SERVER_SECURITY_CORSALLOWEDORIGINS` | `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}` | Allow browser requests through Traefik | +| `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` | (generated) | Bootstrap auth token for M2M communication | +| `CAMELEER_SERVER_RUNTIME_ENABLED` | `true` | Enable Docker orchestration | +| `CAMELEER_SERVER_RUNTIME_SERVERURL` | `http://cameleer3-server-{slug}:8081` | Per-tenant server URL (DNS alias on tenant network) | +| `CAMELEER_SERVER_RUNTIME_ROUTINGDOMAIN` | `${PUBLIC_HOST}` | Domain for Traefik routing labels | +| `CAMELEER_SERVER_RUNTIME_ROUTINGMODE` | `path` | `path` or `subdomain` routing | +| `CAMELEER_SERVER_RUNTIME_JARSTORAGEPATH` | `/data/jars` | Directory for uploaded JARs | +| `CAMELEER_SERVER_RUNTIME_DOCKERNETWORK` | `cameleer-tenant-{slug}` | Primary network for deployed app containers | +| `CAMELEER_SERVER_RUNTIME_JARDOCKERVOLUME` | `cameleer-jars-{slug}` | Docker volume name for JAR sharing between server and deployed containers | +| `CAMELEER_SERVER_TENANT_ID` | (tenant UUID) | Tenant identifier for data isolation | | `BASE_PATH` (server-ui) | `/t/{slug}` | React Router basename + `` tag | | `CAMELEER_API_URL` (server-ui) | `http://cameleer-server-{slug}:8081` | Nginx upstream proxy target (NOT `API_URL` — image uses `${CAMELEER_API_URL}`) | @@ -194,9 +197,29 @@ These env vars are injected into provisioned per-tenant server containers: | Mount | Container path | Purpose | |-------|---------------|---------| | `/var/run/docker.sock` | `/var/run/docker.sock` | Docker socket for app deployment orchestration | -| `cameleer-jars-{slug}` (volume) | `/data/jars` | Shared JAR storage — server writes, deployed app containers read | +| `cameleer-jars-{slug}` (volume, via `CAMELEER_SERVER_RUNTIME_JARDOCKERVOLUME`) | `/data/jars` | Shared JAR storage — server writes, deployed app containers read | | `cameleer-saas_certs` (volume, ro) | `/certs` | Platform TLS certs + CA bundle for OIDC trust | +### SaaS app configuration (env vars for cameleer-saas itself) + +SaaS properties use the `cameleer.saas.*` prefix (env vars: `CAMELEER_SAAS_*`). Two groups: + +**Identity** (`cameleer.saas.identity.*` / `CAMELEER_SAAS_IDENTITY_*`): +- Logto endpoint, M2M credentials, bootstrap file path — used by `LogtoConfig.java` + +**Provisioning** (`cameleer.saas.provisioning.*` / `CAMELEER_SAAS_PROVISIONING_*`): + +| Env var | Spring property | Purpose | +|---------|----------------|---------| +| `CAMELEER_SAAS_PROVISIONING_SERVERIMAGE` | `cameleer.saas.provisioning.serverimage` | Docker image for per-tenant server containers | +| `CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE` | `cameleer.saas.provisioning.serveruiimage` | Docker image for per-tenant UI containers | +| `CAMELEER_SAAS_PROVISIONING_NETWORKNAME` | `cameleer.saas.provisioning.networkname` | Shared services Docker network (compose default) | +| `CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK` | `cameleer.saas.provisioning.traefiknetwork` | Traefik Docker network for routing | +| `CAMELEER_SAAS_PROVISIONING_PUBLICHOST` | `cameleer.saas.provisioning.publichost` | Public hostname (same value as infrastructure `PUBLIC_HOST`) | +| `CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL` | `cameleer.saas.provisioning.publicprotocol` | Public protocol (same value as infrastructure `PUBLIC_PROTOCOL`) | + +**Note:** `PUBLIC_HOST` and `PUBLIC_PROTOCOL` remain as infrastructure env vars for Traefik and Logto containers. The SaaS app reads its own copies via the `CAMELEER_SAAS_PROVISIONING_*` prefix. `LOGTO_ENDPOINT` and `LOGTO_DB_PASSWORD` are infrastructure env vars for the Logto service and are unchanged. + ### Server OIDC role extraction (two paths) | Path | Token type | Role source | How it works | @@ -308,7 +331,7 @@ PostgreSQL (Flyway): `src/main/resources/db/migration/` - `cameleer-saas` — SaaS vendor management plane (frontend + JAR baked in) - `cameleer-logto` — custom Logto with sign-in UI baked in - `cameleer3-server` / `cameleer3-server-ui` — provisioned per-tenant (not in compose, created by `DockerTenantProvisioner`) - - `cameleer-runtime-base` — base image for deployed apps (agent JAR + JRE). CI downloads latest agent SNAPSHOT from Gitea Maven registry. Uses `CAMELEER_SERVER_URL` env var (not CAMELEER_EXPORT_ENDPOINT). + - `cameleer-runtime-base` — base image for deployed apps (agent JAR + JRE). CI downloads latest agent SNAPSHOT from Gitea Maven registry. Uses `CAMELEER_SERVER_RUNTIME_SERVERURL` env var (not CAMELEER_EXPORT_ENDPOINT). - Docker builds: `--no-cache`, `--provenance=false` for Gitea compatibility - `docker-compose.dev.yml` — exposes ports for direct access, sets `SPRING_PROFILES_ACTIVE: dev`, `VENDOR_SEED_ENABLED: true`. Volume-mounts `./ui/dist` into the container so local UI builds are served without rebuilding the Docker image (`SPRING_WEB_RESOURCES_STATIC_LOCATIONS` overrides classpath). Adds Docker socket mount for tenant provisioning. - Design system: import from `@cameleer/design-system` (Gitea npm registry) diff --git a/HOWTO.md b/HOWTO.md index ceb9d44..ce6fd50 100644 --- a/HOWTO.md +++ b/HOWTO.md @@ -63,12 +63,12 @@ Edit `.env` and set at minimum: ```bash # Change in production POSTGRES_PASSWORD= -CAMELEER_AUTH_TOKEN= +CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN= CAMELEER_TENANT_SLUG= # e.g., "acme" — tags all observability data # Logto M2M credentials (get from Logto admin console after first boot) -LOGTO_M2M_CLIENT_ID= -LOGTO_M2M_CLIENT_SECRET= +CAMELEER_SAAS_IDENTITY_M2MCLIENTID= +CAMELEER_SAAS_IDENTITY_M2MCLIENTSECRET= ``` ### 2. Ed25519 Keys @@ -139,8 +139,8 @@ On first boot, Logto seeds its database automatically. Access the admin console - Assign the **Logto Management API** resource with all scopes 4. Update `.env`: ``` - LOGTO_M2M_CLIENT_ID= - LOGTO_M2M_CLIENT_SECRET= + CAMELEER_SAAS_IDENTITY_M2MCLIENTID= + CAMELEER_SAAS_IDENTITY_M2MCLIENTSECRET= ``` 5. Restart cameleer-saas: `docker compose restart cameleer-saas` diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 9942f7f..289da37 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -24,12 +24,12 @@ services: environment: SPRING_PROFILES_ACTIVE: dev SPRING_WEB_RESOURCES_STATIC_LOCATIONS: file:/app/static/,classpath:/static/ - PUBLIC_HOST: ${PUBLIC_HOST:-localhost} - PUBLIC_PROTOCOL: ${PUBLIC_PROTOCOL:-https} - CAMELEER_SERVER_IMAGE: gitea.siegeln.net/cameleer/cameleer3-server:${VERSION:-latest} - CAMELEER_SERVER_UI_IMAGE: gitea.siegeln.net/cameleer/cameleer3-server-ui:${VERSION:-latest} - CAMELEER_NETWORK: cameleer-saas_cameleer - CAMELEER_TRAEFIK_NETWORK: cameleer-traefik + CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost} + CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https} + CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: gitea.siegeln.net/cameleer/cameleer3-server:${VERSION:-latest} + CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: gitea.siegeln.net/cameleer/cameleer3-server-ui:${VERSION:-latest} + CAMELEER_SAAS_PROVISIONING_NETWORKNAME: cameleer-saas_cameleer + CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK: cameleer-traefik clickhouse: ports: diff --git a/docker-compose.yml b/docker-compose.yml index 7eb49c7..7376974 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -178,12 +178,12 @@ services: SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB:-cameleer_saas} SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-cameleer} SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD:-cameleer_dev} - LOGTO_ENDPOINT: ${LOGTO_ENDPOINT:-http://logto:3001} - LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} - LOGTO_ISSUER_URI: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}/oidc - LOGTO_JWK_SET_URI: ${LOGTO_ENDPOINT:-http://logto:3001}/oidc/jwks - LOGTO_M2M_CLIENT_ID: ${LOGTO_M2M_CLIENT_ID:-} - LOGTO_M2M_CLIENT_SECRET: ${LOGTO_M2M_CLIENT_SECRET:-} + CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT: ${LOGTO_ENDPOINT:-http://logto:3001} + CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} + CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https} + CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost} + CAMELEER_SAAS_IDENTITY_M2MCLIENTID: ${LOGTO_M2M_CLIENT_ID:-} + CAMELEER_SAAS_IDENTITY_M2MCLIENTSECRET: ${LOGTO_M2M_CLIENT_SECRET:-} labels: - traefik.enable=true - traefik.http.routers.saas.rule=PathPrefix(`/platform`) diff --git a/docs/architecture.md b/docs/architecture.md index e1fd162..22877d3 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -193,7 +193,7 @@ the bootstrap script (`docker/logto-bootstrap.sh`): **Agent -> cameleer3-server:** -1. Agent reads `CAMELEER_AUTH_TOKEN` environment variable (API key). +1. Agent reads `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` environment variable (API key). 2. Calls `POST /api/v1/agents/register` with the key as Bearer token. 3. Server validates via `BootstrapTokenValidator` (constant-time comparison). 4. Server issues internal HMAC JWT (access + refresh) + Ed25519 public key. @@ -493,9 +493,9 @@ The deployment lifecycle is managed by `DeploymentService`: | Variable | Value | |-----------------------------|----------------------------------------| - | `CAMELEER_AUTH_TOKEN` | API key for agent registration | + | `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` | API key for agent registration | | `CAMELEER_EXPORT_TYPE` | `HTTP` | - | `CAMELEER_SERVER_URL` | cameleer3-server internal URL | + | `CAMELEER_SERVER_RUNTIME_SERVERURL` | cameleer3-server internal URL | | `CAMELEER_APPLICATION_ID` | App slug | | `CAMELEER_ENVIRONMENT_ID` | Environment slug | | `CAMELEER_DISPLAY_NAME` | `{tenant}-{env}-{app}` | @@ -529,7 +529,7 @@ aspects relevant to the SaaS platform. ### 6.1 Agent Registration -1. Agent starts with `CAMELEER_AUTH_TOKEN` environment variable (an API key +1. Agent starts with `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` environment variable (an API key generated by the SaaS platform, prefixed with `cmk_`). 2. Agent calls `POST /api/v1/agents/register` on the cameleer3-server with the API key as a Bearer token. @@ -862,17 +862,15 @@ state (`currentTenantId`). Provides `logout` and `signIn` callbacks. | `SPRING_DATASOURCE_USERNAME`| `cameleer` | PostgreSQL user | | `SPRING_DATASOURCE_PASSWORD`| `cameleer_dev` | PostgreSQL password | -**Logto / OIDC:** +**Identity / OIDC:** | Variable | Default | Description | |---------------------------|------------|--------------------------------------------| -| `LOGTO_ENDPOINT` | (empty) | Logto internal URL (Docker-internal) | -| `LOGTO_PUBLIC_ENDPOINT` | (empty) | Logto public URL (browser-accessible) | -| `LOGTO_ISSUER_URI` | (empty) | OIDC issuer URI for JWT validation | -| `LOGTO_JWK_SET_URI` | (empty) | JWKS endpoint for JWT signature validation | -| `LOGTO_M2M_CLIENT_ID` | (empty) | M2M app client ID (from bootstrap) | -| `LOGTO_M2M_CLIENT_SECRET` | (empty) | M2M app client secret (from bootstrap) | -| `LOGTO_SPA_CLIENT_ID` | (empty) | SPA app client ID (fallback; bootstrap preferred) | +| `CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT` | (empty) | Logto internal URL (Docker-internal) | +| `CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT` | (empty) | Logto public URL (browser-accessible) | +| `CAMELEER_SAAS_IDENTITY_M2MCLIENTID` | (empty) | M2M app client ID (from bootstrap) | +| `CAMELEER_SAAS_IDENTITY_M2MCLIENTSECRET` | (empty) | M2M app client secret (from bootstrap) | +| `CAMELEER_SAAS_IDENTITY_SPACLIENTID` | (empty) | SPA app client ID (fallback; bootstrap preferred) | **Runtime / Deployment:** @@ -898,11 +896,11 @@ state (`currentTenantId`). Provides `logout` and `signIn` callbacks. | `SPRING_DATASOURCE_USERNAME`| `cameleer` | PostgreSQL user | | `SPRING_DATASOURCE_PASSWORD`| `cameleer_dev` | PostgreSQL password | | `CLICKHOUSE_URL` | `jdbc:clickhouse://clickhouse:8123/cameleer` | ClickHouse JDBC URL | -| `CAMELEER_AUTH_TOKEN` | `default-bootstrap-token` | Agent bootstrap token | +| `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` | `default-bootstrap-token` | Agent bootstrap token | | `CAMELEER_JWT_SECRET` | `cameleer-dev-jwt-secret-...` | HMAC secret for internal JWTs | -| `CAMELEER_TENANT_ID` | `default` | Tenant slug for data isolation | -| `CAMELEER_OIDC_ISSUER_URI` | (empty) | Logto issuer for M2M token validation | -| `CAMELEER_OIDC_AUDIENCE` | (empty) | Expected JWT audience | +| `CAMELEER_SERVER_TENANT_ID` | `default` | Tenant slug for data isolation | +| `CAMELEER_SERVER_SECURITY_OIDCISSUERURI` | (empty) | Logto issuer for M2M token validation | +| `CAMELEER_SERVER_SECURITY_OIDCAUDIENCE` | (empty) | Expected JWT audience | ### 10.3 logto @@ -927,7 +925,7 @@ state (`currentTenantId`). Provides `logout` and `signIn` callbacks. | `SAAS_ADMIN_PASS` | `admin` | Platform admin password | | `TENANT_ADMIN_USER` | `camel` | Default tenant admin username | | `TENANT_ADMIN_PASS` | `camel` | Default tenant admin password | -| `CAMELEER_AUTH_TOKEN`| `default-bootstrap-token` | Agent bootstrap token | +| `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN`| `default-bootstrap-token` | Agent bootstrap token | ### 10.6 Bootstrap Output diff --git a/docs/user-manual.md b/docs/user-manual.md index 53257ac..821f4c4 100644 --- a/docs/user-manual.md +++ b/docs/user-manual.md @@ -435,14 +435,12 @@ Copy `.env.example` to `.env` and configure as needed: | `POSTGRES_USER` | PostgreSQL username | `cameleer` | | `POSTGRES_PASSWORD` | PostgreSQL password | `change_me_in_production` | | `POSTGRES_DB` | PostgreSQL database name | `cameleer_saas` | -| `LOGTO_ENDPOINT` | Internal Logto URL (container-to-container) | `http://logto:3001` | -| `LOGTO_PUBLIC_ENDPOINT` | Public-facing Logto URL | `http://localhost:3001` | -| `LOGTO_ISSUER_URI` | OIDC issuer URI | `http://localhost:3001/oidc` | -| `LOGTO_JWK_SET_URI` | OIDC JWK set URI | `http://logto:3001/oidc/jwks` | -| `LOGTO_M2M_CLIENT_ID` | Machine-to-machine client ID (auto-set by bootstrap) | _(empty)_ | -| `LOGTO_M2M_CLIENT_SECRET` | Machine-to-machine client secret (auto-set by bootstrap) | _(empty)_ | -| `LOGTO_SPA_CLIENT_ID` | SPA client ID for the frontend | _(empty)_ | -| `CAMELEER_AUTH_TOKEN` | Bootstrap token for agent registration | `change_me_bootstrap_token` | +| `CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT` | Internal Logto URL (container-to-container) | `http://logto:3001` | +| `CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT` | Public-facing Logto URL | `http://localhost:3001` | +| `CAMELEER_SAAS_IDENTITY_M2MCLIENTID` | Machine-to-machine client ID (auto-set by bootstrap) | _(empty)_ | +| `CAMELEER_SAAS_IDENTITY_M2MCLIENTSECRET` | Machine-to-machine client secret (auto-set by bootstrap) | _(empty)_ | +| `CAMELEER_SAAS_IDENTITY_SPACLIENTID` | SPA client ID for the frontend | _(empty)_ | +| `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` | Bootstrap token for agent registration | `change_me_bootstrap_token` | | `CAMELEER_CONTAINER_MEMORY_LIMIT` | Memory limit for deployed containers | `512m` | | `CAMELEER_CONTAINER_CPU_SHARES` | CPU shares for deployed containers | `512` | | `CAMELEER_TENANT_SLUG` | Default tenant slug | `default` | @@ -550,7 +548,7 @@ The Cameleer SaaS application itself does not need any changes -- all identity c **Resolution:** 1. Check backend logs: `docker compose logs cameleer-saas`. -2. Verify that `LOGTO_ISSUER_URI` and `LOGTO_JWK_SET_URI` in `.env` are correct. +2. Verify that `CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT` in `.env` is correct (the OIDC issuer and JWK set URIs are derived from it automatically). 3. If the issue persists, restart the services: `docker compose restart cameleer-saas logto`. ### Deployment Stuck in BUILDING @@ -577,14 +575,14 @@ The Cameleer SaaS application itself does not need any changes -- all identity c **Possible causes:** - The agent cannot reach the cameleer3-server endpoint. Check network connectivity between the deployed container and the observability server. -- The bootstrap token does not match. The agent uses `CAMELEER_AUTH_TOKEN` to register with the server. +- The bootstrap token does not match. The agent uses `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` to register with the server. - The cameleer3-server is not healthy. **Resolution:** 1. Check cameleer3-server health: `docker compose logs cameleer3-server`. 2. Verify the app container's logs for agent connection errors (use the Logs tab on the app detail page). -3. Confirm that `CAMELEER_AUTH_TOKEN` is the same in both the `cameleer-saas` and `cameleer3-server` service configurations. +3. Confirm that `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` is the same in both the `cameleer-saas` and `cameleer3-server` service configurations. ### Container Health Check Failing diff --git a/src/main/java/net/siegeln/cameleer/saas/config/PublicConfigController.java b/src/main/java/net/siegeln/cameleer/saas/config/PublicConfigController.java index 5955d90..6973b2b 100644 --- a/src/main/java/net/siegeln/cameleer/saas/config/PublicConfigController.java +++ b/src/main/java/net/siegeln/cameleer/saas/config/PublicConfigController.java @@ -18,10 +18,10 @@ public class PublicConfigController { private static final Logger log = LoggerFactory.getLogger(PublicConfigController.class); private static final String BOOTSTRAP_FILE = "/data/bootstrap/logto-bootstrap.json"; - @Value("${cameleer.identity.logto-public-endpoint:${cameleer.identity.logto-endpoint:}}") + @Value("${cameleer.saas.identity.logtopublicendpoint:${cameleer.saas.identity.logtoendpoint:}}") private String logtoPublicEndpoint; - @Value("${cameleer.identity.spa-client-id:}") + @Value("${cameleer.saas.identity.spaclientid:}") private String spaClientId; private final ObjectMapper objectMapper = new ObjectMapper(); diff --git a/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java b/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java index e694b6f..f16ab1d 100644 --- a/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java +++ b/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java @@ -80,7 +80,7 @@ public class SecurityConfig { public JwtDecoder jwtDecoder( @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") String jwkSetUri, @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri:}") String issuerUri, - @Value("${cameleer.identity.audience:}") String audience) throws Exception { + @Value("${cameleer.saas.identity.audience:}") String audience) throws Exception { var jwkSource = JWKSourceBuilder.create(new URL(jwkSetUri)).build(); var keySelector = new JWSVerificationKeySelector( JWSAlgorithm.ES384, jwkSource); diff --git a/src/main/java/net/siegeln/cameleer/saas/identity/LogtoConfig.java b/src/main/java/net/siegeln/cameleer/saas/identity/LogtoConfig.java index 49f33e0..707f83e 100644 --- a/src/main/java/net/siegeln/cameleer/saas/identity/LogtoConfig.java +++ b/src/main/java/net/siegeln/cameleer/saas/identity/LogtoConfig.java @@ -16,16 +16,16 @@ public class LogtoConfig { private static final Logger log = LoggerFactory.getLogger(LogtoConfig.class); private static final String BOOTSTRAP_FILE = "/data/bootstrap/logto-bootstrap.json"; - @Value("${cameleer.identity.logto-endpoint:}") + @Value("${cameleer.saas.identity.logtoendpoint:}") private String logtoEndpoint; - @Value("${cameleer.identity.m2m-client-id:}") + @Value("${cameleer.saas.identity.m2mclientid:}") private String m2mClientId; - @Value("${cameleer.identity.m2m-client-secret:}") + @Value("${cameleer.saas.identity.m2mclientsecret:}") private String m2mClientSecret; - @Value("${cameleer.identity.server-endpoint:http://cameleer3-server:8081}") + @Value("${cameleer.saas.identity.serverendpoint:http://cameleer3-server:8081}") private String serverEndpoint; private String tradAppId; diff --git a/src/main/java/net/siegeln/cameleer/saas/provisioning/CertificateManagerAutoConfig.java b/src/main/java/net/siegeln/cameleer/saas/provisioning/CertificateManagerAutoConfig.java index b8294e1..a4c7fd1 100644 --- a/src/main/java/net/siegeln/cameleer/saas/provisioning/CertificateManagerAutoConfig.java +++ b/src/main/java/net/siegeln/cameleer/saas/provisioning/CertificateManagerAutoConfig.java @@ -17,7 +17,7 @@ public class CertificateManagerAutoConfig { @Bean CertificateManager certificateManager( - @Value("${cameleer.certs.path:/certs}") String certsPath) { + @Value("${cameleer.saas.certs.path:/certs}") String certsPath) { Path path = Path.of(certsPath); if (Files.isDirectory(path)) { log.info("Certs directory found at {} — enabling Docker certificate manager", certsPath); diff --git a/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerTenantProvisioner.java b/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerTenantProvisioner.java index b530ae0..c530668 100644 --- a/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerTenantProvisioner.java +++ b/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerTenantProvisioner.java @@ -195,27 +195,27 @@ public class DockerTenantProvisioner implements TenantProvisioner { "SPRING_DATASOURCE_URL=" + props.datasourceUrl(), "SPRING_DATASOURCE_USERNAME=cameleer", "SPRING_DATASOURCE_PASSWORD=cameleer_dev", - "CLICKHOUSE_URL=jdbc:clickhouse://clickhouse:8123/cameleer", - "CAMELEER_TENANT_ID=" + slug, - "CAMELEER_AUTH_TOKEN=" + req.licenseToken(), - "CAMELEER_JWT_SECRET=cameleer-dev-jwt-secret-change-in-production", - "CAMELEER_OIDC_ISSUER_URI=" + props.oidcIssuerUri(), - "CAMELEER_OIDC_JWK_SET_URI=" + props.oidcJwkSetUri(), - "CAMELEER_OIDC_AUDIENCE=https://api.cameleer.local", - "CAMELEER_CORS_ALLOWED_ORIGINS=" + props.corsOrigins(), - "CAMELEER_LICENSE_TOKEN=" + req.licenseToken(), - "CAMELEER_RUNTIME_ENABLED=true", - "CAMELEER_SERVER_URL=http://" + name + ":8081", - "CAMELEER_ROUTING_DOMAIN=" + props.publicHost(), - "CAMELEER_ROUTING_MODE=path", - "CAMELEER_JAR_STORAGE_PATH=/data/jars", + "CAMELEER_SERVER_CLICKHOUSE_URL=jdbc:clickhouse://clickhouse:8123/cameleer", + "CAMELEER_SERVER_TENANT_ID=" + slug, + "CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN=" + req.licenseToken(), + "CAMELEER_SERVER_SECURITY_JWTSECRET=cameleer-dev-jwt-secret-change-in-production", + "CAMELEER_SERVER_SECURITY_OIDCISSUERURI=" + props.oidcIssuerUri(), + "CAMELEER_SERVER_SECURITY_OIDCJWKSETURI=" + props.oidcJwkSetUri(), + "CAMELEER_SERVER_SECURITY_OIDCAUDIENCE=https://api.cameleer.local", + "CAMELEER_SERVER_SECURITY_CORSALLOWEDORIGINS=" + props.corsOrigins(), + "CAMELEER_SERVER_LICENSE_TOKEN=" + req.licenseToken(), + "CAMELEER_SERVER_RUNTIME_ENABLED=true", + "CAMELEER_SERVER_RUNTIME_SERVERURL=http://" + name + ":8081", + "CAMELEER_SERVER_RUNTIME_ROUTINGDOMAIN=" + props.publicHost(), + "CAMELEER_SERVER_RUNTIME_ROUTINGMODE=path", + "CAMELEER_SERVER_RUNTIME_JARSTORAGEPATH=/data/jars", // Apps deployed by this server join the tenant network (isolated) - "CAMELEER_DOCKER_NETWORK=" + tenantNetwork, - "CAMELEER_JAR_DOCKER_VOLUME=cameleer-jars-" + slug + "CAMELEER_SERVER_RUNTIME_DOCKERNETWORK=" + tenantNetwork, + "CAMELEER_SERVER_RUNTIME_JARDOCKERVOLUME=cameleer-jars-" + slug )); // If no CA bundle exists, fall back to TLS skip for OIDC (self-signed dev) if (!java.nio.file.Files.exists(java.nio.file.Path.of("/certs/ca.pem"))) { - env.add("CAMELEER_OIDC_TLS_SKIP_VERIFY=true"); + env.add("CAMELEER_SERVER_SECURITY_OIDCTLSSKIPVERIFY=true"); } // Primary network = tenant-isolated network diff --git a/src/main/java/net/siegeln/cameleer/saas/provisioning/ProvisioningProperties.java b/src/main/java/net/siegeln/cameleer/saas/provisioning/ProvisioningProperties.java index 62d77f4..c12ba7a 100644 --- a/src/main/java/net/siegeln/cameleer/saas/provisioning/ProvisioningProperties.java +++ b/src/main/java/net/siegeln/cameleer/saas/provisioning/ProvisioningProperties.java @@ -2,7 +2,7 @@ package net.siegeln.cameleer.saas.provisioning; import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties(prefix = "cameleer.provisioning") +@ConfigurationProperties(prefix = "cameleer.saas.provisioning") public record ProvisioningProperties( String serverImage, String serverUiImage, diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index ed3b0d3..291ca40 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -17,7 +17,9 @@ spring: jwk-set-uri: http://localhost:3001/oidc/jwks cameleer: - clickhouse: - url: jdbc:clickhouse://localhost:8123/cameleer - runtime: - cameleer3-server-endpoint: http://localhost:8081 + saas: + identity: + logtoendpoint: http://localhost:3001 + serverendpoint: http://localhost:8081 + provisioning: + clickhouseurl: jdbc:clickhouse://localhost:8123/cameleer diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0d39f4f..5898ce7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,8 +20,8 @@ spring: oauth2: resourceserver: jwt: - issuer-uri: ${LOGTO_ISSUER_URI:} - jwk-set-uri: ${LOGTO_JWK_SET_URI:} + issuer-uri: ${cameleer.saas.provisioning.publicprotocol:https}://${cameleer.saas.provisioning.publichost:localhost}/oidc + jwk-set-uri: ${cameleer.saas.identity.logtoendpoint:http://logto:3001}/oidc/jwks management: endpoints: @@ -33,23 +33,26 @@ management: show-details: when-authorized cameleer: - identity: - logto-endpoint: ${LOGTO_ENDPOINT:} - logto-public-endpoint: ${LOGTO_PUBLIC_ENDPOINT:} - m2m-client-id: ${LOGTO_M2M_CLIENT_ID:} - m2m-client-secret: ${LOGTO_M2M_CLIENT_SECRET:} - spa-client-id: ${LOGTO_SPA_CLIENT_ID:} - audience: ${CAMELEER_OIDC_AUDIENCE:https://api.cameleer.local} - server-endpoint: ${CAMELEER3_SERVER_ENDPOINT:http://cameleer3-server:8081} - provisioning: - server-image: ${CAMELEER_SERVER_IMAGE:gitea.siegeln.net/cameleer/cameleer3-server:latest} - server-ui-image: ${CAMELEER_SERVER_UI_IMAGE:gitea.siegeln.net/cameleer/cameleer3-server-ui:latest} - network-name: ${CAMELEER_NETWORK:cameleer-saas_cameleer} - traefik-network: ${CAMELEER_TRAEFIK_NETWORK:cameleer-traefik} - public-host: ${PUBLIC_HOST:localhost} - public-protocol: ${PUBLIC_PROTOCOL:https} - datasource-url: ${CAMELEER_SERVER_DB_URL:jdbc:postgresql://postgres:5432/cameleer3} - clickhouse-url: ${CLICKHOUSE_URL:jdbc:clickhouse://clickhouse:8123/cameleer} - oidc-issuer-uri: ${PUBLIC_PROTOCOL:https}://${PUBLIC_HOST:localhost}/oidc - oidc-jwk-set-uri: http://logto:3001/oidc/jwks - cors-origins: ${PUBLIC_PROTOCOL:https}://${PUBLIC_HOST:localhost} + saas: + identity: + logtoendpoint: ${CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT:} + logtopublicendpoint: ${CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT:} + m2mclientid: ${CAMELEER_SAAS_IDENTITY_M2MCLIENTID:} + m2mclientsecret: ${CAMELEER_SAAS_IDENTITY_M2MCLIENTSECRET:} + spaclientid: ${CAMELEER_SAAS_IDENTITY_SPACLIENTID:} + audience: ${CAMELEER_SAAS_IDENTITY_AUDIENCE:https://api.cameleer.local} + serverendpoint: ${CAMELEER_SAAS_IDENTITY_SERVERENDPOINT:http://cameleer3-server:8081} + provisioning: + serverimage: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:gitea.siegeln.net/cameleer/cameleer3-server:latest} + serveruiimage: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:gitea.siegeln.net/cameleer/cameleer3-server-ui:latest} + networkname: ${CAMELEER_SAAS_PROVISIONING_NETWORKNAME:cameleer-saas_cameleer} + traefiknetwork: ${CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK:cameleer-traefik} + publichost: ${CAMELEER_SAAS_PROVISIONING_PUBLICHOST:localhost} + publicprotocol: ${CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL:https} + datasourceurl: ${CAMELEER_SAAS_PROVISIONING_DATASOURCEURL:jdbc:postgresql://postgres:5432/cameleer3} + clickhouseurl: ${CAMELEER_SAAS_PROVISIONING_CLICKHOUSEURL:jdbc:clickhouse://clickhouse:8123/cameleer} + oidcissueruri: ${cameleer.saas.provisioning.publicprotocol}://${cameleer.saas.provisioning.publichost}/oidc + oidcjwkseturi: http://logto:3001/oidc/jwks + corsorigins: ${cameleer.saas.provisioning.publicprotocol}://${cameleer.saas.provisioning.publichost} + certs: + path: ${CAMELEER_SAAS_CERTS_PATH:/certs}