ce7cb702218693d08ea6d83464d2457c9250027c
This commit is contained in:
commit
34c5f8963c
1 changed files with 145336 additions and 0 deletions
272
docs/spec/forgejo-deployment-spec.md
Normal file
272
docs/spec/forgejo-deployment-spec.md
Normal 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 |
|
||||
Loading…
Reference in a new issue