IPCR Forge: bridge 1.0.0 and DAC_READ_SEARCH so IPCR can import from Gitea
Found on a fresh install (demostaging1): the Forge pinned bridge 0.1.0, which predates IPCR_AUTH_FILE, so IPCR's read-only Gitea token was never written. And ipcr-gateway (root, cap_drop ALL) could not read the token anyway: it is 0600, owned by $PUID, in a 0700 folder. Bump both bridge refs to 1.0.0 and give the gateway back DAC_READ_SEARCH (read-only), documented in rationale.md.
This commit is contained in:
@@ -331,7 +331,7 @@ services:
|
|||||||
# Verbatim from Apps/Radicle-Gitea-Bridge; only its data folder moves to $AppID/bridge/.
|
# Verbatim from Apps/Radicle-Gitea-Bridge; only its data folder moves to $AppID/bridge/.
|
||||||
|
|
||||||
radicle-gitea-bridge:
|
radicle-gitea-bridge:
|
||||||
image: ghcr.io/worph/radicle-gitea-bridge:0.1.0
|
image: ghcr.io/worph/radicle-gitea-bridge:1.0.0
|
||||||
container_name: radicle-gitea-bridge
|
container_name: radicle-gitea-bridge
|
||||||
user: $PUID:$PGID
|
user: $PUID:$PGID
|
||||||
# Reads two HTTP APIs and runs git. Needs no capability at all.
|
# Reads two HTTP APIs and runs git. Needs no capability at all.
|
||||||
@@ -386,6 +386,11 @@ services:
|
|||||||
user: "0:0"
|
user: "0:0"
|
||||||
# Root needs no capabilities to write root-owned files it owns; drop them all.
|
# Root needs no capabilities to write root-owned files it owns; drop them all.
|
||||||
cap_drop: [ALL]
|
cap_drop: [ALL]
|
||||||
|
# Forge only: one back, read-only. The import token below is written by bridge-credentials as
|
||||||
|
# $PUID, mode 0600, in a 0700 folder; a root with no capabilities is bound by those modes like
|
||||||
|
# anyone else (verified: "permission denied"). DAC_READ_SEARCH bypasses read and search checks
|
||||||
|
# only, never write, and only on what this container mounts. See rationale.md.
|
||||||
|
cap_add: [DAC_READ_SEARCH]
|
||||||
security_opt: ["no-new-privileges:true"]
|
security_opt: ["no-new-privileges:true"]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["gateway"]
|
command: ["gateway"]
|
||||||
@@ -757,7 +762,7 @@ x-compose-app:
|
|||||||
chmod 600 /runner/token
|
chmod 600 /runner/token
|
||||||
when: absent:/DATA/AppData/$AppID/gitea/runner/.runner
|
when: absent:/DATA/AppData/$AppID/gitea/runner/.runner
|
||||||
- name: bridge-credentials
|
- name: bridge-credentials
|
||||||
image: ghcr.io/worph/radicle-gitea-bridge:0.1.0
|
image: ghcr.io/worph/radicle-gitea-bridge:1.0.0
|
||||||
user: $PUID:$PGID
|
user: $PUID:$PGID
|
||||||
phase: post_up
|
phase: post_up
|
||||||
network: $APP_NET
|
network: $APP_NET
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ bundle adds.
|
|||||||
3. **A public landing page.** `ipcr-forge-<domain>` is not behind the SSO gate.
|
3. **A public landing page.** `ipcr-forge-<domain>` is not behind the SSO gate.
|
||||||
4. **It replaces four apps** and cannot be installed beside them: same container names,
|
4. **It replaces four apps** and cannot be installed beside them: same container names,
|
||||||
domains and host ports.
|
domains and host ports.
|
||||||
|
5. **One added capability.** `ipcr-gateway` gets `cap_add: [DAC_READ_SEARCH]` on top of the
|
||||||
|
standalone listing's `cap_drop: [ALL]`.
|
||||||
|
|
||||||
## Why it is necessary
|
## Why it is necessary
|
||||||
|
|
||||||
@@ -55,8 +57,21 @@ Renaming every container, domain and port to coexist would break the defaults th
|
|||||||
and IPCR rely on (`radicle-api:8080`, `gitea:3000`) and the documented addresses users type,
|
and IPCR rely on (`radicle-api:8080`, `gitea:3000`) and the documented addresses users type,
|
||||||
for no use case: running two copies of the same forge on one server.
|
for no use case: running two copies of the same forge on one server.
|
||||||
|
|
||||||
|
**5. `DAC_READ_SEARCH`.** The gateway runs as root (for the CA it drops into the host's Docker
|
||||||
|
trust store) with every capability dropped, and a root with no capabilities is bound by file
|
||||||
|
modes like any other user. The import token is written by the bridge's install step as `$PUID`,
|
||||||
|
mode `0600`, in a `0700` folder, so without the capability the gateway cannot open it (verified
|
||||||
|
on a fresh install: `open /data/secrets/registry-auth: permission denied`, then `401` from
|
||||||
|
Gitea). `DAC_READ_SEARCH` restores exactly the missing half: reading files and traversing
|
||||||
|
directories regardless of their modes.
|
||||||
|
|
||||||
## Security mitigations in place
|
## Security mitigations in place
|
||||||
|
|
||||||
|
- **The added capability is read-only and narrow.** `DAC_READ_SEARCH` bypasses read and
|
||||||
|
directory-search checks only. It grants no write, ownership or privilege change, and reaches
|
||||||
|
only what the gateway mounts: its own TLS folder and state, the token (mounted read-only) and
|
||||||
|
the Docker trust store it already writes to.
|
||||||
|
|
||||||
- **The network link is narrow.** `gitea-internal` holds only `gitea`, `gitea-runner` and now
|
- **The network link is narrow.** `gitea-internal` holds only `gitea`, `gitea-runner` and now
|
||||||
`ipcr-gateway`. The gateway's only listener is the read-only registry API. Kubo's
|
`ipcr-gateway`. The gateway's only listener is the read-only registry API. Kubo's
|
||||||
unauthenticated RPC API stays on `ipcr-internal`, which nothing from Gitea joins. Jobs run
|
unauthenticated RPC API stays on `ipcr-internal`, which nothing from Gitea joins. Jobs run
|
||||||
|
|||||||
Reference in New Issue
Block a user