feat(gitea): require sign-in to read anything; Flux and CI authenticate first #175

Open
gitea-admin wants to merge 1 commits from feat/gitea-require-signin into main
pull from: feat/gitea-require-signin
Owner

DO NOT MERGE until operator steps 1–3 below are done

This PR makes gitea.<domain> require a login to read anything. The operator decided this on
2026-11-14. Gitea stays public, but anonymous crawlers have kept the pod at 2–6 cores since #156.

Mechanism. The fix uses Gitea's own setting rather than an oauth2-proxy gate.
GITEA__service__REQUIRE_SIGNIN_VIEW=true is the strict mode. It refuses anonymous web pages, API
calls, git clone/fetch and container-registry pulls. People sign in through the existing Keycloak
OIDC source. An oauth2-proxy gate would break git, API, registry and runner clients, which need a
401 rather than a 302. robots.txt (Disallow: /) is written by the postStart hook as defence in
depth.

Files

  • catalog/gitea.yaml and the re-rendered apps/wave-0/gitea.yaml: the env var and the robots.txt
    write.
  • clusters/prod/kustomization.yaml: the flux-system GitRepository gets
    secretRef: flux-gitea-read. This Secret is out-of-band, like sops-age.
  • .gitea/workflows/gate.yml and .gitea/workflows/cve-triage.yaml: the toolbox job image was
    pulled anonymously from the Gitea registry. It now uses container.credentials with the
    existing RENOVATE_TOKEN, whose read:package scope renovate.yaml's preflight proves.
  • decisions/2026-11-14-gitea-require-signin.md: the full anonymous-reader inventory, operator
    steps and gaps.

Anonymous readers found (live, read-only)

Reader Fix
Flux GitRepository: anonymous, no secretRef secretRef. Needs a NEW read token (operator)
gate.yml job image pull credentials: RENOVATE_TOKEN (this PR)
cve-triage.yaml job image pull credentials: RENOVATE_TOKEN (this PR)
conformance in-cluster clone dormant. Its conformance-git-readonly Secret becomes mandatory once wired

Already authenticated, so unaffected: Renovate, pm-agent (token and pull secret), images.yaml
(push token; PRs build the base in-run), the actions/checkout job token, the act_runner
registration, and workstations. vuln-scanner treats the Gitea registry as private and never pulls
from it. There are zero image-automation objects, no registries.yaml, and no probes or
ServiceMonitor on Gitea.

Operator steps, in order

  1. Mint a Gitea token with scope read:repository only, on a non-admin account (a dedicated
    flux-reader, or renovate-bot).
  2. Create flux-system/flux-gitea-read with keys username and password (the token), out-of-band.
    The exact commands are in the handoff file.
  3. Verify the token with git ls-remote, and check the Secret's key names (commands in the handoff).
  4. Post /approve-override <reason> as a PR review. This PR touches the sensitive paths
    clusters/** and .gitea/**. The agent did not and will not post it.
  5. Merge, then confirm the GitRepository is Ready on the merge SHA. Anonymous API calls should then
    get 401/403, and /robots.txt should return 200.

If step 2 is missed, the source stalls with "secret not found", and that blocks any revert too.
Recovery is step 2 itself.

Spec conflicts (the spec repo was not modified)

  • ADR-0027 rejects a CVE-findings issue because "the repository is publicly readable". That
    premise is now false for anonymous readers.
  • docs/15, R-IDP-06: auth.groups: [platform-admins] is not required by the Gitea auth source.
    It only maps that group to admin. Any homelab-realm user can sign in and read. This matches the
    operator's "Keycloak login", but enforcing the group would need --required-claim-* on the
    source. Not done here (lock-out risk). It is the architect's or operator's call.
  • docs/05 and docs/09 link to the impl repo publicly; those links now land on a login page.

Gate (toolbox, from the worktree, on committed HEAD b855b6b)

rc 3, UNDETERMINED. The gate printed: Stages that did not run: gitea-settings.
spec-immutability, lint, l0, render-golden, l1 and decisions-log passed. L2/L3 conformance SKIPPED
(no cluster). approval-policy is N/A offline and will be red in CI until the override is
posted
.

Not verified

Nothing is deployed. The Gitea behaviour was checked against upstream main source, not the 28.3.0
tag. Two auth paths are unproven. On a warm DinD cache the job-image credentials auth is not
exercised. The Flux token does not exist yet. Whether the robots.txt write succeeds is not observed.
Whether crawler CPU actually drops is not observed. Details are in the handoff.

🤖 Generated with Claude Code

## DO NOT MERGE until operator steps 1–3 below are done This PR makes `gitea.<domain>` require a login to read anything. The operator decided this on 2026-11-14. Gitea stays public, but anonymous crawlers have kept the pod at 2–6 cores since #156. **Mechanism.** The fix uses Gitea's own setting rather than an oauth2-proxy gate. `GITEA__service__REQUIRE_SIGNIN_VIEW=true` is the strict mode. It refuses anonymous web pages, API calls, git clone/fetch and container-registry pulls. People sign in through the existing Keycloak OIDC source. An oauth2-proxy gate would break git, API, registry and runner clients, which need a 401 rather than a 302. `robots.txt` (`Disallow: /`) is written by the postStart hook as defence in depth. ### Files - `catalog/gitea.yaml` and the re-rendered `apps/wave-0/gitea.yaml`: the env var and the robots.txt write. - `clusters/prod/kustomization.yaml`: the `flux-system` GitRepository gets `secretRef: flux-gitea-read`. This Secret is **out-of-band**, like `sops-age`. - `.gitea/workflows/gate.yml` and `.gitea/workflows/cve-triage.yaml`: the toolbox job image was pulled **anonymously** from the Gitea registry. It now uses `container.credentials` with the existing `RENOVATE_TOKEN`, whose `read:package` scope renovate.yaml's preflight proves. - `decisions/2026-11-14-gitea-require-signin.md`: the full anonymous-reader inventory, operator steps and gaps. ### Anonymous readers found (live, read-only) | Reader | Fix | |---|---| | Flux GitRepository: anonymous, no secretRef | `secretRef`. **Needs a NEW read token (operator)** | | gate.yml job image pull | `credentials:` RENOVATE_TOKEN (this PR) | | cve-triage.yaml job image pull | `credentials:` RENOVATE_TOKEN (this PR) | | conformance in-cluster clone | dormant. Its `conformance-git-readonly` Secret becomes mandatory once wired | Already authenticated, so unaffected: Renovate, pm-agent (token and pull secret), images.yaml (push token; PRs build the base in-run), the actions/checkout job token, the act_runner registration, and workstations. vuln-scanner treats the Gitea registry as private and never pulls from it. There are zero image-automation objects, no `registries.yaml`, and no probes or ServiceMonitor on Gitea. ### Operator steps, in order 1. Mint a Gitea token with scope **`read:repository` only**, on a non-admin account (a dedicated `flux-reader`, or `renovate-bot`). 2. Create `flux-system/flux-gitea-read` with keys `username` and `password` (the token), out-of-band. The exact commands are in the handoff file. 3. Verify the token with `git ls-remote`, and check the Secret's key names (commands in the handoff). 4. Post `/approve-override <reason>` **as a PR review**. This PR touches the sensitive paths `clusters/**` and `.gitea/**`. The agent did not and will not post it. 5. Merge, then confirm the GitRepository is Ready on the merge SHA. Anonymous API calls should then get 401/403, and `/robots.txt` should return 200. If step 2 is missed, the source stalls with "secret not found", and that blocks any revert too. Recovery is step 2 itself. ### Spec conflicts (the spec repo was not modified) - **ADR-0027** rejects a CVE-findings issue because "the repository is publicly readable". That premise is now false for anonymous readers. - **docs/15, R-IDP-06**: `auth.groups: [platform-admins]` is not *required* by the Gitea auth source. It only maps that group to admin. Any homelab-realm user can sign in and read. This matches the operator's "Keycloak login", but enforcing the group would need `--required-claim-*` on the source. Not done here (lock-out risk). It is the architect's or operator's call. - **docs/05 and docs/09** link to the impl repo publicly; those links now land on a login page. ### Gate (toolbox, from the worktree, on committed HEAD b855b6b) **rc 3, UNDETERMINED.** The gate printed: `Stages that did not run: gitea-settings`. spec-immutability, lint, l0, render-golden, l1 and decisions-log passed. L2/L3 conformance SKIPPED (no cluster). approval-policy is N/A offline and **will be red in CI until the override is posted**. ### Not verified Nothing is deployed. The Gitea behaviour was checked against upstream `main` source, not the 28.3.0 tag. Two auth paths are unproven. On a warm DinD cache the job-image `credentials` auth is not exercised. The Flux token does not exist yet. Whether the robots.txt write succeeds is not observed. Whether crawler CPU actually drops is not observed. Details are in the handoff. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gitea-admin added 1 commit 2026-11-14 20:18:23 +00:00
feat(gitea): require sign-in to read anything; Flux and CI authenticate first
gate / gate (pull_request) Failing after 59s
gate / conformance (pull_request) Skipped
b855b6b294
Gitea stays public on the Cloudflare Tunnel, but anonymous crawlers have kept
the pod at 2-6 cores since PR #156. Operator decision 2026-11-14: reading
anything requires a (Keycloak) login.

- catalog/gitea.yaml (+ re-rendered apps/wave-0/gitea.yaml):
  GITEA__service__REQUIRE_SIGNIN_VIEW=true (strict: web, API, git pull,
  registry /v2/token), and a postStart-written robots.txt (Disallow: /).
- clusters/prod: flux-system GitRepository gets secretRef flux-gitea-read,
  an OUT-OF-BAND read:repository token the operator must create BEFORE merge.
- gate.yml / cve-triage.yaml: the toolbox job image was pulled anonymously
  from the Gitea registry; it now uses container.credentials with the
  existing RENOVATE_TOKEN (read:package, proven by renovate.yaml preflight).

Anonymous-reader inventory and operator steps:
decisions/2026-11-14-gitea-require-signin.md

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Some required checks failed
gate / gate (pull_request) Failing after 59s
Required
Details
gate / conformance (pull_request) Skipped
Some required checks were not successful.
You are not authorized to merge this pull request.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/gitea-require-signin:feat/gitea-require-signin
git checkout feat/gitea-require-signin
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Home-Network/home-network-k3s-impl#175