Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/workflows/ai-workspace-pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ concurrency:
jobs:
pr-check:
runs-on: ubuntu-24.04
env:
APIP_CP_ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
APIP_CP_AUTH_JWT_SECRET_KEY: "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
steps:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
Expand Down Expand Up @@ -58,6 +55,22 @@ jobs:
- name: Map host.docker.internal on CI host
run: echo "127.0.0.1 host.docker.internal" | sudo tee -a /etc/hosts

- name: Generate quickstart keys, credentials and certificates
# setup.sh writes api-platform.env (encryption/JWT keys + admin credentials) and
# the TLS certs both services now require. Credentials are pinned to the
# Cypress defaults (admin/admin) for the E2E suite.
run: ADMIN_USERNAME=admin ADMIN_PASSWORD=admin ./setup.sh
working-directory: portals/ai-workspace

- name: Export quickstart credentials as environment variables
# api-platform.env is loaded into the containers via docker-compose's env_file,
# but later steps in this job (readiness probe, Cypress) run outside those
# containers and need the same values as real env vars. Export every generated
# key to $GITHUB_ENV so each subsequent step ("test") in this job sees them.
run: |
grep -v '^#' api-platform.env | grep -v '^\s*$' >> "$GITHUB_ENV"
working-directory: portals/ai-workspace

- name: Start quickstart stack
# --wait blocks until all healthchecks pass. The ai-workspace healthcheck
# now verifies the nginx→platform-api proxy route (not just the static
Expand Down
20 changes: 20 additions & 0 deletions distribution/all-in-one/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ services:
depends_on:
postgres:
condition: service_healthy
platform-api-certgen:
condition: service_completed_successfully

# One-shot init container: generates the TLS pair the platform-api HTTPS
# listener requires (the server no longer generates a self-signed fallback).
platform-api-certgen:
image: alpine/openssl
entrypoint: ["/bin/sh", "-c"]
command:
- |
[ -f /certs/cert.pem ] && [ -f /certs/key.pem ] && exit 0
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
-keyout /certs/key.pem -out /certs/cert.pem \
-subj "/O=WSO2 API Platform/CN=platform-api" \
-addext "subjectAltName=DNS:localhost,DNS:platform-api,IP:127.0.0.1"
volumes:
- platform-api-certs:/certs

platform-api:
image: ghcr.io/wso2/api-platform/platform-api:latest
Expand All @@ -62,6 +79,7 @@ services:
- "9243:9243"
volumes:
- platform-api-data:/api-platform/data
- platform-api-certs:/app/data/certs
environment:
- APIP_CP_DEFAULT_DEVPORTAL_API_URL=http://devportal:3001
- APIP_CP_DATABASE_DRIVER=postgres
Expand Down Expand Up @@ -100,4 +118,6 @@ services:
volumes:
platform-api-data:
driver: local
platform-api-certs:
driver: local
postgres_data:
2 changes: 1 addition & 1 deletion docs/ai-workspace/authentication/asgardeo-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ client_id = "<ai-workspace-client-id>"
redirect_url = "https://<your-domain>/api/auth/callback" # the BFF callback (section 2)
post_logout_redirect_url = "https://<your-domain>/login"

# Preferred — a mounted secret file. To read it from a git-ignored .env instead, swap the
# Preferred — a mounted secret file. To read it from the git-ignored api-platform.env instead, swap the
# token for '{{ env "APIP_AIW_OIDC_CLIENT_SECRET" }}': the key needs one token or the other.
client_secret = '{{ file "/secrets/ai-workspace/oidc_client_secret" }}'

Expand Down
2 changes: 1 addition & 1 deletion docs/ai-workspace/authentication/oidc-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ The redirect URLs are ordinary `config.toml` keys; the secret is *referenced* by
than written into it, so the raw value never lands in a committed file.

For a simpler local setup, swap the `{{ file }}` token for `'{{ env "APIP_AIW_OIDC_CLIENT_SECRET" }}'` and keep
the value in a git-ignored `.env`. The key must carry one token or the other — a variable set with
the value in the git-ignored `api-platform.env`. The key must carry one token or the other — a variable set with
no token to read it is ignored. Either token fails closed: a missing secret aborts startup rather
than yielding an empty credential. See [Configuration → Secrets](../configuration.md#secrets).

Expand Down
8 changes: 4 additions & 4 deletions docs/ai-workspace/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ A key written this way can be set from the environment without editing the file.

By convention the variable a token names is the key's full path — table and key, uppercased, dots as underscores — prefixed with **`APIP_AIW_`**: `[oidc] client_id` → `APIP_AIW_OIDC_CLIENT_ID`, `[platform_api] url` → `APIP_AIW_PLATFORM_API_URL`, and a top-level `log_level` → `APIP_AIW_LOG_LEVEL`. (The same prefix convention gives the Platform API `APIP_CP_` and the Developer Portal `APIP_DP_`.) It is only a convention: a token may name any variable, which is what lets a key read an existing secret under its own name.

The file's own location is not a config key — it cannot be, since it is needed before the file is read. The server reads its mount, `/etc/ai-workspace/config.toml`, unless `-config` names another path (`bff -config ../configs/config.toml`, which is what `make bff-run` does). Two variables are likewise read directly by the server rather than through a token: `APIP_DEMO_MODE` (a stack-wide runtime flag) and `APIP_CONFIG_FILE_SOURCE_ALLOWLIST`, which bounds where `{{ file }}` tokens may read from (see below).
The file's own location is not a config key — it cannot be, since it is needed before the file is read. The server reads its mount, `/etc/ai-workspace/config.toml`, unless `-config` names another path (`bff -config ../configs/config.toml`, which is what `make bff-run` does). One variable is likewise read directly by the server rather than through a token: `APIP_CONFIG_FILE_SOURCE_ALLOWLIST`, which bounds where `{{ file }}` tokens may read from (see below).

Copy `configs/config-template.toml` to `configs/config.toml` and fill in the values for your deployment before starting the stack.

## Secrets

Never write a secret as a literal in `config.toml`, and never hardcode one in `docker-compose.yaml`. There are two supported ways to supply the OIDC client secret:

**Environment variable (default)** — the key's token names the variable and has no default value, so an unset variable fails startup rather than running with an empty credential. Keep the value in a git-ignored `.env`:
**Environment variable (default)** — the key's token names the variable and has no default value, so an unset variable fails startup rather than running with an empty credential. Keep the value in the git-ignored `api-platform.env` (loaded into both services via `env_file`):

```toml
[oidc]
Expand Down Expand Up @@ -59,7 +59,7 @@ The "Env var" column is the variable each key's shipped `{{ env }}` token names
| Key | Env var | Default | Description |
|-----|-------------|---------|-------------|
| `url` | `APIP_AIW_PLATFORM_API_URL` | — | **Required.** Absolute URL the BFF uses to reach the Platform API server-to-server (e.g. `https://platform-api:9243`) — an origin, not a base path; the API paths are appended by the proxy. Its scheme decides whether the upstream hop uses TLS. |
| `tls_skip_verify` | `APIP_AIW_PLATFORM_API_TLS_SKIP_VERIFY` | `false` | Accept the upstream's self-signed certificate. Rejected when `APIP_DEMO_MODE=false`. |
| `tls_skip_verify` | `APIP_AIW_PLATFORM_API_TLS_SKIP_VERIFY` | `false` | Skip upstream certificate verification entirely (local development only) — prefer `ca_file`. |
| `ca_file` | `APIP_AIW_PLATFORM_API_CA_FILE` | — | PEM bundle trusted for the upstream certificate, appended to the system roots. Prefer this over `tls_skip_verify`. |

### `[oidc]` (only required when `auth_mode = "oidc"`)
Expand Down Expand Up @@ -164,7 +164,7 @@ encryption_key = '{{ env "APIP_CP_ENCRYPTION_KEY" }}' # from an env v
secret_key = '{{ file "/secrets/platform-api/jwt_secret" }}' # from a file (preferred)
```

Supply the env values from a git-ignored `keys.env` and start with `docker compose --env-file keys.env up`
Supply the env values from a git-ignored `api-platform.env` and start with `docker compose up`
Or mount a secret file and use `{{ file }}`.

The `APIP_CP_`-prefixed names referenced by the tokens above:
Expand Down
13 changes: 7 additions & 6 deletions docs/ai-workspace/features/secrets-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ Generate a stable key with:
openssl rand -hex 32
```

For Docker Compose deployments, set the key in a `keys.env` file next to `docker-compose.yaml`. First generate a key:
For Docker Compose deployments, set the key in a `api-platform.env` file next to `docker-compose.yaml`. First generate a key:

```sh
openssl rand -hex 32
Expand Down Expand Up @@ -378,17 +378,18 @@ a Docker/Kubernetes secret) under an allowed directory (`/etc/platform-api` or
with `{{ file "..." }}`. The value never appears in the environment or the compose file.

**Simple — an env file (`{{ env "..." }}`).** For local/quickstart Docker Compose, put the
values in `keys.env` (git-ignored) and start the stack with `--env-file`. The compose forwards them into the container via an `environment:`
`${APIP_CP_…}` passthrough — never an `env_file:` block or a hardcoded value:
values in `api-platform.env` (git-ignored). The compose loads it into the container via an
`env_file:` entry with `format: raw` (raw so the bcrypt hash's `$` is not treated as compose
interpolation) — never a hardcoded value in an `environment:` block:

```sh
cp keys.env.example keys.env
# then edit keys.env (values are the `openssl rand -hex 32` output — .env files
cp api-platform.env.example api-platform.env
# then edit api-platform.env (values are the `openssl rand -hex 32` output — .env files
# do NOT run command substitution, so paste the generated string, not the command):
# APIP_CP_ENCRYPTION_KEY=a3f1e2d4b5c6...
# APIP_CP_AUTH_JWT_SECRET_KEY=b7c8d9e0f1a2...

docker compose --env-file keys.env up -d
docker compose up -d
```

> **Warning:** Use the **same** stable keys across restarts and across all replicas. Changing
Expand Down
21 changes: 9 additions & 12 deletions platform-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,11 @@ token placed in the config file, which is resolved at load time via `os.LookupEn
with no token always takes its literal TOML value (or the built-in default). See "Providing
secrets via the config file" below.

By convention the samples name these interpolation variables with an `APIP_CP_` prefix for one
consistent namespace — e.g. `encryption_key = '{{ env "APIP_CP_ENCRYPTION_KEY" }}'`. The prefix
is only a naming convention on the token argument; there is no prefix-stripping or automatic
key mapping. So the `APIP_CP_*` names in the tables below are the variable names you place in an
`{{ env "…" }}` token (shown next to the config key they populate), not standalone overrides.

Two variables are read directly, outside any token: `APIP_DEMO_MODE` (a standalone runtime flag)
and `APIP_CONFIG_FILE_SOURCE_ALLOWLIST` (the allowlist of directories a `{{ file "…" }}` token
may read from).
One variable is intentionally **not** prefixed: the shared `APIP_CONFIG_FILE_SOURCE_ALLOWLIST`. The `{{ env "NAME" }}` interpolation
tokens in the config file read the literal name via `os.LookupEnv` (independent of the koanf
prefix mechanism); the samples use the same `APIP_CP_`-prefixed names for one consistent
namespace — e.g. `{{ env "APIP_CP_ENCRYPTION_KEY" }}` (see "Providing secrets via the config
file" below).

### Authentication

Expand All @@ -255,8 +251,9 @@ APIP_CP_AUTH_IDP_ENABLED=false (default) → Local JWT mode (HMAC signature v
APIP_CP_AUTH_IDP_ENABLED=true → IDP mode (JWKS-based verification)
```

> **Demo mode (`APIP_DEMO_MODE`).** Defaults to `true`; an explicit `false`/`0` opts into
> production-grade startup checks. Note that `APIP_CP_ENCRYPTION_KEY` and `APIP_CP_AUTH_JWT_SECRET_KEY` are **required**.
> `APIP_CP_ENCRYPTION_KEY` and `APIP_CP_AUTH_JWT_SECRET_KEY` are **required**; startup
> fails without them. TLS certificates are likewise required whenever the HTTPS
> listener is enabled — the server never generates a self-signed pair.

---

Expand Down Expand Up @@ -420,7 +417,7 @@ a missing/empty required env var, or a missing/disallowed/oversize file, aborts
| Variable | Default | Description |
|---|---|---|
| `LOG_LEVEL` | `DEBUG` | Log verbosity (`DEBUG`, `INFO`, `WARN`, `ERROR`) |
| `HTTPS_ENABLED` | `true` | Enable the TLS listener. Certificates are read from `HTTPS_CERT_DIR` (or generated in demo mode) |
| `HTTPS_ENABLED` | `true` | Enable the TLS listener. Certificates are read from `HTTPS_CERT_DIR` (cert.pem / key.pem — required) |
| `HTTPS_PORT` | `9243` | Port for the TLS listener |
| `HTTPS_CERT_DIR` | `./data/certs` | Directory holding `cert.pem` / `key.pem` (used only when `HTTPS_ENABLED=true`) |
| `HTTP_ENABLED` | `false` | Enable the plain-HTTP listener. Use only behind a TLS-terminating ingress/sidecar or for internal traffic — never expose directly to untrusted networks |
Expand Down
Loading
Loading