From ba371e37bd5650e8a0397b5fc3973a16cdd3aea2 Mon Sep 17 00:00:00 2001 From: O M Date: Tue, 13 Oct 2026 19:05:27 -0400 Subject: [PATCH 1/4] Document Google and GitHub console login. The console no longer asks operators to paste the API secret. Co-authored-by: Cursor --- src/pages/docs/api.astro | 10 +++++--- src/pages/docs/architecture.astro | 2 +- src/pages/docs/configuration.astro | 14 +++++++++-- src/pages/docs/console.astro | 33 ++++++++++++++++-------- src/pages/docs/docker.astro | 4 ++- src/pages/docs/kubernetes.astro | 18 +++++++++++++- src/pages/docs/rules.astro | 4 +-- src/pages/docs/security.astro | 40 +++++++++++++++++++++--------- 8 files changed, 92 insertions(+), 33 deletions(-) diff --git a/src/pages/docs/api.astro b/src/pages/docs/api.astro index aa437cf..173b8b6 100644 --- a/src/pages/docs/api.astro +++ b/src/pages/docs/api.astro @@ -15,10 +15,12 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; no GraphQL endpoint.

- Every /v1 route except GET /v1/health checks - OM_API_SECRET. Send it as Authorization: Bearer … or - X-API-Key. The console HTML does not. Treat the API as an internal service. - Details are on Security. + Machine clients send OM_API_SECRET as Authorization: Bearer … or + X-API-Key. A console session cookie is the other accepted credential. + GET /v1/health, GET /v1/auth/session, and + POST /v1/auth/logout are open. Admin management requires the session. + Treat the API as an internal service. Details are on Security + and Console.

Reads

diff --git a/src/pages/docs/architecture.astro b/src/pages/docs/architecture.astro index 083184f..959744a 100644 --- a/src/pages/docs/architecture.astro +++ b/src/pages/docs/architecture.astro @@ -104,7 +104,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; The CLI opens Postgres and writes batches, findings, and export calls itself. The API opens the same database. POST /v1/ingest is the HTTP equivalent of an upsert. om scan does not call the API. That split is why scans work when you only start - the Postgres container, and why the console API key is irrelevant to the CLI. + the Postgres container, and why a console session is irrelevant to the CLI.

Deployment shapes

diff --git a/src/pages/docs/configuration.astro b/src/pages/docs/configuration.astro index 0a90d7e..486353a 100644 --- a/src/pages/docs/configuration.astro +++ b/src/pages/docs/configuration.astro @@ -45,7 +45,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; OM_PUBLIC_URL http://localhost:8080 - Public URL hint. Requests use the listen address. + Browser origin for OAuth callbacks and setup links. No trailing path. OM_API_PORT @@ -55,7 +55,17 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; OM_API_SECRET change-me-in-production - Shared secret for every /v1 route except GET /v1/health. See Security. + Machine credential for /v1 routes. The console uses a session instead. See Security. + + + OM_GOOGLE_CLIENT_ID / OM_GOOGLE_CLIENT_SECRET + empty + Google sign-in. Both are required. Callback {OM_PUBLIC_URL}/auth/google/callback. + + + OM_GITHUB_CLIENT_ID / OM_GITHUB_CLIENT_SECRET + empty + GitHub sign-in. Both are required. Callback {OM_PUBLIC_URL}/auth/github/callback. diff --git a/src/pages/docs/console.astro b/src/pages/docs/console.astro index 88bdb4f..025b8fa 100644 --- a/src/pages/docs/console.astro +++ b/src/pages/docs/console.astro @@ -32,19 +32,32 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; list and is not drawn.

-

Run CSPM rules from the browser

+

Sign in

+

+ The console signs people in with Google or GitHub. There is no password and no API-key field. + Configure OM_GOOGLE_CLIENT_ID and OM_GOOGLE_CLIENT_SECRET, or the + OM_GITHUB_* pair, and register + {OM_PUBLIC_URL}/auth/google/callback or + {OM_PUBLIC_URL}/auth/github/callback on that app. + See Configuration. +

- The Run CSPM rules button calls POST /v1/rules/run. That route - requires the API secret. Enter it in the API key field and choose - Save API key. The page stores it in localStorage.om_api_key and - sends X-API-Key on /v1 calls. The key is not written into the graph. - Clearing the field and saving removes it. The HTML page loads without a key. Stats, findings, - and the graph need the secret. GET /v1/health does not. + On an install with no admin, the API log prints a one-time setup link. Open that link and + finish sign-in. That account becomes the admin. Restarting the API before anyone uses the + link replaces it; use the latest line. An admin can create another setup link from the + Admins panel. The link expires after 72 hours. Every signed-in account is an + admin, and the last admin cannot be removed.

