Add Docker-routed dev tooling, smoke test, example config, and docs
Route every compile/vet/test path through a digest-pinned golang:1.26
builder container (dev.sh + Makefile) so the host never runs a Go
toolchain. Add an end-to-end smoke that serves keyproxy in a container
with a throwaway config and drives 401/200/404/400/501/405 paths with
python urllib, asserting the server log is redacted. Ship a commented
keyproxy.toml.example (the real config stays gitignored along with any
*.env tripwire) and rewrite the README as the full quickstart, config,
HTTP, CLI, and redaction reference.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
+108
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
"""keyproxy resolve smoke probe.
|
||||
|
||||
Drives `keyproxy serve` on BASE (default http://127.0.0.1:8082) with
|
||||
plain stdlib urllib: the host has no curl by policy. Checks: healthz,
|
||||
401 without/with wrong token, 200 file + env resolves, 404 unknown ref,
|
||||
400 invalid ref format (without echo), 501 bitwarden/vault stubs, 502
|
||||
empty-value failure. Prints one PASS/FAIL line per check; exits non-zero
|
||||
on any failure.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
BASE = os.environ.get("BASE_URL", "http://127.0.0.1:8082")
|
||||
TOKEN = os.environ.get("KEYPROXY_TOKEN", "smoke-bearer-token-1111")
|
||||
|
||||
failures = 0
|
||||
|
||||
|
||||
def req(method, path, body=None, headers=None):
|
||||
r = urllib.request.Request(BASE + path, data=body, method=method,
|
||||
headers=headers or {})
|
||||
try:
|
||||
with urllib.request.urlopen(r, timeout=5) as resp:
|
||||
return resp.status, resp.read().decode()
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read().decode()
|
||||
except Exception as e: # transport errors surface as FAIL lines, not tracebacks
|
||||
return 0, f"transport error: {e}"
|
||||
|
||||
|
||||
def check(name, cond, detail=""):
|
||||
global failures
|
||||
print(("PASS" if cond else "FAIL"), name, ("- " + detail) if detail else "")
|
||||
if not cond:
|
||||
failures += 1
|
||||
|
||||
|
||||
def wait_healthz(tries=50):
|
||||
for _ in range(tries):
|
||||
try:
|
||||
code, _ = req("GET", "/healthz")
|
||||
if code == 200:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(0.2)
|
||||
return False
|
||||
|
||||
|
||||
def resolve(token, ref):
|
||||
body = json.dumps({"ref": ref}).encode()
|
||||
headers = {"Authorization": "Bearer " + token} if token else {}
|
||||
return req("POST", "/v1/resolve", body, headers)
|
||||
|
||||
|
||||
def main():
|
||||
check("healthz answers (unauthenticated)", wait_healthz())
|
||||
|
||||
code, text = resolve(None, "mpk-smoke")
|
||||
check("no bearer token rejected 401", code == 401, text.strip())
|
||||
|
||||
code, text = resolve("wrong-token", "mpk-smoke")
|
||||
check("wrong bearer token rejected 401", code == 401, text.strip())
|
||||
|
||||
code, text = resolve(TOKEN, "mpk-smoke")
|
||||
obj = json.loads(text) if text else {}
|
||||
check("file ref resolves 200 with value",
|
||||
code == 200 and obj.get("value") == "smoke-secret-material-2222",
|
||||
text.strip())
|
||||
|
||||
code, text = resolve(TOKEN, "mpk-smoke-env")
|
||||
obj = json.loads(text) if text else {}
|
||||
check("env ref resolves 200 with value",
|
||||
code == 200 and obj.get("value") == "smoke-env-material-4444",
|
||||
text.strip())
|
||||
|
||||
code, text = resolve(TOKEN, "mpk-ghost")
|
||||
check("unknown ref 404", code == 404 and "mpk-ghost" in text, text.strip())
|
||||
|
||||
code, text = resolve(TOKEN, "sk-live-pasted-secret")
|
||||
check("invalid ref format 400 without echo",
|
||||
code == 400 and "pasted" not in text, text.strip())
|
||||
|
||||
code, text = resolve(TOKEN, "mpk-smoke-bw")
|
||||
check("bitwarden stub 501 not_implemented",
|
||||
code == 501 and "not_implemented" in text and "bitwarden" in text,
|
||||
text.strip())
|
||||
|
||||
code, text = resolve(TOKEN, "mpk-smoke-vault")
|
||||
check("vault stub 501 not_implemented",
|
||||
code == 501 and "not_implemented" in text and "vault" in text,
|
||||
text.strip())
|
||||
|
||||
code, text = req("GET", "/v1/resolve")
|
||||
check("GET resolve rejected 405", code == 405, text.strip())
|
||||
|
||||
print("probe:", "OK" if failures == 0 else f"{failures} FAILURE(S)")
|
||||
return 0 if failures == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
#!/bin/sh
|
||||
# keyproxy end-to-end smoke: builds in the digest-pinned builder, starts
|
||||
# `keyproxy serve` in a container on host port 8082 with a throwaway
|
||||
# config + throwaway 0600 env files, drives it from the host with python
|
||||
# urllib (curl is banned on the host by policy), checks the 401/200/404/
|
||||
# 400/501/502 paths AND that the server log is redacted (no material,
|
||||
# every ref masked as <ref>=***), then tears everything down.
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
IMAGE="golang@sha256:e8c859f5632dcfde7b32d2012b4351728f6437930887c2f6a91ea242459e5514" # = golang:1.26-bookworm
|
||||
NAME="keyproxy-smoke"
|
||||
PORT="${PORT:-8082}"
|
||||
|
||||
./dev.sh build
|
||||
|
||||
rm -rf .smoke
|
||||
mkdir -p .smoke
|
||||
chmod 0700 .smoke
|
||||
|
||||
# Throwaway material (smoke values only; never real secrets).
|
||||
cat > .smoke/keyproxy.env <<'EOF'
|
||||
KEYPROXY_TOKEN=smoke-bearer-token-1111
|
||||
EOF
|
||||
cat > .smoke/creds.env <<'EOF'
|
||||
API_KEY=smoke-secret-material-2222
|
||||
OTHER=smoke-other-3333
|
||||
EOF
|
||||
chmod 0600 .smoke/keyproxy.env .smoke/creds.env
|
||||
|
||||
# In-container bind is all-interfaces ONLY because docker -p publishing
|
||||
# is the boundary here; the host default (and example config) stays
|
||||
# loopback.
|
||||
cat > .smoke/keyproxy.toml <<EOF
|
||||
listen = ":8082"
|
||||
|
||||
[auth]
|
||||
token_ref = "mpk-keyproxy-self"
|
||||
|
||||
[refs."mpk-keyproxy-self"]
|
||||
backend = "file"
|
||||
source = "/h/.smoke/keyproxy.env"
|
||||
key = "KEYPROXY_TOKEN"
|
||||
|
||||
[refs."mpk-smoke"]
|
||||
backend = "file"
|
||||
source = "/h/.smoke/creds.env"
|
||||
key = "API_KEY"
|
||||
|
||||
[refs."mpk-smoke-env"]
|
||||
backend = "env"
|
||||
source = "KEYPROXY_SMOKE_ENV"
|
||||
|
||||
[refs."mpk-smoke-bw"]
|
||||
backend = "bitwarden"
|
||||
source = "sm://p/smoke"
|
||||
key = "API_KEY"
|
||||
|
||||
[refs."mpk-smoke-vault"]
|
||||
backend = "vault"
|
||||
source = "secret/data/smoke"
|
||||
key = "API_KEY"
|
||||
EOF
|
||||
|
||||
docker rm -f "$NAME" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$NAME" -p "$PORT:8082" \
|
||||
-v "$PWD:/h" -w /h -u "$(id -u):$(id -g)" -e HOME=/tmp \
|
||||
-e KEYPROXY_SMOKE_ENV=smoke-env-material-4444 \
|
||||
"$IMAGE" /h/bin/keyproxy serve -config /h/.smoke/keyproxy.toml
|
||||
|
||||
cleanup() {
|
||||
docker rm -f "$NAME" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "--- probing http://127.0.0.1:$PORT (python urllib; curl banned on host) ---"
|
||||
BASE_URL="http://127.0.0.1:$PORT" KEYPROXY_TOKEN="smoke-bearer-token-1111" \
|
||||
python3 smoke/probe.py
|
||||
probe_rc=$?
|
||||
|
||||
echo "--- server log (redaction check) ---"
|
||||
LOG="$(docker logs "$NAME" 2>&1)"
|
||||
echo "$LOG"
|
||||
|
||||
if echo "$LOG" | grep -q 'smoke-secret-material-2222\|smoke-env-material-4444\|smoke-bearer-token-1111'; then
|
||||
echo "FAIL: server log contains material"
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "$LOG" | grep -q 'ref=mpk-smoke=\*\*\*'; then
|
||||
echo "FAIL: server log does not mask refs as <ref>=***"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit "$probe_rc"
|
||||
Reference in New Issue
Block a user