quorate/docs/spec/forgejo-deployment-spec.md

24 KiB

Forgejo Deployment Specification

1. Scope

This document specifies every Forgejo deployment in the cluster. Two repository authorities are implemented by three frontend groups.

Authority Frontend groups Role
Internal Forgejo One private root Private development, operations, and Flux GitOps origin
Public Forgejo Anonymous and authenticated roots Public project development and release origin

2. Authority and Topology

flowchart LR
    Operators[Operators and Flux]
    Internet[Internet]
    Trusted[LAN or WireGuard]
    Internal[Internal frontend]
    Facade[Envoy anonymous facade]
    Code[Public anonymous frontend]
    Git[Public authenticated frontend]
    InternalState[(Internal Forgejo state)]
    PublicState[(Public Forgejo state)]

    Operators --> Internal --> InternalState
    Internet -->|code.quorate.dev| Facade --> Code --> PublicState
    Internet -->|git.quorate.dev| Git --> PublicState
    Trusted -->|git-ssh.quorate.internal| Git
    InternalState -. sanitized quorate tip .-> PublicState
Relationship Contract
Internal ↔ public authority No shared application state or runtime credential
Public anonymous ↔ authenticated roots Two roots share one public-forge application state
Repository ↔ authority Exactly one authoritative forge per repository
Backup ↔ authority Backups are recovery artifacts, not alternate Git remotes

3. Common Deployment Contract

3.1 Best-Effort HA Profile

Forgejo does not provide a coordinated multi-replica HA mode. The deployment uses shared state and removes replica-local workers so interchangeable frontends can serve the same authority.

Component Durable HA contract
Forgejo frontend group Multiple replicas run on distinct eligible nodes
Anonymous Envoy facade Multiple replicas run on distinct eligible nodes
Rollout A replacement settles and becomes ready before an old replica is removed
Voluntary disruption Disruption policy preserves a ready replica for each group
Insufficient placement capacity Availability requirements take precedence over co-locating redundant replicas
Manifest-owned tuning Replica counts, resource requests and limits, rollout thresholds, and probe timing
Shared state Placement HA purpose
Git repositories and SSH host material One Longhorn RWX claim per authority Every replica reads and writes the same Git namespace
Users, permissions, and repository metadata Dedicated database and role in CloudNativePG No replica-local metadata authority
Sessions, cache, and queue Dedicated Dragonfly instance and ACL per authority Shared sessions, cache invalidation, and queued work
Attachments and avatars Dedicated private VersityGW bucket and principal per authority No replica-local blob state
Public repository archives Dedicated public-forge object prefix Either public root can retrieve a generated archive
Runtime secrets Authority-scoped Vault projections Every replica in one authority receives compatible application material
Failure or rollout condition Contract
One frontend pod fails Another ready replica continues serving that group
One public frontend group stops The other group continues its permitted operations against shared state
Voluntary disruption Disruption policy protects a ready replica; it does not protect against involuntary node failure
Missing spread capacity Redundant replicas do not collapse onto one node merely to satisfy replica count
Rolling update A replacement becomes settled and ready before an old replica is removed
Shared dependency failure All frontends using that authority may fail together
In-flight queued operation loss Automatic takeover is not guaranteed; a client or controller must be able to retry

The HA claim covers redundant stateless frontends. It does not claim seamless failover for every in-flight operation or remove the availability boundaries of the RWX volume, PostgreSQL, Dragonfly, VersityGW, DNS, or ingress path.

3.2 Security Profile

Control Requirement
Process identity Non-root user and group
Privilege No privilege escalation; all capabilities dropped except an explicitly required capability
Root filesystem Read-only
Database transport PostgreSQL TLS required
Internal trust Dedicated internal CA projection for private service TLS
Network Default-deny Cilium policy with explicit ingress and dependency egress

3.3 Common Feature Profile

