fix(repo-readonly): own read-only deploy key; workflow-script drops write-capable gitea-ssh/v1
repo-readonly/v1 now declares write_targets: [], an ssh_hosts alias and its own secrets_required (account platform-readonly.space:platform, a read-only Gitea deploy key), and stages the key before cloning. workflow-script/v1 no longer layers gitea-ssh/v1, so script nodes cannot push. Invariants updated: credential layers in the composite must declare write_targets: []. Bug 45a48685-23e9-4605-af1f-d93306108cef Claude-Session: https://claude.ai/code/session_01N218bERyNeLhbLmDRDvbWF
This commit is contained in:
@@ -32,5 +32,4 @@ layers:
|
|||||||
# is a no-op there too. Layered-in instead of layering agent-repo/v1
|
# is a no-op there too. Layered-in instead of layering agent-repo/v1
|
||||||
# because that context's fork management and push side effects are wrong
|
# because that context's fork management and push side effects are wrong
|
||||||
# for a read-only script node (see bug item body).
|
# for a read-only script node (see bug item body).
|
||||||
- context: gitea-ssh/v1
|
|
||||||
- context: repo-readonly/v1
|
- context: repo-readonly/v1
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ description: "Read-only project-repo clone for harness-path kind: script nodes (
|
|||||||
requires: []
|
requires: []
|
||||||
provides: []
|
provides: []
|
||||||
|
|
||||||
|
# HC-11: read-only layer. No push target; the credential below is a read-only
|
||||||
|
# deploy key (Gitea key id 20 on skynet/agent-runtimes), so a script node
|
||||||
|
# cannot write even if its code tries (bug 45a48685).
|
||||||
|
write_targets: []
|
||||||
|
|
||||||
# WF-COMP-34: this layer's init.sh is the read-only clone. It only runs
|
# WF-COMP-34: this layer's init.sh is the read-only clone. It only runs
|
||||||
# when AGENT_REPO_URL is set; the unset case is a no-op (a script node
|
# when AGENT_REPO_URL is set; the unset case is a no-op (a script node
|
||||||
# whose template omits agent_repo keeps its pre-fix silent empty workspace
|
# whose template omits agent_repo keeps its pre-fix silent empty workspace
|
||||||
@@ -21,4 +26,14 @@ env:
|
|||||||
scripts:
|
scripts:
|
||||||
init: "./init.sh"
|
init: "./init.sh"
|
||||||
|
|
||||||
secrets_required: []
|
ssh_hosts:
|
||||||
|
- alias: gitea.oreillyit.nz-ai-enablement
|
||||||
|
hostname: gitea.oreillyit.nz
|
||||||
|
user: git
|
||||||
|
identity_secret: GITEA_SSH_KEY
|
||||||
|
|
||||||
|
secrets_required:
|
||||||
|
- name: gitea-ssh-readonly
|
||||||
|
account_ref: "platform-readonly.space:platform"
|
||||||
|
mount_path: /run/agent/secrets/gitea-ssh-readonly
|
||||||
|
mode: "0400"
|
||||||
|
|||||||
@@ -52,6 +52,18 @@ if [ -d /workspace/project ] && [ -n "$(ls -A /workspace/project 2>/dev/null)" ]
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Stage the ESO-mounted read-only deploy key where the harness-init
|
||||||
|
# ~/.ssh/config IdentityFile expects it (same destination gitea-ssh/v1 used;
|
||||||
|
# that layer is no longer part of workflow-script/v1 — bug 45a48685).
|
||||||
|
SSH_KEY_SRC="/run/agent/secrets/gitea-ssh-readonly/private_key"
|
||||||
|
SSH_KEY_DST="/home/agent/.ssh/gitea-oreillyit-nz-ai-enablement"
|
||||||
|
if [ ! -r "$SSH_KEY_SRC" ]; then
|
||||||
|
echo "ERROR: read-only SSH key not found at $SSH_KEY_SRC — ESO ExternalSecret not Ready?" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p /home/agent/.ssh
|
||||||
|
install -m 0600 -o agent -g agent "$SSH_KEY_SRC" "$SSH_KEY_DST"
|
||||||
|
|
||||||
mkdir -p /workspace
|
mkdir -p /workspace
|
||||||
|
|
||||||
echo "Cloning (read-only): $AGENT_REPO_URL -> /workspace/project"
|
echo "Cloning (read-only): $AGENT_REPO_URL -> /workspace/project"
|
||||||
|
|||||||
@@ -16,11 +16,12 @@ def _load(kind_dir: str, name: str) -> dict:
|
|||||||
return yaml.safe_load((_ROOT / kind_dir / name / "v1" / "harness.yaml").read_text())
|
return yaml.safe_load((_ROOT / kind_dir / name / "v1" / "harness.yaml").read_text())
|
||||||
|
|
||||||
|
|
||||||
def test_workflow_script_layers_shared_variables_then_cp_service_token():
|
def test_workflow_script_layers_shared_variables_cp_service_token_repo_readonly():
|
||||||
doc = _load("composites", "workflow-script")
|
doc = _load("composites", "workflow-script")
|
||||||
assert doc["layers"] == [
|
assert doc["layers"] == [
|
||||||
{"context": "shared-variables/v1"},
|
{"context": "shared-variables/v1"},
|
||||||
{"context": "cp-service-token/v1"},
|
{"context": "cp-service-token/v1"},
|
||||||
|
{"context": "repo-readonly/v1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -203,27 +203,25 @@ def test_cp_service_token_layer_already_sets_cp_service_token_file_env():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_workflow_script_composite_no_forge_credentials_no_write_targets_needed():
|
def test_workflow_script_composite_forge_credentials_are_read_only():
|
||||||
"""workflow-script/v1 has no forge credentials of its own and so does
|
"""Any layer in workflow-script/v1 that carries a forge credential MUST
|
||||||
NOT need to declare ``write_targets`` (HC-11). This is the inverse of
|
declare ``write_targets: []`` (HC-11): a script node may read the project
|
||||||
the forge-credentials invariant: a non-credential composite is fine
|
repo but never push. The write-capable gitea-ssh/v1 layer must not be
|
||||||
without that field.
|
present (bug 45a48685).
|
||||||
|
|
||||||
Spec: spec/harness.md HC-11 (only layers with forge credentials MUST
|
Spec: spec/harness.md HC-11, spec/typed-workflows.md WF-COMP-34.
|
||||||
declare write_targets).
|
|
||||||
"""
|
"""
|
||||||
parsed = _load_layer("composites", "workflow-script")
|
parsed = _load_layer("composites", "workflow-script")
|
||||||
# Walk every layer entry, ensure none introduce a forge credential.
|
|
||||||
from tests.invariants.forge_credentials import _layer_has_forge_credentials
|
from tests.invariants.forge_credentials import _layer_has_forge_credentials
|
||||||
|
|
||||||
for layer in parsed["layers"]:
|
for layer in parsed["layers"]:
|
||||||
kind_name, ref = next(iter(layer.items()))
|
kind_name, ref = next(iter(layer.items()))
|
||||||
name = ref.split("/")[0]
|
name = ref.split("/")[0]
|
||||||
|
assert name != "gitea-ssh", "workflow-script/v1 must not layer the write-capable gitea-ssh/v1 (bug 45a48685)"
|
||||||
kind_dir = _KIND_TO_DIR[kind_name]
|
kind_dir = _KIND_TO_DIR[kind_name]
|
||||||
sub = _load_layer(kind_dir, name)
|
sub = _load_layer(kind_dir, name)
|
||||||
assert not _layer_has_forge_credentials(sub), (
|
if _layer_has_forge_credentials(sub):
|
||||||
f"workflow-script/v1's layer {name} introduced a forge credential; "
|
assert sub.get("write_targets") == [], (
|
||||||
f"HC-11 would then require write_targets to be declared on the "
|
f"workflow-script/v1's layer {name} carries a forge credential "
|
||||||
f"composite, and a script-node composite should never carry "
|
f"but does not declare write_targets: [] (HC-11 read-only)."
|
||||||
f"forge credentials (WF-COMP-31)."
|
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user