diff --git a/src/pages/docs/api.astro b/src/pages/docs/api.astro index aa437cf..8acd03b 100644 --- a/src/pages/docs/api.astro +++ b/src/pages/docs/api.astro @@ -15,10 +15,13 @@ 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 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 + and Console.

Reads

@@ -33,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 @@ -105,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`}
@@ -151,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/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/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 0a90d7e..a872ace 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 - Public URL hint. Requests use the listen address. + Browser origin for the console and optional OAuth callbacks. No trailing path. OM_API_PORT @@ -53,9 +53,29 @@ import DocsLayout from '@/layouts/DocsLayout.astro'; om serve - OM_API_SECRET - change-me-in-production - Shared secret for every /v1 route except GET /v1/health. See Security. + 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 + Optional. When set, adding an admin also emails the temporary password. + + + 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 + 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..6c16fbb 100644 --- a/src/pages/docs/console.astro +++ b/src/pages/docs/console.astro @@ -32,19 +32,39 @@ 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 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 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. + 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.

- 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. 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 add or + remove admins. Rotate it from the Admins panel when you need a new one. +

+ +

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..9bf0586 100644 --- a/src/pages/docs/docker.astro +++ b/src/pages/docs/docker.astro @@ -21,7 +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 OM_API_SECRET from .env. 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/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 2a6f1e1..93e46c0 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,29 @@ 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 + 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 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 + 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 @@ -144,6 +163,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 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 dc62074..0145b51 100644 --- a/src/pages/docs/rules.astro +++ b/src/pages/docs/rules.astro @@ -94,14 +94,15 @@ 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. 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..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

    @@ -14,27 +14,50 @@ 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. 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. + 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. There is no shared + secret and no open mode.

    - 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, + 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 + add or remove admins. The console HTML at / and /login 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. 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.

    - 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. + 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

    +

    + 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. +

    +

    + 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

    @@ -54,8 +77,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