Area Required state
Repository units Code and releases enabled
Disabled units Issues, pull requests, wikis, projects, packages, Actions, and external trackers
Repository migration Disabled
Repository indexer Disabled to avoid divergent replica-local indexes
Forgejo cron Disabled to avoid independently scheduled duplicate work
Forgejo Actions Disabled to keep execution in the external workflow plane
Native mirrors Disabled to avoid replica-local workers and schedules
Metrics endpoint Disabled

Argo Workflows owns CI, release orchestration, scheduled reconciliation, and publication automation. Forgejo-native workers remain disabled to avoid duplicate execution across replicas.

4. Internal Authority

4.1 Access and Purpose

Surface Hostname Tier Use
HTTPS forge.quorate.internal I Authenticated browser and API access
SSH forge-ssh.quorate.internal D / C Operator Git and in-cluster Flux fetches
Property Contract
Repository classes Private, operational, and GitOps repositories
Flux origin Internal Forgejo is authoritative for the cluster configuration repository
Anonymous access Sign-in required
Interactive identity Invite-only Zitadel OIDC
SSH clients Private CIDRs and explicitly admitted in-cluster identities
Internet exposure None
Runtime Default container runtime; not a Tier O or Tier R application backend

4.2 State Boundary

Dependency Internal allocation
PostgreSQL Dedicated internal-forge database and role
Dragonfly Dedicated internal-forge instance and ACL
Git repositories Dedicated RWX claim
Object storage Dedicated private bucket and principal
Vault Dedicated application subtree and projected secrets
Zitadel Dedicated confidential OIDC application

5. Public Authority

5.1 Repository Authority

Repository class Sole authority Publication contract
Private and operational repositories Internal Forgejo Absent from public Forgejo
Inherently public projects Public Forgejo Full public history; developed and released in place
quorate Internal Forgejo Sanitized, history-free snapshot of the current tip
Transition Requirement
Internal → public Review the complete history and current tree before transfer
Public → internal Treat all previously public objects as irretractable
Routine cross-forge transfer Only the quorate snapshot
flowchart LR
    Tree[Approved quorate export tree]
    Orphan[Construct orphan commit]
    Scan{Secret scan clean?}
    Reject[Fail closed]
    Publish[Force-push public branch]

    Tree --> Orphan --> Scan
    Scan -->|No| Reject
    Scan -->|Yes| Publish

The published branch has a one-commit history, no internal ancestry, and no tags. Its public commit identity is not stable across exports.

5.2 Ingress Boundaries

Property Anonymous root Authenticated root Trusted SSH
Hostname code.quorate.dev git.quorate.dev git-ssh.quorate.internal
Tier O R D
Termination Cloudflare TLS Gateway R TLS SSH transport
Path Cloudflare Tunnel → Envoy OCI relay → WireGuard → Gateway R Private LoadBalancer
Supported use Browse, public API reads, downloads, clone, fetch OIDC, authenticated API, push, releases Git SSH
Secret-bearing workflow None OIDC codes, sessions, API tokens, HTTPS Git credentials SSH keys and sessions
Frontend Anonymous root through Envoy only Authenticated root Authenticated root
Internet publication HTTPS HTTPS None

Split-horizon DNS for git.quorate.dev may select the private Gateway R address, but preserves the hostname, certificate identity, cookie origin, and OIDC callback.

5.3 Anonymous Envoy Facade

Boundary Requirement
Authorization Default deny by normalized method and path
Path handling Normalize paths, merge slashes, and reject ambiguous escaped separators before policy evaluation
Admitted operations Only qualified anonymous browser, API, archive, release, and smart-HTTP fetch operations
Denied operations Login, OIDC, account, administration, notifications, repository mutation, git-receive-pack, and unclassified methods
Request stripping Credentials, cookies, proxy authentication, signature credentials, and trusted reverse-proxy identity headers
Response stripping Session headers and authentication challenges
Query credentials Rejected
Logging No request paths or credential values
Network policy Tunnel identity → facade only; facade → anonymous Forgejo HTTP only
Bypass No alternate anonymous route to either Forgejo frontend

The facade remains mandatory because distinct browser roots do not partition database-backed access tokens or HTTPS Git credentials shared by the public frontends.

5.4 Frontend and Session Isolation

Property Anonymous root Authenticated root
Canonical root https://code.quorate.dev/ https://git.quorate.dev/
Interactive login Absent Invite-only Zitadel OIDC
First login Not applicable Automatic provisioning for an authorized Zitadel identity
SSH listener Disabled Internal-only endpoint
Session cookie Distinct, secure, host-only Distinct, secure, host-only
Session-store keys Distinct prefix Distinct prefix
Generated configuration Private ephemeral volume Private ephemeral volume
Shared public state Isolated per frontend
PostgreSQL data Root URL and generated configuration
Git repository volume Session cookie name and store prefix
Cache and queue OIDC source and callbacks
Object storage Exposed network path
Application cryptographic material

Generated configuration never resides on the shared repository volume. Each root emits its own hostname in canonical links, clone URLs, API pagination, redirects, releases, attachments, and archives.

5.5 Public Feature Profile

Area Required state
Anonymous use Repository and organization exploration, source, commits, branches, tags, releases, downloads, archives, feeds, sitemap, public API reads, HTTPS clone, and fetch
Maintainer use OIDC, authenticated API, HTTPS push, trusted SSH, repository administration, and release publication
Repository creation Administrator-created and public by default
Disabled creation paths Regular-user creation, push-to-create, migration, and forks
Search Bounded repository-local Git search; global code search disabled
Git and artifacts HTTPS, source archives, and release attachments enabled; LFS and packages disabled
API Public and authenticated API enabled; Swagger and query-string tokens disabled
Webhooks Disabled unless restricted to an exact in-cluster Argo Events receiver by application and network policy

A newly enabled feature remains denied on Tier O until its routes, methods, resource bounds, and credential behavior pass the facade acceptance suite.

5.6 Public State and Runtime

Dependency Public allocation Isolation from internal Forgejo
PostgreSQL Dedicated public-forge database and role Database and role sharing prohibited
Dragonfly Dedicated public-forge instance and ACL Instance, ACL, and credential sharing prohibited
Git repositories Dedicated RWX claim Volume sharing prohibited
Object storage Dedicated private bucket, principal, and prefixes Bucket, prefix, principal, and credential sharing prohibited
Vault Dedicated subtree and projected secrets Runtime-secret sharing prohibited
Kubernetes Dedicated namespace and service account API authority is not inherited from internal Forgejo
Zitadel Dedicated confidential OIDC application Client and callback sharing prohibited
Runtime control Requirement
Namespace classification security.quorate.internal/public-runtime: gvisor
Runtime Explicit gvisor RuntimeClass for Forgejo, facade, init, upgrade, and setup pods
Admission Reject a public backend outside a labeled namespace and any non-gVisor pod inside one
Service accounts No Kubernetes API token without a documented need
Git hooks Disabled
Images Reviewed immutable digests
Public storage bootstrap Requirement
New RWX volume Create data directories with ownership compatible with the rootless Forgejo process before either frontend starts
Generated configuration Keep it off the shared volume; bootstrap creates storage structure only

A public-forge compromise must not provide a data path or credential path into internal Forgejo.

6. Change and Verification Contract

Change or event Required verification before acceptance
Any Forgejo upgrade Effective configuration, startup, Git read/write paths, release paths, dependency access, and replica replacement
Internal ingress or identity change Sign-in requirement, Zitadel login, Flux fetch, private SSH admission, and absence of Internet exposure
Forgejo or Envoy change affecting Tier O Complete admitted method and route inventory; clean reproducible facade suite
Facade change Anonymous positives; authenticated denials; credential stripping; malformed, encoded, and query bypass rejection
Public network-policy change Tunnel-to-facade path succeeds; direct tunnel-to-Forgejo path fails
Public frontend change Distinct roots, private configuration, host-only disjoint sessions, correct generated links, and cross-root visibility
Dependency or replica restart Affected frontends recover without divergent state
New publication class Repository-specific clone, fetch, archive, release, and cross-root update checks
quorate publication One orphan snapshot, no tags, no internal ancestry, and no secret-scan findings