Prepare private dispatcher queue on Kubernetes fsGroup volumes
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
This commit is contained in:
parent
c69c4c6f2c
commit
1321361d85
4 changed files with 81 additions and 5 deletions
|
|
@ -65,3 +65,9 @@ Live observation: State Hub's API Service selects app=state-hub. This worker
|
|||
does not carry that label, so it cannot enter the API endpoints. At inspection
|
||||
there were no other State Hub NetworkPolicies; recheck effective selection before
|
||||
activation. Its own policy allows only DNS and the exact intake peer on 8081.
|
||||
|
||||
The approved Kubernetes deployment prepares the private queue with a non-root
|
||||
init container using the identical image. It removes inherited setgid only on
|
||||
an empty, owned 2700 directory and otherwise requires 0700. It never changes
|
||||
existing nonempty evidence custody. This covers local-path fsGroup behavior
|
||||
that a plain Docker volume does not reproduce.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"name": "state-hub-memo-dispatch",
|
||||
"namespace": "state-hub",
|
||||
"annotations": {
|
||||
"state-hub.coulomb.social/admission": "proposal; STATE-WP-0096 / INFD-WP-0007 approved rollout required"
|
||||
"state-hub.coulomb.social/admission": "approved; INFD-WP-0007 cutover"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
"name": "state-hub-memo-dispatch",
|
||||
"namespace": "state-hub",
|
||||
"annotations": {
|
||||
"state-hub.coulomb.social/admission": "proposal; STATE-WP-0096 / INFD-WP-0007 approved rollout required"
|
||||
"state-hub.coulomb.social/admission": "approved; INFD-WP-0007 cutover"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
|
|
@ -131,6 +131,42 @@
|
|||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"initContainers": [
|
||||
{
|
||||
"name": "prepare-private-queue",
|
||||
"image": "forgejo.coulomb.social/coulomb/state-hub-memo-dispatch@sha256:410fb6b6c9fd7a7ee0b63d727fd3249fb3ae13943421d6a5ce88b7ad2520bd2f",
|
||||
"command": [
|
||||
"python",
|
||||
"-c",
|
||||
"import os, stat\np='/data/private'\ntry:\n os.mkdir(p, 0o700)\nexcept FileExistsError:\n pass\nfd=os.open(p, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)\ntry:\n info=os.fstat(fd)\n if info.st_uid != os.getuid():\n raise SystemExit('queue owner refused')\n mode=stat.S_IMODE(info.st_mode)\n if mode == 0o2700 and not os.listdir(fd):\n os.fchmod(fd, 0o700)\n elif mode != 0o700:\n raise SystemExit('queue mode refused')\nfinally:\n os.close(fd)\n"
|
||||
],
|
||||
"securityContext": {
|
||||
"allowPrivilegeEscalation": false,
|
||||
"readOnlyRootFilesystem": true,
|
||||
"capabilities": {
|
||||
"drop": [
|
||||
"ALL"
|
||||
]
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": "5m",
|
||||
"memory": "24Mi"
|
||||
},
|
||||
"limits": {
|
||||
"cpu": "100m",
|
||||
"memory": "96Mi"
|
||||
}
|
||||
},
|
||||
"volumeMounts": [
|
||||
{
|
||||
"name": "queue",
|
||||
"mountPath": "/data"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +179,7 @@
|
|||
"name": "state-hub-memo-dispatch",
|
||||
"namespace": "state-hub",
|
||||
"annotations": {
|
||||
"state-hub.coulomb.social/admission": "proposal; STATE-WP-0096 / INFD-WP-0007 approved rollout required"
|
||||
"state-hub.coulomb.social/admission": "approved; INFD-WP-0007 cutover"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
|
|
|
|||
|
|
@ -124,3 +124,26 @@ def test_transport_suppresses_proxy_redirect_and_oversized_response(monkeypatch)
|
|||
transport('POST',origin+'/v1/memos',{}, {})
|
||||
finally:
|
||||
server.shutdown();server.server_close();thread.join(timeout=2)
|
||||
|
||||
|
||||
def test_kubernetes_queue_init_handles_setgid_without_taking_over_data(tmp_path):
|
||||
manifest = json.loads((Path(__file__).parents[1] / 'deploy/informed-decision/dispatcher.candidate.json').read_text())
|
||||
pod = next(item for item in manifest['items'] if item['kind'] == 'Deployment')['spec']['template']['spec']
|
||||
init = pod['initContainers'][0]
|
||||
assert init['image'] == pod['containers'][0]['image']
|
||||
tmp_path.chmod(0o2770)
|
||||
private = tmp_path / 'private'
|
||||
code = init['command'][2].replace('/data/private', str(private))
|
||||
def run():
|
||||
return subprocess.run([sys.executable, '-c', code], capture_output=True).returncode
|
||||
assert run() == 0
|
||||
assert stat.S_IMODE(private.stat().st_mode) == 0o700
|
||||
retained = private / 'retained'
|
||||
retained.write_text('existing evidence')
|
||||
assert run() == 0
|
||||
private.chmod(0o2700)
|
||||
assert run() != 0 # Existing nonempty custody is never repaired silently.
|
||||
retained.unlink()
|
||||
assert run() == 0 # Empty directory left by the initial failed startup.
|
||||
private.chmod(0o755)
|
||||
assert run() != 0
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ status: active
|
|||
owner: codex
|
||||
topic_slug: netkingdom
|
||||
created: "2026-10-04"
|
||||
updated: "2026-10-04"
|
||||
updated: "2026-10-05"
|
||||
related: [INFD-WP-0005, INFD-WP-0007]
|
||||
state_hub_workstream_id: "4cca4a91-3b21-51f0-a61f-902ccb1289c6"
|
||||
---
|
||||
|
|
@ -53,7 +53,7 @@ caller identity.
|
|||
|
||||
```task
|
||||
id: STATE-WP-0096-T03
|
||||
status: todo
|
||||
status: progress
|
||||
priority: high
|
||||
state_hub_task_id: "1aeb4da7-5411-5634-ae6a-2e4bf0e8738b"
|
||||
```
|
||||
|
|
@ -85,3 +85,14 @@ Final Forgejo image 410fb6b (source 4a59b23) passed five installed-container
|
|||
checks and Trivy HIGH/CRITICAL scan (zero findings). Owner tests: 28 passed.
|
||||
Cross-service suite: 663 passed, one skipped. Both projected audiences and final
|
||||
image pin passed server dry-run. Receipts: docs/evidence/2026-10-04-memo-dispatch/.
|
||||
|
||||
## Approved native cutover — 2026-10-05
|
||||
|
||||
Founder approved the cutover (State Hub decision d2eaa2e9-6d11-4676-a07f-e3b7e59a88e3).
|
||||
The worker was deployed by a pinned manual Argo application. Native startup
|
||||
exposed inherited setgid on the newly created private queue (2700 instead of
|
||||
0700). A same-image, non-root init container normalizes only an empty owned
|
||||
2700 directory; existing nonempty or unsafe custody fails closed. Regression
|
||||
coverage exercises first start under setgid, restart preservation, the empty
|
||||
failed-start repair, and rejection of nonempty or world-readable custody.
|
||||
Human acceptance remains pending.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue