ce7cb702218693d08ea6d83464d2457c9250027c

This commit is contained in:
ms2k 2026-11-12 20:46:28 +09:00
commit 34c5f8963c

View file

@ -0,0 +1,272 @@
# 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
```mermaid
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 |
```mermaid
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 |