diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 458bb67e0a..261aece1d1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,6 +67,14 @@ jobs: bump-dev-version: needs: publish if: ${{ inputs.dry-run != true }} + # A reusable-workflow CALL cannot grant the callee more than the calling job holds, + # and GitHub refuses the whole run at startup when the called workflow's own job + # declares permissions the caller did not pass down ("startup_failure", runs + # 33615174183 / 33615177849 — the first dispatches since #3129 wired this call). + # The callee's job declares exactly these two; nothing else in this file gains them. + permissions: + contents: write + pull-requests: write uses: ./.github/workflows/dev-version-bump.yml with: released-version: v${{ inputs.version }} diff --git a/docs-site/src/content/docs/guides/remote-hub.md b/docs-site/src/content/docs/guides/remote-hub.md index 16ef574ab1..d36bda0704 100644 --- a/docs-site/src/content/docs/guides/remote-hub.md +++ b/docs-site/src/content/docs/guides/remote-hub.md @@ -77,7 +77,7 @@ ocx config set hostname 100.64.0.10 ocx config set hub.managementPublicOrigin '"https://hub-name.tailnet-name.ts.net"' ocx config set corsAllowOrigins '["http://localhost:10100"]' ocx config set hub.managementIngress '{"enabled":true,"port":10101}' -ocx config set remoteGui.allowedTailscaleUsers '["operator@example.com"]' +ocx config set remoteGui.allowedTailscaleUsers '[]' # Generate/read this in a protected operator shell or secret manager. # It is a data-admission token, not a provider credential. @@ -115,7 +115,14 @@ tailscale serve status Set `hub.managementPublicOrigin` to the exact HTTPS origin shown by Serve. Add the operator's exact Tailscale login to `remoteGui.allowedTailscaleUsers`; an empty list means no remote identity can mint -a session. Verify both directions: +a session. Enable that allowlist only when Tailscale Serve is the process forwarding to the +management ingress: + +```bash +ocx config set remoteGui.allowedTailscaleUsers '["operator@example.com"]' +``` + +Verify both directions: ```bash # Negative: the loopback-only port must not be reachable through the node's tailnet address. @@ -138,8 +145,10 @@ tailscale cert hub-name.tailnet-name.ts.net ``` Protect the private key, renew it through Tailscale's supported mechanism, and proxy only to -`127.0.0.1:10101`. A generic TLS proxy does not supply trustworthy Tailscale identity. Do not -fabricate `Tailscale-User-*` headers; use the single-use, origin-bound pairing flow instead. +`127.0.0.1:10101`. A generic TLS proxy does not supply trustworthy Tailscale identity. Keep +`remoteGui.allowedTailscaleUsers` empty, and configure the proxy to remove or reject every incoming +`Tailscale-User-*` header before forwarding. Preserving client-supplied identity headers is unsafe; +do not merely avoid adding new ones. Use the single-use, origin-bound pairing flow instead. ## Headless OAuth @@ -231,14 +240,16 @@ docker compose run --rm hub bun run src/cli/index.ts config set runtimeRole hub docker compose run --rm hub bun run src/cli/index.ts config set hostname 0.0.0.0 docker compose run --rm hub bun run src/cli/index.ts config set hub.managementPublicOrigin '"https://hub-name.tailnet-name.ts.net"' docker compose run --rm hub bun run src/cli/index.ts config set hub.managementIngress '{"enabled":true,"port":10101}' -docker compose run --rm hub bun run src/cli/index.ts config set remoteGui.allowedTailscaleUsers '["operator@example.com"]' +docker compose run --rm hub bun run src/cli/index.ts config set remoteGui.allowedTailscaleUsers '[]' docker compose up -d ``` Do not put a token in `ARG`, `ENV`, `COPY`, Compose YAML, image history, or the command line. Do not mount the Docker socket, host home, Codex home, SSH agent, or provider-key files. Publish only port `10100`. A management ingress bound to `127.0.0.1:10101` inside the container is reachable only by a -TLS/tailnet frontend in the same network namespace; never publish `10101` as a shortcut. +TLS/tailnet frontend in the same network namespace; never publish `10101` as a shortcut. Keep the +allowlist empty and use pairing for a generic frontend. If a Tailscale Serve sidecar is the process +forwarding to `10101`, set the exact operator allowlist only after that topology is in place. After the container is healthy, run a separate readiness promotion check: diff --git a/structure/05_gui-and-management-api.md b/structure/05_gui-and-management-api.md index ff75b15e88..2b8468f620 100644 --- a/structure/05_gui-and-management-api.md +++ b/structure/05_gui-and-management-api.md @@ -70,9 +70,10 @@ Tailscale identity headers authorize session issuance only when the request arri listener and the exact login appears in `remoteGui.allowedTailscaleUsers`. The public listener and the unauthenticated data-loopback listener always pass `trustedTailscaleIngress: false`, regardless of `Host`, `Origin`, `Forwarded`, `X-Forwarded-*`, or `Tailscale-User-*` values. A generic TLS proxy -cannot establish that identity and uses the existing single-use, digest-only, origin-bound pairing -exchange. Pairing accepts no admin/data credential substitute and consumes a grant only after the -full origin predicate succeeds. +cannot establish that identity: its deployment must keep the Tailscale allowlist empty, remove or +reject every client-supplied `Tailscale-User-*` header, and use the existing single-use, digest-only, +origin-bound pairing exchange. Pairing accepts no admin/data credential substitute and consumes a +grant only after the full origin predicate succeeds. The server issues a local in-memory session for five minutes or a remote session for twelve hours, with 128 live sessions maximum. Every session is bound to the exact server and browser origins;