- Use the value of OM_API_SECRET from the environment the API process actually - has. Running om rules run on the CLI does the same work without the browser key, - because the CLI uses Postgres directly. + The browser keeps an httpOnly session cookie for 7 days. Collectors and curl + still send OM_API_SECRET. That secret cannot create or remove admins. +

+ +

Run CSPM rules from the browser

+

+ The Run CSPM rules button calls POST /v1/rules/run with the + session cookie. Running om rules run on the CLI does the same work without a + browser session, because the CLI uses Postgres directly.

Graph script

diff --git a/src/pages/docs/docker.astro b/src/pages/docs/docker.astro index f7f0981..3a7638d 100644 --- a/src/pages/docs/docker.astro +++ b/src/pages/docs/docker.astro @@ -21,7 +21,9 @@ docker compose ps`} Postgres listens on host port 5432 with user, password, and database opensourceom unless you override POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB. The API listens on OM_API_PORT (default 8080) and - receives OM_API_SECRET from .env. A volume named + receives OM_API_SECRET and the optional Google or GitHub client credentials from + .env. With no admin yet, docker compose logs api prints a one-time + setup link after you configure a provider. A volume named postgres-data keeps the database across restarts.

diff --git a/src/pages/docs/kubernetes.astro b/src/pages/docs/kubernetes.astro index 2a6f1e1..0558217 100644 --- a/src/pages/docs/kubernetes.astro +++ b/src/pages/docs/kubernetes.astro @@ -82,6 +82,21 @@ helm install om deploy/helm/opensourceom \\ empty, required OM_API_SECRET. + + api.publicURL + empty + OM_PUBLIC_URL. Set this to the browser origin before console login works. + + + api.google.clientId / api.google.clientSecret + empty + Google sign-in. Setting the id requires the secret. The secret stays in the chart Secret. + + + api.github.clientId / api.github.clientSecret + empty + GitHub sign-in. Same pattern as Google. + postgres.enabled true @@ -144,6 +159,7 @@ helm install om deploy/helm/opensourceom \\

Keep the Service off the public internet, or put your own gateway in front of it. See - Security for how OM_API_SECRET applies. + Security for the API secret and console login. Set + api.publicURL to that gateway’s origin when you enable Google or GitHub.

diff --git a/src/pages/docs/rules.astro b/src/pages/docs/rules.astro index dc62074..dbdd870 100644 --- a/src/pages/docs/rules.astro +++ b/src/pages/docs/rules.astro @@ -100,8 +100,8 @@ curl -s -X POST -H "Authorization: Bearer $OM_API_SECRET" \\ curl -s -X POST -H "Authorization: Bearer $OM_API_SECRET" \\ 'http://localhost:8080/v1/rules/run?id=cspm-internet-workload'`}

- The console button does the same POST. See Console for the - browser API key. + The console button does the same POST with the signed-in session. See + Console.

Adding a check

diff --git a/src/pages/docs/security.astro b/src/pages/docs/security.astro index d5bd773..03371fb 100644 --- a/src/pages/docs/security.astro +++ b/src/pages/docs/security.astro @@ -6,7 +6,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

OpenSourceOM security model

@@ -17,15 +17,18 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

API secret

OM_API_SECRET defaults to change-me-in-production when the variable - is unset or empty. Every /v1 route except GET /v1/health requires - Authorization: Bearer <secret> or X-API-Key: <secret>. - That includes graph reads, findings, the rules list, blast radius, ingest, rules run, and - Slack export. A missing or wrong secret is 401. + is unset or empty. Graph and ingest routes require + Authorization: Bearer <secret>, X-API-Key: <secret>, or + a console session cookie. That includes graph reads, findings, the rules list, blast radius, + ingest, rules run, and Slack export. A missing or wrong credential is 401.

- GET /v1/health does not check the secret, so probes can call it. The console - HTML at / and /ui/ is also unauthenticated. The page sends the key - from local storage on API calls. See Console. + GET /v1/health, GET /v1/auth/session, and + POST /v1/auth/logout do not check the secret. + GET /v1/admins, POST /v1/admins/invites, and + DELETE /v1/admins/{id} require a console session. The API secret cannot mint + admins. The console HTML at /, /login, and /setup/ + loads without a session so the sign-in page can render. See Console.

Change the secret before anyone else can reach the port. Helm refuses to install until @@ -33,8 +36,22 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; shell as a user with database credentials can scan and export without it.

- The secret is one shared key, not a user login. Do not publish the Service to the internet. - Put it on a private network or behind a gateway you operate. + The secret is the machine credential. People use Google or GitHub. Do not publish the + Service to the internet. Put it on a private network or behind a gateway you operate. +

+ +

Console admins

+

+ Google and GitHub are the only human logins. Every account is an admin. The first account + is whoever finishes the setup link printed in the API logs, so do not leave that link where + other people can open it. Later admins are whoever finishes a link an existing admin creates. + Sessions are httpOnly cookies. The cookie is marked Secure when + OM_PUBLIC_URL uses https. +

+

+ OAuth client secrets are deployment secrets, next to OM_API_SECRET. The API + must be able to reach Google or GitHub over HTTPS. An air-gapped console cannot sign anyone + in with this build.

Collectors

@@ -54,8 +71,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

Console script

The graph view loads vis-network from /ui/vis-network.min.js on the API. It is - not loaded from a CDN. The API key saved in the console stays in browser local storage and - is not written into the graph. + not loaded from a CDN. The session cookie is httpOnly, so page script cannot read it.

Reporting a vulnerability

From c68cd8bb5d5f13d187b2893b8bb124e5bac095c2 Mon Sep 17 00:00:00 2001 From: O M Date: Tue, 13 Oct 2026 19:19:08 -0400 Subject: [PATCH 2/4] Escape braces in the console login docs so the preview can prerender them. Co-authored-by: Cursor --- src/pages/docs/configuration.astro | 4 ++-- src/pages/docs/console.astro | 4 ++-- src/pages/docs/security.astro | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/docs/configuration.astro b/src/pages/docs/configuration.astro index 486353a..b15542e 100644 --- a/src/pages/docs/configuration.astro +++ b/src/pages/docs/configuration.astro @@ -60,12 +60,12 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; OM_GOOGLE_CLIENT_ID / OM_GOOGLE_CLIENT_SECRET empty - Google sign-in. Both are required. Callback {OM_PUBLIC_URL}/auth/google/callback. + Google sign-in. Both are required. Callback {'{OM_PUBLIC_URL}'}/auth/google/callback. OM_GITHUB_CLIENT_ID / OM_GITHUB_CLIENT_SECRET empty - GitHub sign-in. Both are required. Callback {OM_PUBLIC_URL}/auth/github/callback. + GitHub sign-in. Both are required. Callback {'{OM_PUBLIC_URL}'}/auth/github/callback. diff --git a/src/pages/docs/console.astro b/src/pages/docs/console.astro index 025b8fa..1324484 100644 --- a/src/pages/docs/console.astro +++ b/src/pages/docs/console.astro @@ -37,8 +37,8 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; The console signs people in with Google or GitHub. There is no password and no API-key field. Configure OM_GOOGLE_CLIENT_ID and OM_GOOGLE_CLIENT_SECRET, or the OM_GITHUB_* pair, and register - {OM_PUBLIC_URL}/auth/google/callback or - {OM_PUBLIC_URL}/auth/github/callback on that app. + {'{OM_PUBLIC_URL}'}/auth/google/callback or + {'{OM_PUBLIC_URL}'}/auth/github/callback on that app. See Configuration.

diff --git a/src/pages/docs/security.astro b/src/pages/docs/security.astro index 03371fb..6b57040 100644 --- a/src/pages/docs/security.astro +++ b/src/pages/docs/security.astro @@ -26,7 +26,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; GET /v1/health, GET /v1/auth/session, and POST /v1/auth/logout do not check the secret. GET /v1/admins, POST /v1/admins/invites, and - DELETE /v1/admins/{id} require a console session. The API secret cannot mint + DELETE /v1/admins/{'{id}'} require a console session. The API secret cannot mint admins. The console HTML at /, /login, and /setup/ loads without a session so the sign-in page can render. See Console.

From 3c9711340df5083a45ef95b50efa50cbba488644 Mon Sep 17 00:00:00 2001 From: O M Date: Tue, 13 Oct 2026 19:23:46 -0400 Subject: [PATCH 3/4] Document first-login admins, email invites, and personal API tokens. Co-authored-by: Cursor --- src/pages/docs/api.astro | 15 +++++----- src/pages/docs/cli.astro | 2 +- src/pages/docs/configuration.astro | 15 ++++++---- src/pages/docs/console.astro | 16 +++++----- src/pages/docs/docker.astro | 6 ++-- src/pages/docs/exports.astro | 2 +- src/pages/docs/index.astro | 2 +- src/pages/docs/kubernetes.astro | 11 ++++--- src/pages/docs/rules.astro | 7 +++-- src/pages/docs/security.astro | 47 ++++++++++++++++-------------- 10 files changed, 67 insertions(+), 56 deletions(-) diff --git a/src/pages/docs/api.astro b/src/pages/docs/api.astro index 173b8b6..8acd03b 100644 --- a/src/pages/docs/api.astro +++ b/src/pages/docs/api.astro @@ -15,8 +15,9 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; no GraphQL endpoint.

- Machine clients send OM_API_SECRET as Authorization: Bearer … or - X-API-Key. A console session cookie is the other accepted credential. + Machine clients send that admin's personal API token as Authorization: Bearer … or + X-API-Key. A console session cookie is the other accepted credential. The examples + below use $OM_API_TOKEN for the value the console showed once. GET /v1/health, GET /v1/auth/session, and POST /v1/auth/logout are open. Admin management requires the session. Treat the API as an internal service. Details are on Security @@ -35,7 +36,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; GET /v1/health - {`{"status":"ok","service":"opensourceom-api"}`}. Does not check the API secret. Helm probes this path. + {`{"status":"ok","service":"opensourceom-api"}`}. Does not check a token. Helm probes this path. GET /v1/graph/stats @@ -107,9 +108,9 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

{`curl -s http://localhost:8080/v1/health
-curl -s -H "Authorization: Bearer $OM_API_SECRET" \\
+curl -s -H "Authorization: Bearer $OM_API_TOKEN" \\
   http://localhost:8080/v1/graph/stats
-curl -s -H "Authorization: Bearer $OM_API_SECRET" \\
+curl -s -H "Authorization: Bearer $OM_API_TOKEN" \\
   -H 'Content-Type: application/json' \\
   -d '{"nodes":[],"edges":[]}' \\
   http://localhost:8080/v1/ingest`}
@@ -153,9 +154,9 @@ curl -s -H "Authorization: Bearer $OM_API_SECRET" \\ GET /v1/identity/blast-radius returns the identity, reachable (at most 200 nodes), max_depth (6), and summary. Pass identity_id or name. A name that matches no identity is 400. - This route requires the API secret, like the other /v1 reads. + This route requires a personal API token or a console session, like the other /v1 reads.

-
{`curl -s -X POST -H "Authorization: Bearer $OM_API_SECRET" \\
+  
{`curl -s -X POST -H "Authorization: Bearer $OM_API_TOKEN" \\
   -H 'Content-Type: application/json' \\
   -d '{"webhook":"https://hooks.slack.com/services/..."}' \\
   http://localhost:8080/v1/export/slack`}
diff --git a/src/pages/docs/cli.astro b/src/pages/docs/cli.astro index 5f913f9..92a4675 100644 --- a/src/pages/docs/cli.astro +++ b/src/pages/docs/cli.astro @@ -11,7 +11,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

OpenSourceOM CLI

The om binary talks to Postgres directly. Scans, rules, queries, and exports do - not go through the HTTP API, so they do not send OM_API_SECRET. Build it from + not go through the HTTP API, so they do not send a personal API token. Build it from the core repo with go build -o om ./cmd/om, or use a release archive.

diff --git a/src/pages/docs/configuration.astro b/src/pages/docs/configuration.astro index b15542e..3d924a7 100644 --- a/src/pages/docs/configuration.astro +++ b/src/pages/docs/configuration.astro @@ -6,7 +6,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

Configure OpenSourceOM

@@ -45,7 +45,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; OM_PUBLIC_URL http://localhost:8080 - Browser origin for OAuth callbacks and setup links. No trailing path. + Browser origin for OAuth callbacks and invite links. No trailing path. OM_API_PORT @@ -53,9 +53,14 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; om serve - OM_API_SECRET - change-me-in-production - Machine credential for /v1 routes. The console uses a session instead. See Security. + OM_SMTP_HOST / OM_SMTP_PORT / OM_SMTP_FROM + empty / 587 / empty + Required to email an admin invite. The message contains the link, not a token. + + + OM_SMTP_USERNAME / OM_SMTP_PASSWORD + empty + Optional SMTP authentication. Set both when the server requires a login. OM_GOOGLE_CLIENT_ID / OM_GOOGLE_CLIENT_SECRET diff --git a/src/pages/docs/console.astro b/src/pages/docs/console.astro index 1324484..dedfad5 100644 --- a/src/pages/docs/console.astro +++ b/src/pages/docs/console.astro @@ -42,15 +42,17 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; See Configuration.

- On an install with no admin, the API log prints a one-time setup link. Open that link and - finish sign-in. That account becomes the admin. Restarting the API before anyone uses the - link replaces it; use the latest line. An admin can create another setup link from the - Admins panel. The link expires after 72 hours. Every signed-in account is an - admin, and the last admin cannot be removed. + The first successful Google or GitHub sign-in becomes the admin. Keep the API port private + until that happens. An admin invites the next person by email from the + Admins panel. Sending an invite needs SMTP. The invited person opens the + link and signs in with the Google or GitHub account for that address. The invite expires + after 72 hours. Every signed-in account is an admin, and the last admin cannot be removed.

- The browser keeps an httpOnly session cookie for 7 days. Collectors and curl - still send OM_API_SECRET. That secret cannot create or remove admins. + The browser keeps an httpOnly session cookie for 7 days. Creating an admin also creates a + personal API token. The console shows it once. Scripts send that token as + Authorization: Bearer or X-API-Key. The token cannot invite or + remove admins. Rotate it from the same panel when you need a new one.

Run CSPM rules from the browser

diff --git a/src/pages/docs/docker.astro b/src/pages/docs/docker.astro index 3a7638d..9c9c165 100644 --- a/src/pages/docs/docker.astro +++ b/src/pages/docs/docker.astro @@ -21,9 +21,9 @@ docker compose ps`}
Postgres listens on host port 5432 with user, password, and database opensourceom unless you override POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB. The API listens on OM_API_PORT (default 8080) and - receives OM_API_SECRET and the optional Google or GitHub client credentials from - .env. With no admin yet, docker compose logs api prints a one-time - setup link after you configure a provider. A volume named + receives the optional Google or GitHub client credentials and SMTP settings from + .env. The first Google or GitHub sign-in becomes the admin. Keep port 8080 + private until that happens. A volume named postgres-data keeps the database across restarts.

diff --git a/src/pages/docs/exports.astro b/src/pages/docs/exports.astro index 73d8d0c..a6a1a4d 100644 --- a/src/pages/docs/exports.astro +++ b/src/pages/docs/exports.astro @@ -67,7 +67,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

The API posts the same digest:

-
{`curl -s -X POST -H "Authorization: Bearer $OM_API_SECRET" \\
+  
{`curl -s -X POST -H "Authorization: Bearer $OM_API_TOKEN" \\
   -H 'Content-Type: application/json' \\
   -d '{"webhook":"https://hooks.slack.com/services/..."}' \\
   http://localhost:8080/v1/export/slack`}
diff --git a/src/pages/docs/index.astro b/src/pages/docs/index.astro index 1c47e8f..a737507 100644 --- a/src/pages/docs/index.astro +++ b/src/pages/docs/index.astro @@ -78,7 +78,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro';
  • Architecture — components and data flow
  • Docker Compose — local Postgres and API
  • Kubernetes — Helm chart
  • -
  • Security — API secret, read-only collectors, vulnerability reports
  • +
  • Security — personal API tokens, read-only collectors, vulnerability reports
  • Open source scope — what stays in Core versus planned commercial work
  • diff --git a/src/pages/docs/kubernetes.astro b/src/pages/docs/kubernetes.astro index 0558217..5e1aa22 100644 --- a/src/pages/docs/kubernetes.astro +++ b/src/pages/docs/kubernetes.astro @@ -23,14 +23,13 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

    Install

    - api.secret and postgres.password are required. The image repository + postgres.password is required. The image repository defaults to ghcr.io/opensourceom/core. An empty image.tag falls back to the chart appVersion (0.3.0). Set the tag when you are not running that release.

    {`docker build -t ghcr.io/opensourceom/core:0.3.0 .
     helm install om deploy/helm/opensourceom \\
    -  --set api.secret='change-me' \\
       --set postgres.password='change-me' \\
       --set image.tag=0.3.0`}

    @@ -78,9 +77,9 @@ helm install om deploy/helm/opensourceom \\ Written to OM_ENV. - api.secret - empty, required - OM_API_SECRET. + api.smtp.host / api.smtp.port / api.smtp.from + empty / 587 / empty + SMTP for admin invites. Setting the host requires from. A password stays in the chart Secret. api.publicURL @@ -159,7 +158,7 @@ helm install om deploy/helm/opensourceom \\

    Keep the Service off the public internet, or put your own gateway in front of it. See - Security for the API secret and console login. Set + Security for personal API tokens and console login. Set api.publicURL to that gateway’s origin when you enable Google or GitHub.

    diff --git a/src/pages/docs/rules.astro b/src/pages/docs/rules.astro index dbdd870..0145b51 100644 --- a/src/pages/docs/rules.astro +++ b/src/pages/docs/rules.astro @@ -94,10 +94,11 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

    HTTP

    -
    {`curl -s http://localhost:8080/v1/rules
    -curl -s -X POST -H "Authorization: Bearer $OM_API_SECRET" \\
    +  
    {`curl -s -H "Authorization: Bearer $OM_API_TOKEN" \\
    +  http://localhost:8080/v1/rules
    +curl -s -X POST -H "Authorization: Bearer $OM_API_TOKEN" \\
       http://localhost:8080/v1/rules/run
    -curl -s -X POST -H "Authorization: Bearer $OM_API_SECRET" \\
    +curl -s -X POST -H "Authorization: Bearer $OM_API_TOKEN" \\
       'http://localhost:8080/v1/rules/run?id=cspm-internet-workload'`}

    The console button does the same POST with the signed-in session. See diff --git a/src/pages/docs/security.astro b/src/pages/docs/security.astro index 6b57040..bfdbe48 100644 --- a/src/pages/docs/security.astro +++ b/src/pages/docs/security.astro @@ -6,7 +6,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

    OpenSourceOM security model

    @@ -14,44 +14,47 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; IAM relationships, and findings. Protect Postgres and the API the way you protect that data.

    -

    API secret

    +

    Personal API tokens

    - OM_API_SECRET defaults to change-me-in-production when the variable - is unset or empty. Graph and ingest routes require - Authorization: Bearer <secret>, X-API-Key: <secret>, or + Each admin has a personal API token. The console shows the raw value once, after the account + is created or after a rotation. Graph and ingest routes require + Authorization: Bearer <token>, X-API-Key: <token>, or a console session cookie. That includes graph reads, findings, the rules list, blast radius, - ingest, rules run, and Slack export. A missing or wrong credential is 401. + ingest, rules run, and Slack export. A missing or wrong credential is 401. There is no shared + secret and no open mode.

    GET /v1/health, GET /v1/auth/session, and - POST /v1/auth/logout do not check the secret. - GET /v1/admins, POST /v1/admins/invites, and - DELETE /v1/admins/{'{id}'} require a console session. The API secret cannot mint - admins. The console HTML at /, /login, and /setup/ - loads without a session so the sign-in page can render. See Console. + POST /v1/auth/logout do not check a token. + GET /v1/admins, POST /v1/admins/invites, + POST /v1/admins/token, and + DELETE /v1/admins/{'{id}'} require a console session. A personal token cannot + mint or remove admins. The console HTML at /, /login, and + /invite/ loads without a session so the sign-in page can render. See + Console.

    - Change the secret before anyone else can reach the port. Helm refuses to install until - api.secret is set. The CLI never sends this header. Anyone who can open a local - shell as a user with database credentials can scan and export without it. + Keep the API port private until the first admin exists. Whoever can open it first can become + that admin. The CLI never sends this header. Anyone who can open a local shell as a user + with database credentials can scan and export without a token.

    - The secret is the machine credential. People use Google or GitHub. Do not publish the - Service to the internet. Put it on a private network or behind a gateway you operate. + People use Google or GitHub. Scripts use a personal token. Do not publish the Service to + the internet. Put it on a private network or behind a gateway you operate.

    Console admins

    - Google and GitHub are the only human logins. Every account is an admin. The first account - is whoever finishes the setup link printed in the API logs, so do not leave that link where - other people can open it. Later admins are whoever finishes a link an existing admin creates. + Google and GitHub are the only human logins. Every account is an admin. The first successful + sign-in becomes the admin. Later admins open an email invite and sign in with the Google or + GitHub account for the invited address. A verified email from the provider must match. Sessions are httpOnly cookies. The cookie is marked Secure when OM_PUBLIC_URL uses https.

    - OAuth client secrets are deployment secrets, next to OM_API_SECRET. The API - must be able to reach Google or GitHub over HTTPS. An air-gapped console cannot sign anyone - in with this build. + OAuth client secrets and the SMTP password are deployment secrets. The API must be able to + reach Google or GitHub over HTTPS, and your mail server when you invite another admin. An + air-gapped console cannot sign anyone in with this build.

    Collectors

    From 200459b4ea6b6fd92669cf5d9ef11c3476cd8af1 Mon Sep 17 00:00:00 2001 From: O M Date: Wed, 14 Oct 2026 13:31:31 -0400 Subject: [PATCH 4/4] Document email and password console login, with optional Google and GitHub. Co-authored-by: Cursor --- src/pages/docs/configuration.astro | 9 ++++++-- src/pages/docs/console.astro | 31 ++++++++++++++----------- src/pages/docs/docker.astro | 7 +++--- src/pages/docs/kubernetes.astro | 9 ++++++-- src/pages/docs/security.astro | 37 ++++++++++++++++-------------- 5 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/pages/docs/configuration.astro b/src/pages/docs/configuration.astro index 3d924a7..a872ace 100644 --- a/src/pages/docs/configuration.astro +++ b/src/pages/docs/configuration.astro @@ -45,17 +45,22 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; OM_PUBLIC_URL http://localhost:8080 - Browser origin for OAuth callbacks and invite links. No trailing path. + Browser origin for the console and optional OAuth callbacks. No trailing path. OM_API_PORT 8080 om serve + + OM_ADMIN_EMAIL + empty + When set, only this address can become the first admin. + OM_SMTP_HOST / OM_SMTP_PORT / OM_SMTP_FROM empty / 587 / empty - Required to email an admin invite. The message contains the link, not a token. + Optional. When set, adding an admin also emails the temporary password. OM_SMTP_USERNAME / OM_SMTP_PASSWORD diff --git a/src/pages/docs/console.astro b/src/pages/docs/console.astro index dedfad5..6c16fbb 100644 --- a/src/pages/docs/console.astro +++ b/src/pages/docs/console.astro @@ -34,25 +34,30 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

    Sign in

    - The console signs people in with Google or GitHub. There is no password and no API-key field. - Configure OM_GOOGLE_CLIENT_ID and OM_GOOGLE_CLIENT_SECRET, or the - OM_GITHUB_* pair, and register - {'{OM_PUBLIC_URL}'}/auth/google/callback or - {'{OM_PUBLIC_URL}'}/auth/github/callback on that app. - See Configuration. + The console signs people in with an email and password. There is no API-key field. + While no admin exists, the page is a setup form. Keep the API port private until you + finish it, or run om admin create --email you@example.com on the machine + first. Set OM_ADMIN_EMAIL when that first address should be fixed ahead of + time. See Configuration. +

    +

    + An admin adds the next person from the Admins panel. The temporary + password is shown once. If SMTP is configured, it is emailed too. That person chooses + their own password at the next sign-in. Every signed-in account is an admin, and the last + admin cannot be removed.

    - The first successful Google or GitHub sign-in becomes the admin. Keep the API port private - until that happens. An admin invites the next person by email from the - Admins panel. Sending an invite needs SMTP. The invited person opens the - link and signs in with the Google or GitHub account for that address. The invite expires - after 72 hours. Every signed-in account is an admin, and the last admin cannot be removed. + Google and GitHub buttons appear only when that provider is configured. Register + {'{OM_PUBLIC_URL}'}/auth/google/callback or + {'{OM_PUBLIC_URL}'}/auth/github/callback. A verified email from the provider + must already be an admin. The Security panel turns on optional + authenticator-app two-factor authentication.

    The browser keeps an httpOnly session cookie for 7 days. Creating an admin also creates a personal API token. The console shows it once. Scripts send that token as - Authorization: Bearer or X-API-Key. The token cannot invite or - remove admins. Rotate it from the same panel when you need a new one. + Authorization: Bearer or X-API-Key. The token cannot add or + remove admins. Rotate it from the Admins panel when you need a new one.

    Run CSPM rules from the browser

    diff --git a/src/pages/docs/docker.astro b/src/pages/docs/docker.astro index 9c9c165..9bf0586 100644 --- a/src/pages/docs/docker.astro +++ b/src/pages/docs/docker.astro @@ -21,9 +21,10 @@ docker compose ps`}
    Postgres listens on host port 5432 with user, password, and database opensourceom unless you override POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB. The API listens on OM_API_PORT (default 8080) and - receives the optional Google or GitHub client credentials and SMTP settings from - .env. The first Google or GitHub sign-in becomes the admin. Keep port 8080 - private until that happens. A volume named + receives OM_ADMIN_EMAIL and the optional Google, GitHub, and SMTP settings from + .env. Open the console and create the admin, or run + om admin create --email on the host first. Keep port 8080 private until an + admin exists. A volume named postgres-data keeps the database across restarts.

    diff --git a/src/pages/docs/kubernetes.astro b/src/pages/docs/kubernetes.astro index 5e1aa22..93e46c0 100644 --- a/src/pages/docs/kubernetes.astro +++ b/src/pages/docs/kubernetes.astro @@ -79,12 +79,17 @@ helm install om deploy/helm/opensourceom \\ api.smtp.host / api.smtp.port / api.smtp.from empty / 587 / empty - SMTP for admin invites. Setting the host requires from. A password stays in the chart Secret. + Optional SMTP for temporary admin passwords. Setting the host requires from. A password stays in the chart Secret. api.publicURL empty - OM_PUBLIC_URL. Set this to the browser origin before console login works. + OM_PUBLIC_URL. Set this to the browser origin before optional Google or GitHub login works. + + + api.adminEmail + empty + OM_ADMIN_EMAIL. When set, only this address can become the first admin. api.google.clientId / api.google.clientSecret diff --git a/src/pages/docs/security.astro b/src/pages/docs/security.astro index bfdbe48..7e3bad1 100644 --- a/src/pages/docs/security.astro +++ b/src/pages/docs/security.astro @@ -6,7 +6,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro';

    OpenSourceOM security model

    @@ -24,37 +24,40 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; secret and no open mode.

    - GET /v1/health, GET /v1/auth/session, and - POST /v1/auth/logout do not check a token. - GET /v1/admins, POST /v1/admins/invites, + GET /v1/health, GET /v1/auth/session, + POST /v1/auth/logout, POST /v1/auth/setup, + POST /v1/auth/login, and POST /v1/auth/totp do not require a session. + GET /v1/admins, POST /v1/admins, POST /v1/admins/token, and DELETE /v1/admins/{'{id}'} require a console session. A personal token cannot - mint or remove admins. The console HTML at /, /login, and - /invite/ loads without a session so the sign-in page can render. See - Console. + add or remove admins. The console HTML at / and /login loads + without a session so the sign-in page can render. See Console.

    Keep the API port private until the first admin exists. Whoever can open it first can become - that admin. The CLI never sends this header. Anyone who can open a local shell as a user - with database credentials can scan and export without a token. + that admin. Create the account with om admin create before you publish the + port when you can. The CLI never sends this header. Anyone who can open a local shell as a + user with database credentials can scan and export without a token.

    - People use Google or GitHub. Scripts use a personal token. Do not publish the Service to - the internet. Put it on a private network or behind a gateway you operate. + People use an email and password. Scripts use a personal token. Do not publish the Service + to the internet. Put it on a private network or behind a gateway you operate.

    Console admins

    - Google and GitHub are the only human logins. Every account is an admin. The first successful - sign-in becomes the admin. Later admins open an email invite and sign in with the Google or - GitHub account for the invited address. A verified email from the provider must match. + People sign in with an email and password. Every account is an admin. The first account is + created from the setup form, or with om admin create before the port is + reachable. OM_ADMIN_EMAIL pins that first address when you set it. Later admins + receive a temporary password and choose their own at the next sign-in. Passwords are stored + as Argon2id hashes. Optional authenticator-app two-factor authentication is per admin. Sessions are httpOnly cookies. The cookie is marked Secure when OM_PUBLIC_URL uses https.

    - OAuth client secrets and the SMTP password are deployment secrets. The API must be able to - reach Google or GitHub over HTTPS, and your mail server when you invite another admin. An - air-gapped console cannot sign anyone in with this build. + Google and GitHub are optional. A verified provider email must match an existing admin. + OAuth client secrets and the SMTP password are deployment secrets. Password login works + without them, including on an air-gapped console.

    Collectors