Merge pull request 'fix: feat: vault actions should support mount declarations for credentials like SSH keys (#528)' (#536) from fix/issue-528 into main
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
This commit is contained in:
commit
31fde3d471
6 changed files with 72 additions and 4 deletions
|
|
@ -437,13 +437,40 @@ launch_runner() {
|
|||
log "Action ${action_id} has no secrets declared — runner will execute without extra env vars"
|
||||
fi
|
||||
|
||||
# Add volume mounts for file-based credentials (if any declared)
|
||||
local mounts_array
|
||||
mounts_array="${VAULT_ACTION_MOUNTS:-}"
|
||||
if [ -n "$mounts_array" ]; then
|
||||
local runtime_home="${HOME:-/home/debian}"
|
||||
for mount_alias in $mounts_array; do
|
||||
mount_alias=$(echo "$mount_alias" | xargs)
|
||||
[ -n "$mount_alias" ] || continue
|
||||
case "$mount_alias" in
|
||||
ssh)
|
||||
cmd+=(-v "${runtime_home}/.ssh:/home/agent/.ssh:ro")
|
||||
;;
|
||||
gpg)
|
||||
cmd+=(-v "${runtime_home}/.gnupg:/home/agent/.gnupg:ro")
|
||||
;;
|
||||
sops)
|
||||
cmd+=(-v "${runtime_home}/.config/sops/age:/home/agent/.config/sops/age:ro")
|
||||
;;
|
||||
*)
|
||||
log "ERROR: Unknown mount alias '${mount_alias}' for action ${action_id}"
|
||||
write_result "$action_id" 1 "Unknown mount alias: ${mount_alias}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
# Mount the ops repo so the runner entrypoint can read the action TOML
|
||||
cmd+=(-v "${OPS_REPO_ROOT}:/home/agent/ops:ro")
|
||||
|
||||
# Service name and action-id argument
|
||||
cmd+=(runner "$action_id")
|
||||
|
||||
log "Running: docker compose run --rm runner ${action_id} (secrets: ${secrets_array:-none})"
|
||||
log "Running: docker compose run --rm runner ${action_id} (secrets: ${secrets_array:-none}, mounts: ${mounts_array:-none})"
|
||||
|
||||
# Create temp file for logs
|
||||
local log_file
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ id = "${id}"
|
|||
formula = "release"
|
||||
context = "Release ${version}"
|
||||
secrets = ["GITHUB_TOKEN", "CODEBERG_TOKEN"]
|
||||
mounts = ["ssh"]
|
||||
EOF
|
||||
|
||||
echo "Created vault item: ${vault_toml}"
|
||||
|
|
|
|||
|
|
@ -14,9 +14,12 @@ id = "publish-skill-20260331"
|
|||
formula = "clawhub-publish"
|
||||
context = "SKILL.md bumped to 0.3.0"
|
||||
|
||||
# Required secrets to inject
|
||||
# Required secrets to inject (env vars)
|
||||
secrets = ["CLAWHUB_TOKEN"]
|
||||
|
||||
# Optional file-based credential mounts
|
||||
mounts = ["ssh"]
|
||||
|
||||
# Optional
|
||||
model = "sonnet"
|
||||
tools = ["clawhub"]
|
||||
|
|
@ -39,6 +42,7 @@ blast_radius = "low" # optional: overrides policy.toml tier ("low"|"medium
|
|||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `mounts` | array of strings | `[]` | Well-known mount aliases for file-based credentials. The dispatcher maps each alias to a read-only volume flag |
|
||||
| `model` | string | `sonnet` | Override the default Claude model for this action |
|
||||
| `tools` | array of strings | `[]` | MCP tools to enable during execution |
|
||||
| `timeout_minutes` | integer | `60` | Maximum execution time in minutes |
|
||||
|
|
@ -53,6 +57,16 @@ Common secret names:
|
|||
- `GITHUB_TOKEN` - GitHub API token for repository operations
|
||||
- `DEPLOY_KEY` - Infrastructure deployment key
|
||||
|
||||
## Mount Aliases
|
||||
|
||||
Mount aliases map to read-only volume flags passed to the runner container:
|
||||
|
||||
| Alias | Maps to |
|
||||
|-------|---------|
|
||||
| `ssh` | `-v ${HOME}/.ssh:/home/agent/.ssh:ro` |
|
||||
| `gpg` | `-v ${HOME}/.gnupg:/home/agent/.gnupg:ro` |
|
||||
| `sops` | `-v ${HOME}/.config/sops/age:/home/agent/.config/sops/age:ro` |
|
||||
|
||||
## Validation Rules
|
||||
|
||||
1. **Required fields**: `id`, `formula`, `context`, and `secrets` must be present
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
# id = "release-v120"
|
||||
# formula = "release"
|
||||
# context = "Release v1.2.0"
|
||||
# secrets = ["GITHUB_TOKEN", "CODEBERG_TOKEN"]
|
||||
# secrets = []
|
||||
# mounts = ["ssh"]
|
||||
#
|
||||
# Steps executed by the release formula:
|
||||
# 1. preflight - Validate prerequisites (version, FORGE_TOKEN, Docker)
|
||||
|
|
@ -27,6 +28,7 @@ id = "release-v120"
|
|||
formula = "release"
|
||||
context = "Release v1.2.0 — includes vault redesign, .profile system, architect agent"
|
||||
secrets = ["GITHUB_TOKEN", "CODEBERG_TOKEN"]
|
||||
mounts = ["ssh"]
|
||||
|
||||
# Optional: specify a larger model for complex release logic
|
||||
# model = "sonnet"
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ if validate_vault_action "$TOML_FILE"; then
|
|||
echo " Formula: $VAULT_ACTION_FORMULA"
|
||||
echo " Context: $VAULT_ACTION_CONTEXT"
|
||||
echo " Secrets: $VAULT_ACTION_SECRETS"
|
||||
echo " Mounts: ${VAULT_ACTION_MOUNTS:-none}"
|
||||
exit 0
|
||||
else
|
||||
echo "INVALID: $TOML_FILE" >&2
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ fi
|
|||
# Allowed secret names - must match keys in .env.vault.enc
|
||||
VAULT_ALLOWED_SECRETS="CLAWHUB_TOKEN GITHUB_TOKEN CODEBERG_TOKEN DEPLOY_KEY NPM_TOKEN DOCKER_HUB_TOKEN"
|
||||
|
||||
# Allowed mount aliases — well-known file-based credential directories
|
||||
VAULT_ALLOWED_MOUNTS="ssh gpg sops"
|
||||
|
||||
# Validate a vault action TOML file
|
||||
# Usage: validate_vault_action <path-to-toml>
|
||||
# Returns: 0 if valid, 1 if invalid
|
||||
|
|
@ -69,11 +72,16 @@ validate_vault_action() {
|
|||
secrets_line=$(echo "$toml_content" | grep -E '^secrets\s*=' | tr -d '\r')
|
||||
secrets_array=$(echo "$secrets_line" | sed -E 's/^secrets\s*=\s*\[(.*)\]/\1/' | tr -d '[]"' | tr ',' ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
|
||||
# Extract mounts array (optional)
|
||||
local mounts_line mounts_array
|
||||
mounts_line=$(echo "$toml_content" | grep -E '^mounts\s*=' | tr -d '\r') || true
|
||||
mounts_array=$(echo "$mounts_line" | sed -E 's/^mounts\s*=\s*\[(.*)\]/\1/' | tr -d '[]"' | tr ',' ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') || true
|
||||
|
||||
# Check for unknown fields (any top-level key not in allowed list)
|
||||
local unknown_fields
|
||||
unknown_fields=$(echo "$toml_content" | grep -E '^[a-zA-Z_][a-zA-Z0-9_]*\s*=' | sed -E 's/^([a-zA-Z_][a-zA-Z0-9_]*)\s*=.*/\1/' | sort -u | while read -r field; do
|
||||
case "$field" in
|
||||
id|formula|context|secrets|model|tools|timeout_minutes|dispatch_mode|blast_radius) ;;
|
||||
id|formula|context|secrets|mounts|model|tools|timeout_minutes|dispatch_mode|blast_radius) ;;
|
||||
*) echo "$field" ;;
|
||||
esac
|
||||
done)
|
||||
|
|
@ -122,6 +130,19 @@ validate_vault_action() {
|
|||
fi
|
||||
done
|
||||
|
||||
# Validate each mount alias is in the allowlist
|
||||
if [ -n "$mounts_array" ]; then
|
||||
for mount in $mounts_array; do
|
||||
mount=$(echo "$mount" | tr -d '"' | xargs) # trim whitespace and quotes
|
||||
if [ -n "$mount" ]; then
|
||||
if ! echo " $VAULT_ALLOWED_MOUNTS " | grep -q " $mount "; then
|
||||
echo "ERROR: Unknown mount alias (not in allowlist): $mount" >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Validate optional fields if present
|
||||
# model
|
||||
if echo "$toml_content" | grep -qE '^model\s*='; then
|
||||
|
|
@ -158,10 +179,12 @@ validate_vault_action() {
|
|||
export VAULT_ACTION_FORMULA="$formula"
|
||||
export VAULT_ACTION_CONTEXT="$context"
|
||||
export VAULT_ACTION_SECRETS="$secrets_array"
|
||||
export VAULT_ACTION_MOUNTS="${mounts_array:-}"
|
||||
|
||||
log "VAULT_ACTION_ID=$VAULT_ACTION_ID"
|
||||
log "VAULT_ACTION_FORMULA=$VAULT_ACTION_FORMULA"
|
||||
log "VAULT_ACTION_SECRETS=$VAULT_ACTION_SECRETS"
|
||||
log "VAULT_ACTION_MOUNTS=${VAULT_ACTION_MOUNTS:-none}"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue