Skip to content

fix(gateway): require bearer auth for /v1/admin/* - #100

Merged
echobt merged 2 commits into
mainfrom
fix/gateway-admin-auth
Aug 8, 2026
Merged

fix(gateway): require bearer auth for /v1/admin/*#100
echobt merged 2 commits into
mainfrom
fix/gateway-admin-auth

Conversation

@echobt

@echobt echobt commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Require bearer auth for gateway /v1/admin/* (seal, backends, attest-grant) whenever BASE_GATEWAY_REQUIRE_OWNER=1, so the public listener is not an open admin plane.
  • Normalize challenge-proxy paths before is_admin_path / is_view_path so public /challenge/*/v1/./admin/* cannot bypass the master-local admin gate after reqwest collapses . segments.

Test plan

  • cargo test -p gateway --lib proxy::tests
  • Existing admin_auth / proxy tests
  • Spot-check from public Internet: GET /challenge/design/v1/./admin/rounds/1/candidates403 (not upstream 401)
  • Spot-check: GET /v1/admin/backends401/403 without bearer

Summary by CodeRabbit

  • New Features

    • Added optional Bearer-token authentication for administrative gateway endpoints.
    • Supports securely loading tokens from configuration or mounted secret files.
    • Updated sealing and backend-registration tools to authenticate automatically when configured.
  • Security

    • Administrative requests now return 401 Unauthorized when credentials are missing or invalid.
    • Owner-protected deployments fail closed without an administrator token.
    • Improved path normalization prevents URL-formatting bypasses.
  • Documentation

    • Added setup guidance for administrator tokens, deployment secrets, and security verification.

Public :80/:443 exposed unauthenticated seal and backend registry CRUD.
Fail-closed when REQUIRE_OWNER=1; wire token into seal/register scripts.
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The gateway now protects /v1/admin/* with configurable bearer authentication. It normalizes proxy paths, updates seal and registration clients to send tokens, and adds deployment configuration, secret handling, tests, and operator documentation.

Changes

Admin bearer authentication

Layer / File(s) Summary
Authentication policy and middleware
crates/gateway-core/Cargo.toml, crates/gateway-core/src/admin_auth.rs, crates/gateway-core/src/lib.rs, crates/gateway-core/src/admin_attest.rs
AdminAuth loads inline or file-based tokens, enforces owner-mode configuration, validates Bearer credentials, and returns JSON 401 responses.
Protected path normalization
crates/gateway/src/proxy.rs
Proxy classification resolves empty, dot, duplicate-slash, and parent path segments before checking admin and viewer routes.
Gateway integration and route tests
crates/gateway/src/lib.rs, crates/gateway/tests/admin_auth.rs
Both gateway builders apply the authentication middleware. Integration tests cover open mode, rejected requests, valid tokens, and unaffected non-admin routes.
Admin token propagation
bins/weights-smoke/src/main.rs, deploy/scripts/prod-burn-seal.sh, deploy/scripts/prod-real-seal.sh, deploy/scripts/register-challenge-backends.sh
CLI and deployment scripts resolve configured tokens and conditionally send Bearer authorization headers for admin requests.
Deployment configuration and operator guidance
deploy/compose/env-prod.yml, deploy/compose/env-staging.yml, deploy/env/gateway.env.example, deploy/secrets/README.md, docs/OPERATOR_SECURITY.md, AGENTS.md
Deployment files mount the admin token secret and document token configuration, ownership, permissions, and security checks.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant Gateway
  participant AdminAuth
  participant AdminEndpoint
  Operator->>Gateway: Send /v1/admin/* with Authorization header
  Gateway->>AdminAuth: Validate bearer token
  AdminAuth-->>Gateway: Allow or reject request
  Gateway->>AdminEndpoint: Forward authenticated request
  AdminEndpoint-->>Operator: Return admin response
Loading

Possibly related PRs

  • BaseIntelligence/base#71: Both PRs modify deploy/scripts/prod-real-seal.sh; this PR adds bearer-token authorization to its gateway seal requests.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: requiring Bearer authentication for gateway admin routes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gateway-admin-auth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Normalize ./ // .. before is_admin_path so public /challenge/*/v1/./admin/*
cannot bypass the master-local admin gate after reqwest collapses the path.
@echobt
echobt merged commit 1e688ad into main Aug 8, 2026
2 of 3 checks passed
@echobt
echobt deleted the fix/gateway-admin-auth branch August 8, 2026 19:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
crates/gateway-core/src/admin_auth.rs (2)

47-48: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Accept mixed-case truthy values for BASE_GATEWAY_REQUIRE_OWNER.

The match list covers 1, true, TRUE, yes, and YES. A value such as True or Yes disables the fail-closed check. The gateway then starts with open /v1/admin/* routes. Compare case-insensitively.

♻️ Proposed case-insensitive parse
         let require_owner = std::env::var(REQUIRE_OWNER_ENV)
-            .is_ok_and(|v| matches!(v.trim(), "1" | "true" | "TRUE" | "yes" | "YES"));
+            .is_ok_and(|v| {
+                let v = v.trim();
+                v == "1" || v.eq_ignore_ascii_case("true") || v.eq_ignore_ascii_case("yes")
+            });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/gateway-core/src/admin_auth.rs` around lines 47 - 48, Update the
require_owner parsing in the environment-variable initialization to compare
truthy values case-insensitively, so mixed-case variants such as “True” and
“Yes” are accepted alongside “1”. Preserve trimming and the existing fail-closed
behavior for all other values.

143-147: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Parse the Bearer scheme case-insensitively.

RFC 7235 defines the authentication scheme token as case-insensitive. The current code accepts only Bearer and bearer . A client that sends BEARER <token> receives 401. Match the scheme without case sensitivity.

♻️ Proposed scheme parse
 fn bearer_from_headers(headers: &axum::http::HeaderMap) -> Option<String> {
     let raw = headers.get(header::AUTHORIZATION)?.to_str().ok()?;
-    let token = raw
-        .strip_prefix("Bearer ")
-        .or_else(|| raw.strip_prefix("bearer "))?;
+    let (scheme, token) = raw.split_once(' ')?;
+    if !scheme.eq_ignore_ascii_case("bearer") {
+        return None;
+    }
     let token = token.trim();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/gateway-core/src/admin_auth.rs` around lines 143 - 147, Update
bearer_from_headers to parse the Authorization scheme case-insensitively,
accepting BEARER and any other casing while preserving the existing token
extraction and None behavior for invalid headers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deploy/secrets/README.md`:
- Around line 22-26: Update the token-generation commands in the README to
create a mode-0600 temporary file, apply UID 65532 ownership and final mode 0400
before publication, then atomically rename it to gateway_admin_token; avoid
redirecting directly into the runtime secret path.

In `@docs/OPERATOR_SECURITY.md`:
- Line 56: Update the deployment guidance in OPERATOR_SECURITY.md so staging and
production require BASE_GATEWAY_ADMIN_TOKEN_FILE, with the token supplied
through age-encrypted files under deploy/env/ or deploy/secrets/. Restrict
BASE_GATEWAY_ADMIN_TOKEN to local tests only, and preserve the requirement that
an owner-protected gateway cannot expose /v1/admin/* without a configured token.

---

Nitpick comments:
In `@crates/gateway-core/src/admin_auth.rs`:
- Around line 47-48: Update the require_owner parsing in the
environment-variable initialization to compare truthy values case-insensitively,
so mixed-case variants such as “True” and “Yes” are accepted alongside “1”.
Preserve trimming and the existing fail-closed behavior for all other values.
- Around line 143-147: Update bearer_from_headers to parse the Authorization
scheme case-insensitively, accepting BEARER and any other casing while
preserving the existing token extraction and None behavior for invalid headers.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4033b048-afa4-4103-8fdb-2d578f692955

📥 Commits

Reviewing files that changed from the base of the PR and between 528f6c7 and e8e1bae.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • AGENTS.md
  • bins/weights-smoke/src/main.rs
  • crates/gateway-core/Cargo.toml
  • crates/gateway-core/src/admin_attest.rs
  • crates/gateway-core/src/admin_auth.rs
  • crates/gateway-core/src/lib.rs
  • crates/gateway/src/lib.rs
  • crates/gateway/src/proxy.rs
  • crates/gateway/tests/admin_auth.rs
  • deploy/compose/env-prod.yml
  • deploy/compose/env-staging.yml
  • deploy/env/gateway.env.example
  • deploy/scripts/prod-burn-seal.sh
  • deploy/scripts/prod-real-seal.sh
  • deploy/scripts/register-challenge-backends.sh
  • deploy/secrets/README.md
  • docs/OPERATOR_SECURITY.md

Comment thread deploy/secrets/README.md
Comment on lines +22 to +26
```bash
# Generate once per environment; never commit the bytes.
openssl rand -hex 32 > deploy/secrets/gateway_admin_token
chown 65532:65532 deploy/secrets/gateway_admin_token
chmod 0400 deploy/secrets/gateway_admin_token

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Create the token with restrictive permissions from the start.

The redirection creates deploy/secrets/gateway_admin_token before chown and chmod. With a permissive umask, another local user can read the token during this window. Generate a mode-0600 temporary file, secure it, and atomically rename it into place.

Proposed fix
-# Generate once per environment; never commit the bytes.
-openssl rand -hex 32 > deploy/secrets/gateway_admin_token
-chown 65532:65532 deploy/secrets/gateway_admin_token
-chmod 0400 deploy/secrets/gateway_admin_token
+# Generate once per environment; never commit the bytes.
+umask 077
+tmp="$(mktemp deploy/secrets/gateway_admin_token.XXXXXX)"
+trap 'rm -f "$tmp"' EXIT
+openssl rand -hex 32 >"$tmp"
+chown 65532:65532 "$tmp"
+chmod 0400 "$tmp"
+mv -f "$tmp" deploy/secrets/gateway_admin_token

As per coding guidelines, runtime secret files must use mode 0400 and be owned by UID 65532.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```bash
# Generate once per environment; never commit the bytes.
openssl rand -hex 32 > deploy/secrets/gateway_admin_token
chown 65532:65532 deploy/secrets/gateway_admin_token
chmod 0400 deploy/secrets/gateway_admin_token
# Generate once per environment; never commit the bytes.
umask 077
tmp="$(mktemp deploy/secrets/gateway_admin_token.XXXXXX)"
trap 'rm -f "$tmp"' EXIT
openssl rand -hex 32 >"$tmp"
chown 65532:65532 "$tmp"
chmod 0400 "$tmp"
mv -f "$tmp" deploy/secrets/gateway_admin_token
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/secrets/README.md` around lines 22 - 26, Update the token-generation
commands in the README to create a mode-0600 temporary file, apply UID 65532
ownership and final mode 0400 before publication, then atomically rename it to
gateway_admin_token; avoid redirecting directly into the runtime secret path.

Source: Coding guidelines

Comment thread docs/OPERATOR_SECURITY.md
## 4. Gateway and TLS

- [ ] Gateway hotkey equals on-chain `SubnetOwnerHotkey` (else process exits 2).
- [ ] `BASE_GATEWAY_ADMIN_TOKEN_FILE` (or `BASE_GATEWAY_ADMIN_TOKEN`) is set whenever `BASE_GATEWAY_REQUIRE_OWNER=1` — `/v1/admin/*` must not be open on a public listener.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Require the file-based token in deployed environments.

BASE_GATEWAY_ADMIN_TOKEN places the bearer token in the process environment. The deployment guidance requires secrets through age-encrypted files under deploy/env/ or deploy/secrets/. Require BASE_GATEWAY_ADMIN_TOKEN_FILE for staging and production, and limit the direct environment form to local tests.

As per coding guidelines, secrets must use age-encrypted files under deploy/env/ or deploy/secrets/.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/OPERATOR_SECURITY.md` at line 56, Update the deployment guidance in
OPERATOR_SECURITY.md so staging and production require
BASE_GATEWAY_ADMIN_TOKEN_FILE, with the token supplied through age-encrypted
files under deploy/env/ or deploy/secrets/. Restrict BASE_GATEWAY_ADMIN_TOKEN to
local tests only, and preserve the requirement that an owner-protected gateway
cannot expose /v1/admin/* without a configured token.

Source: Coding guidelines

echobt added a commit that referenced this pull request Aug 8, 2026
After #100, POST /v1/admin/backends requires Authorization: Bearer. remote-deploy's inline reseed omitted it and staging master CI failed 401.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant