docs: the server runs Gitea 1.27.2, not 1.24.2; say what it actually honours #61
@@ -1,18 +1,13 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
# IMPORTANT — the currently inventoried server is Gitea 1.24.2, which *ignores* several keys used
|
# The server runs Gitea 1.27.2 (docs/development/security.md, "What this server honours"):
|
||||||
# below. Per https://docs.gitea.com/1.24/usage/actions/comparison, its unsupported workflow syntax
|
|
||||||
# includes `permissions` / `jobs.<job_id>.permissions`, `concurrency`, and
|
|
||||||
# `jobs.<job_id>.timeout-minutes`. They are declared here deliberately, so the workflow is already
|
|
||||||
# correct on a supported release and on GitHub Actions, but on this server they are inert:
|
|
||||||
#
|
#
|
||||||
# * `permissions:` does NOT restrict the in-job `GITEA_TOKEN`. Job-token permission controls,
|
# * `concurrency:` is honoured: a newer push to a pull request cancels the run it supersedes, so a
|
||||||
# repository/owner defaults, and server-side maximum-permission clamping arrive in the 1.26
|
# cancelled run's own cleanup is never guaranteed (see the CI stack sweep in each stack job).
|
||||||
# feature line (https://docs.gitea.com/1.26/usage/actions/token-permissions). Until the server
|
# * `permissions:` is supported by 1.27 with Gitea's own scopes, but what the job token can do on
|
||||||
# is upgraded and configured, the token's authority is whatever the server grants — not what
|
# this server is not yet proven, so nothing here relies on it being read-only.
|
||||||
# this file requests.
|
# * `timeout-minutes:` is no longer listed as unsupported, but is not verified here either;
|
||||||
# * `concurrency:` and `timeout-minutes:` are NOT bounded-execution guarantees here. Cancellation,
|
# teardown relies on the stack sweep and the runner's own reaper.
|
||||||
# timeout, and teardown must be enforced by the trusted runner provisioner/reaper.
|
|
||||||
#
|
#
|
||||||
# `persist-credentials: false` below is unaffected by any of this and is enforced by the action
|
# `persist-credentials: false` below is unaffected by any of this and is enforced by the action
|
||||||
# itself. See docs/development/security.md.
|
# itself. See docs/development/security.md.
|
||||||
|
|||||||
@@ -218,9 +218,10 @@ undeployed state, and the launch checklist in a single place. **CI is accepted a
|
|||||||
trusted-code-only boundary**: the self-hosted Gitea runner executes only code controlled and
|
trusted-code-only boundary**: the self-hosted Gitea runner executes only code controlled and
|
||||||
reviewed by trusted repository collaborators, and unknown or untrusted pull requests must not
|
reviewed by trusted repository collaborators, and unknown or untrusted pull requests must not
|
||||||
execute on it automatically. What remains is operational and cannot be proven from this repository —
|
execute on it automatically. What remains is operational and cannot be proven from this repository —
|
||||||
upgrading Gitea past 1.24.2 (which *ignores* the workflow's `permissions`, `concurrency`, and
|
configuring and proving job-token authority server-side (the server now runs Gitea 1.27.2, which
|
||||||
`timeout-minutes` declarations), clamping job-token authority server-side, enforcing timeout and
|
honours `concurrency` and documents `permissions`, but neither token limits nor `timeout-minutes` are
|
||||||
teardown in a trusted provisioner/reaper, and proving disposable runner isolation. Those are
|
verified on it yet), enforcing timeout and teardown in a trusted provisioner/reaper, and proving
|
||||||
|
disposable runner isolation. Those are
|
||||||
prerequisites for **executing untrusted contributions**, alongside the deployment checklist that
|
prerequisites for **executing untrusted contributions**, alongside the deployment checklist that
|
||||||
gates a **public launch**; moving future public contribution workflows to a service with isolated
|
gates a **public launch**; moving future public contribution workflows to a service with isolated
|
||||||
hosted runners is an acceptable alternative. **Neither set of requirements blocks Phase 6** — Surface
|
hosted runners is an acceptable alternative. **Neither set of requirements blocks Phase 6** — Surface
|
||||||
|
|||||||
@@ -163,27 +163,28 @@ It references no secret, needs no registry credential (the harness image is publ
|
|||||||
organization), and uses no `pull_request_target`. The trusted-code-only rule for self-hosted runners
|
organization), and uses no `pull_request_target`. The trusted-code-only rule for self-hosted runners
|
||||||
applies to module repositories exactly as it does to the platform (see `docs/development/security.md`).
|
applies to module repositories exactly as it does to the platform (see `docs/development/security.md`).
|
||||||
|
|
||||||
**Why `docker run`, and not a reusable workflow or a composite action.** The inventoried server is
|
**Why `docker run`, and not a reusable workflow or a composite action.** The server runs Gitea
|
||||||
Gitea 1.24.2.
|
1.27.2. (This proposal first assumed 1.24.2; the reasons below hold on the actual version.)
|
||||||
|
|
||||||
- Its runner does support calling a reusable workflow from another repository, by cloning that
|
- Its runner can call a reusable workflow from another repository by cloning that repository, and
|
||||||
repository. That support is thinly documented, and has known problems in practice.
|
since 1.26 that includes private repositories (go-gitea/gitea#32562). That support is still thinly
|
||||||
- Actions and reusable workflows from **private** repositories only arrive with Gitea 1.26
|
documented, and has known problems in practice.
|
||||||
(go-gitea/gitea#32562).
|
- A reusable workflow would make the harness's behaviour depend on how this server and runner
|
||||||
|
version expand it. An image runs the same everywhere it runs.
|
||||||
- Composite actions from another repository work through absolute `uses:` URLs. That would still
|
- Composite actions from another repository work through absolute `uses:` URLs. That would still
|
||||||
wrap the same `docker run`, adding a second pinned reference and nothing else.
|
wrap the same `docker run`, adding a second pinned reference and nothing else.
|
||||||
|
|
||||||
An image digest is the one reference that is equally immutable, equally reviewable, and works
|
An image digest is the one reference that is equally immutable, equally reviewable, and works
|
||||||
unchanged on Gitea 1.24, Gitea 1.26, and GitHub Actions. A composite action can be added later as a
|
unchanged on any Gitea release and on GitHub Actions. A composite action can be added later as a
|
||||||
convenience over the same image, without changing what a module pins.
|
convenience over the same image, without changing what a module pins.
|
||||||
|
|
||||||
### Nothing the harness starts outlives the job that started it
|
### Nothing the harness starts outlives the job that started it
|
||||||
|
|
||||||
The harness starts Docker stacks on a shared runner, and a runner can kill a job outright: a
|
The harness starts Docker stacks on a shared runner, and a runner can kill a job outright: a
|
||||||
cancelled job, or the runner's own time limit, since Gitea 1.24 ignores `timeout-minutes`. A script's
|
cancelled or superseded job (`concurrency` cancels superseded runs on this server), or the runner's
|
||||||
own cleanup trap does not run then. The platform's CI leaked a whole migration stack that way, and it
|
own time limit. A script's own cleanup trap does not run then. The platform's CI leaked a whole
|
||||||
ran until someone removed it by hand. So the harness follows the platform's rule
|
migration stack that way, and it ran until someone removed it by hand. So the harness follows the
|
||||||
(`scripts/ci-sweep.sh`, `docs/development/security.md`):
|
platform's rule (`scripts/ci-sweep.sh`, `docs/development/security.md`):
|
||||||
|
|
||||||
- everything it starts is named and labeled as a CI stack of its run, attempt, and job key;
|
- everything it starts is named and labeled as a CI stack of its run, attempt, and job key;
|
||||||
- it removes its own stacks in a trap;
|
- it removes its own stacks in a trap;
|
||||||
@@ -233,9 +234,9 @@ The registry is reached exactly as the instance's publish job reaches it.
|
|||||||
|
|
||||||
- **Each module keeps its own copy of the smoke scripts.** Rejected. That is today's situation in
|
- **Each module keeps its own copy of the smoke scripts.** Rejected. That is today's situation in
|
||||||
embryo, and it drifts.
|
embryo, and it drifts.
|
||||||
- **A reusable workflow in the platform repository.** Rejected for now, for the Gitea 1.24 reasons
|
- **A reusable workflow in the platform repository.** Rejected for now, for the reasons above: thin
|
||||||
above. It would also give the harness's behaviour to whichever runner and server version happens
|
support in practice, and a harness whose behaviour depends on whichever runner and server version
|
||||||
to expand it.
|
happens to expand it.
|
||||||
- **Publish the harness as an npm package.** Rejected. The harness needs Docker, a browser, and the
|
- **Publish the harness as an npm package.** Rejected. The harness needs Docker, a browser, and the
|
||||||
platform's deployment files anyway, so a package would ship a fraction of it and assume the rest.
|
platform's deployment files anyway, so a package would ship a fraction of it and assume the rest.
|
||||||
- **Test only the newest platform version.** Rejected. A module's range floor would go untested
|
- **Test only the newest platform version.** Rejected. A module's range floor would go untested
|
||||||
|
|||||||
@@ -693,8 +693,10 @@ public Dashboard login must be assessed with the actual edge/proxy controls befo
|
|||||||
|
|
||||||
## Continuous integration boundary
|
## Continuous integration boundary
|
||||||
|
|
||||||
CI runs on self-hosted Gitea Actions runners. The inventoried server is **Gitea 1.24.2**, and that
|
CI runs on self-hosted Gitea Actions runners. The server reports **Gitea 1.27.2** (`GET
|
||||||
version determines which parts of the workflow are real guarantees and which are not.
|
/api/v1/version`, checked 2026-10-06; this document previously said 1.24.2, which was stale). The
|
||||||
|
server's actual version, not the documentation of an earlier one, determines which parts of the
|
||||||
|
workflow are real guarantees and which are not.
|
||||||
|
|
||||||
### The current operating rule: trusted code only
|
### The current operating rule: trusted code only
|
||||||
|
|
||||||
@@ -727,24 +729,26 @@ so a later step in the same job cannot read or reuse it from there.
|
|||||||
It does **not** remove the short-lived `GITEA_TOKEN` from the job environment, and it does **not**
|
It does **not** remove the short-lived `GITEA_TOKEN` from the job environment, and it does **not**
|
||||||
reduce that token's server-side authority. A step that wants the token can still read it.
|
reduce that token's server-side authority. A step that wants the token can still read it.
|
||||||
|
|
||||||
### What Gitea 1.24 ignores — so the branch cannot claim it
|
### What this server honours, and what is not yet verified
|
||||||
|
|
||||||
Per the official comparison for this version
|
Each item separates what Gitea documents for 1.27
|
||||||
(<https://docs.gitea.com/1.24/usage/actions/comparison>), Gitea 1.24's unsupported workflow syntax
|
(<https://docs.gitea.com/1.27/usage/actions/comparison>) from what has been observed on this server:
|
||||||
includes `permissions` and `jobs.<job_id>.permissions`, `concurrency`, and
|
|
||||||
`jobs.<job_id>.timeout-minutes`. On this server those declarations are inert. Therefore:
|
|
||||||
|
|
||||||
- **The workflow's `permissions: contents: read` does not restrict the job token.** This branch
|
- **`concurrency` is honoured.** Gitea supports it since 1.26 (go-gitea/gitea#32751), and on this
|
||||||
cannot claim the `GITEA_TOKEN` is read-only, because nothing in workflow YAML makes it so on
|
server a newer push to a pull request cancels the run it supersedes. Observed: Ekklesia run 133 was
|
||||||
1.24. Job-token permission controls, repository/owner defaults, and server-side
|
cancelled when run 134 started for the same pull request, and run 135 when run 137 did. A
|
||||||
maximum-permission clamping arrive in the 1.26 feature line
|
superseded run's own cleanup is therefore never guaranteed to run, which is one reason for the
|
||||||
(<https://docs.gitea.com/1.26/usage/actions/token-permissions>).
|
stack sweep below.
|
||||||
- **`concurrency` and `timeout-minutes` are not bounded-execution guarantees here.** Cancellation,
|
- **`permissions` is supported by 1.27, with Gitea's own scopes, but is not verified here.** The
|
||||||
timeout, and teardown must be enforced by a trusted provisioner and reaper outside the workflow.
|
server-side token-permission settings are not visible through the API, and nothing has yet proven
|
||||||
|
what the job token can and cannot do on this server. Until that proof exists (requirement 3
|
||||||
|
below), this repository still does not claim the `GITEA_TOKEN` is read-only.
|
||||||
|
- **`timeout-minutes` is no longer listed as unsupported for 1.27, but is not verified here.**
|
||||||
|
Teardown does not rely on it: the stack sweep and a trusted reaper do.
|
||||||
|
- **Still ignored:** `jobs.<job_id>.environment`, and any `runs-on` other than a single label.
|
||||||
|
|
||||||
The declarations are kept deliberately: they are already correct for a supported Gitea release and
|
The declarations stay in the workflow either way: they state the intent, and are correct on GitHub
|
||||||
for GitHub Actions, and keeping them means the upgrade does not require re-deriving the intent. They
|
Actions as well.
|
||||||
are documented as ignored, not removed.
|
|
||||||
|
|
||||||
### Disposable stacks never outlive their job
|
### Disposable stacks never outlive their job
|
||||||
|
|
||||||
@@ -793,16 +797,17 @@ they are not conditions on merging trusted, reviewed work.
|
|||||||
None of them can be satisfied by editing this repository; each requires access to the Gitea server
|
None of them can be satisfied by editing this repository; each requires access to the Gitea server
|
||||||
and runner hosts, and each must be proven there.
|
and runner hosts, and each must be proven there.
|
||||||
|
|
||||||
1. Upgrade Gitea to a reviewed supported release with job-token permission controls — at least the
|
1. ~~Upgrade Gitea to a reviewed supported release with job-token permission controls — at least
|
||||||
1.26 feature line. Select the actual release during infrastructure work, after backup and
|
the 1.26 feature line.~~ **Done:** the server runs 1.27.2. Items 2 and 3 now configure and prove
|
||||||
compatibility review; do not pin a version from this document.
|
what it makes possible.
|
||||||
2. Configure repository and owner defaults, and the server-side maximum permission, so workflow
|
2. Configure repository and owner defaults, and the server-side maximum permission, so workflow
|
||||||
code cannot request more than read-only repository access.
|
code cannot request more than read-only repository access.
|
||||||
3. Prove against the real server that the job's `GITEA_TOKEN` cannot push code, create or update
|
3. Prove against the real server that the job's `GITEA_TOKEN` cannot push code, create or update
|
||||||
releases, packages, issues, or pull requests, change Actions configuration, or reach another
|
releases, packages, issues, or pull requests, change Actions configuration, or reach another
|
||||||
private repository.
|
private repository.
|
||||||
4. Enforce job timeout, cancellation, concurrency, and teardown in the trusted
|
4. Enforce job timeout and teardown in the trusted provisioner/reaper. `concurrency` is honoured
|
||||||
provisioner/reaper rather than relying on the ignored workflow keys above.
|
by the server; `timeout-minutes` is not yet verified, and the in-workflow stack sweep only
|
||||||
|
complements a reaper.
|
||||||
5. Complete a disposable-guest runner isolation proof: one throwaway guest per job, torn down by a
|
5. Complete a disposable-guest runner isolation proof: one throwaway guest per job, torn down by a
|
||||||
trusted reaper outside workflow YAML, holding no deployment credential, registration token, or
|
trusted reaper outside workflow YAML, holding no deployment credential, registration token, or
|
||||||
reusable key.
|
reusable key.
|
||||||
|
|||||||
+4
-3
@@ -6,9 +6,10 @@
|
|||||||
# bash scripts/ci-sweep.sh --job this job's own stacks, at any age
|
# bash scripts/ci-sweep.sh --job this job's own stacks, at any age
|
||||||
#
|
#
|
||||||
# Every CI script already removes its own stack in an EXIT trap, but a trap never runs when the runner
|
# Every CI script already removes its own stack in an EXIT trap, but a trap never runs when the runner
|
||||||
# kills the job container: a cancelled job, or the runner's own time limit (Gitea 1.24 ignores
|
# kills the job container: a cancelled or superseded job (the server honours `concurrency`), or
|
||||||
# `timeout-minutes`). So each workflow job that starts stacks ends with `--job` in an `if: always()`
|
# the runner's own time limit. So each workflow job that starts stacks ends with `--job` in an
|
||||||
# step, and begins with `--stale`, which catches whatever a killed job left before it could clean up.
|
# `if: always()` step, and begins with `--stale`, which catches whatever a killed job left before
|
||||||
|
# it could clean up.
|
||||||
#
|
#
|
||||||
# What a stack is, and how it is found, is defined in scripts/lib/ci-project.sh: a Compose project
|
# What a stack is, and how it is found, is defined in scripts/lib/ci-project.sh: a Compose project
|
||||||
# named with the `ekklesia-ci-` prefix (Compose labels everything it creates with that name), plus
|
# named with the `ekklesia-ci-` prefix (Compose labels everything it creates with that name), plus
|
||||||
|
|||||||
Reference in New Issue
Block a user