b0ee7da5be
Adds tests/ with a tiny bash assert harness, manifest fixtures, and a
runner. No framework dependency — each test file is self-contained
and exits 0 on pass / 1 on fail; tests/run_tests.sh aggregates.
Unit tests (no docker):
- pipelock_naming: container_name, proxy_url, proxy_host_port shape
- pipelock_classify: _pipelock_is_ipv4_literal classifier coverage
- pipelock_allowlist: bottle_allowlist + ssh hostnames/ip_cidrs/
trusted_domains + effective_allowlist union/dedup/sort, plus
rejection of non-string entries
- pipelock_yaml: emitter shape (mode/enforce/api_allowlist/forward_proxy/
dlp), conditional ssrf+trusted_domains blocks, secret hygiene
(manifest env values must not appear in YAML), file mode 600
Integration tests (require docker, skip cleanly otherwise):
- pipelock_image: pinned digest's ENTRYPOINT is /pipelock and CMD
contains 'run' and the binary --version succeeds — would catch a
future image bump that changes the launcher's argv contract
- pipelock_sidecar_smoke: docker create + cp YAML to /etc/pipelock.yaml
+ start, then probe /health — the regression test for the bug
where the YAML was written to /etc/pipelock/ (parent dir absent in
the distroless image)
- dry_run_plan: cli.sh start --dry-run shows the egress line,
counts the bottle's entry into the effective allowlist, prints
the dry-run banner, and creates zero docker resources
- orphan_cleanup: the cleanup primitives the start-flow trap depends
on (network_remove, pipelock_stop) are idempotent against
missing/never-existed resources, so the trap is safe even if
pipelock_start dies before everything is wired up
Assisted-by: Claude Code
91 lines
3.7 KiB
Bash
Executable File
91 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Unit: pipelock_write_yaml — produces a YAML config containing the
|
|
# expected top-level keys and per-bottle entries. We don't fully parse
|
|
# YAML (no yq dependency); we grep for content shape.
|
|
TEST_NAME="pipelock_yaml"
|
|
|
|
. "$(dirname "$0")/../lib/common.sh"
|
|
# shellcheck source=../../lib/log.sh
|
|
. "${REPO_ROOT}/lib/log.sh"
|
|
# shellcheck source=../../lib/pipelock.sh
|
|
. "${REPO_ROOT}/lib/pipelock.sh"
|
|
|
|
out_dir="$(mktemp -d)"
|
|
cleanup() { rm -rf "$out_dir"; }
|
|
trap cleanup EXIT
|
|
|
|
# --- minimal bottle (no egress, no ssh): only api_allowlist defaults ---
|
|
|
|
m_min="$(write_fixture fixture_minimal)"
|
|
yaml_min="${out_dir}/min.yaml"
|
|
pipelock_write_yaml "$m_min" dev "$yaml_min"
|
|
|
|
content="$(cat "$yaml_min")"
|
|
assert_contains "$content" "mode: strict" "min: mode strict"
|
|
assert_contains "$content" "enforce: true" "min: enforce true"
|
|
assert_contains "$content" "api_allowlist:" "min: api_allowlist block"
|
|
assert_contains "$content" "api.anthropic.com" "min: anthropic baked default"
|
|
assert_contains "$content" "raw.githubusercontent.com" "min: github raw baked default"
|
|
assert_contains "$content" "forward_proxy:" "min: forward_proxy block"
|
|
assert_contains "$content" "enabled: true" "min: forward_proxy enabled"
|
|
assert_contains "$content" "dlp:" "min: dlp block"
|
|
assert_contains "$content" "include_defaults: true" "min: dlp include_defaults"
|
|
assert_contains "$content" "scan_env: true" "min: dlp scan_env"
|
|
# No ssh entries in the manifest, so neither ssrf nor trusted_domains
|
|
# blocks should be emitted.
|
|
assert_not_contains "$content" "trusted_domains:" "min: no trusted_domains"
|
|
assert_not_contains "$content" "ssrf:" "min: no ssrf block"
|
|
|
|
rm -f "$m_min"
|
|
|
|
# --- ssh bottle: trusted_domains for hostname, ssrf.ip_allowlist for ipv4 ---
|
|
|
|
m_ssh="$(write_fixture fixture_with_ssh)"
|
|
yaml_ssh="${out_dir}/ssh.yaml"
|
|
pipelock_write_yaml "$m_ssh" dev "$yaml_ssh"
|
|
|
|
content="$(cat "$yaml_ssh")"
|
|
assert_contains "$content" "trusted_domains:" "ssh: trusted_domains block emitted"
|
|
assert_contains "$content" "github.com" "ssh: hostname in trusted_domains (or allowlist)"
|
|
assert_contains "$content" "ssrf:" "ssh: ssrf block emitted"
|
|
assert_contains "$content" "ip_allowlist:" "ssh: ip_allowlist key under ssrf"
|
|
assert_contains "$content" "100.78.141.42/32" "ssh: ipv4 host emitted as /32"
|
|
# Belt-and-suspenders: the ipv4 host should also be in api_allowlist
|
|
# (strict mode requires both).
|
|
assert_contains "$content" "100.78.141.42" "ssh: ipv4 host in api_allowlist too"
|
|
|
|
rm -f "$m_ssh"
|
|
|
|
# --- secret hygiene: env values from the manifest never enter the YAML ---
|
|
|
|
m_secret="$(mktemp)"
|
|
cat > "$m_secret" <<'JSON'
|
|
{
|
|
"bottles": {
|
|
"dev": {
|
|
"env": {
|
|
"MY_SECRET": "literal-value-should-not-appear",
|
|
"ANOTHER": "?prompt-message"
|
|
},
|
|
"egress": { "allowlist": ["github.com"] }
|
|
}
|
|
},
|
|
"agents": { "demo": { "skills": [], "prompt": "", "bottle": "dev" } }
|
|
}
|
|
JSON
|
|
yaml_sec="${out_dir}/secret.yaml"
|
|
pipelock_write_yaml "$m_secret" dev "$yaml_sec"
|
|
content="$(cat "$yaml_sec")"
|
|
assert_not_contains "$content" "literal-value-should-not-appear" "secret: literal env value not leaked"
|
|
assert_not_contains "$content" "MY_SECRET" "secret: env var name not leaked"
|
|
assert_not_contains "$content" "prompt-message" "secret: prompt sentinel not leaked"
|
|
rm -f "$m_secret"
|
|
|
|
# --- file mode is 600 ---
|
|
mode="$(stat -f '%p' "$yaml_min" 2>/dev/null || stat -c '%a' "$yaml_min")"
|
|
# macOS stat -f '%p' returns full mode like 100600; trim. Linux stat -c '%a' gives just 600.
|
|
mode="${mode: -3}"
|
|
assert_eq "600" "$mode" "yaml file mode is 600"
|
|
|
|
test_summary
|