Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,17 @@ Thumbs.db
*.pem
*.key

# Fuzz corpus (generated by go test -fuzz)
**/testdata/fuzz/

# Coverage
coverage.out
coverage.html
coverage_*.out

# Local evidence (no secrets; do not commit)
LIVE_E2E_EVIDENCE.md
DOCKER_E2E_REPORT.md

# Local E2E helper (operator runs manually with GH_PAT)
dockercomms_e2e.sh
44 changes: 32 additions & 12 deletions docs/repro.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,44 @@ go test -tags=integration -run TestDockerHubTagListing -v ./test/integration/...
# Expected: exit 0, tag count logged
```

## GHCR Integration Test (Scripted)
## GHCR Integration Test (Scripted) — Fast path

For non-TTY environments (e.g. Cursor) or when stored creds are invalid:
1. Create a GitHub PAT with `read:packages` and `write:packages`. If org uses SSO, authorize the token.
2. Run (pick one path):

1. Create a GitHub PAT with `read:packages` and `write:packages`.
2. Save it (pick one):
- `printf '%s' 'ghp_...' > ~/.dockercomms_gh_pat && chmod 600 ~/.dockercomms_gh_pat`
- Or `export GH_PAT='ghp_...'`
3. Set env (or use defaults):
- `export DOCKERCOMMS_IT_GHCR_REPO=ghcr.io/OWNER/REPO`
- `export DOCKERCOMMS_IT_RECIPIENT=team-b`
4. Run: `./scripts/login-and-run-integration.sh`
**Path A — env vars:**

```bash
export DOCKERCOMMS_IT_GHCR_REPO="ghcr.io/OWNER/REPO"
export DOCKERCOMMS_IT_RECIPIENT="team-b"
export GH_USER="codethor0"
export GH_PAT="ghp_..."

# If login previously failed with "denied":
./scripts/purge-ghcr-creds.sh

./scripts/login-and-run-integration.sh
```

**Path B — PAT file:**

```bash
printf '%s' 'ghp_...' > ~/.dockercomms_gh_pat
chmod 600 ~/.dockercomms_gh_pat

export DOCKERCOMMS_IT_GHCR_REPO="ghcr.io/OWNER/REPO"
export DOCKERCOMMS_IT_RECIPIENT="team-b"

./scripts/login-and-run-integration.sh
```

Never paste PAT into issues or logs.

Preflight check (no login): `./scripts/login-and-run-integration.sh --check`
**Dry-run (no login):** `./scripts/login-and-run-integration.sh --check`

**If login shows "denied":** `./scripts/purge-ghcr-creds.sh` then retry.

If login shows "denied", purge bad creds: `./scripts/purge-ghcr-creds.sh`
**If repo has no `:latest` tag:** Set `DOCKERCOMMS_IT_AUTH_TAG` to an existing tag (e.g. `v1.0.0`) so auth proof can verify; otherwise the script prints guidance and proceeds.

## Integration Tests (opt-in)

Expand Down
176 changes: 176 additions & 0 deletions scripts/docker-e2e.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#!/usr/bin/env bash
# Docker-based E2E validation harness for dockercomms.
# Modes: gates | integration | cli | full
# Usage: ./scripts/docker-e2e.sh [gates|integration|cli|full]
# Integration/full require: GH_PAT (or ~/.dockercomms_gh_pat), DOCKERCOMMS_IT_GHCR_REPO, DOCKERCOMMS_IT_RECIPIENT
set -euo pipefail

PROJECT="$(cd "$(dirname "$0")/.." && pwd)"
GO_VERSION="${GO_VERSION:-1.25}"
IMAGE="golang:${GO_VERSION}"
MODE="${1:-gates}"
PAT_FILE="${HOME}/.dockercomms_gh_pat"

# Resolve GH_PAT: env or secure file (never print)
GH_PAT="${GH_PAT:-}"
if [[ -z "${GH_PAT}" ]] && [[ -f "${PAT_FILE}" ]] && [[ -r "${PAT_FILE}" ]]; then
GH_PAT=$(cat "${PAT_FILE}")
fi

# Export for container (do not echo)
export DOCKERCOMMS_IT_GHCR_REPO="${DOCKERCOMMS_IT_GHCR_REPO:-}"
export DOCKERCOMMS_IT_RECIPIENT="${DOCKERCOMMS_IT_RECIPIENT:-}"
export DOCKERCOMMS_IT_DH_REPO="${DOCKERCOMMS_IT_DH_REPO:-}"
export DOCKERCOMMS_IT_LARGE_PAYLOAD="${DOCKERCOMMS_IT_LARGE_PAYLOAD:-}"
export DOCKERCOMMS_IT_AUTH_TAG="${DOCKERCOMMS_IT_AUTH_TAG:-}"
export DOCKERCOMMS_IT_SINCE="${DOCKERCOMMS_IT_SINCE:-}"
export GO_TEST_TIMEOUT="${GO_TEST_TIMEOUT:-240s}"

host_login_ghcr() {
if [[ -z "${GH_PAT}" ]]; then
echo "[docker-e2e] GH_PAT not set; integration tests will fail auth"
return 1
fi
GH_USER="${GH_USER:-codethor0}"
echo "[docker-e2e] Logging in to ghcr.io on host (credentials in ~/.docker for container)..."
printf '%s' "${GH_PAT}" | docker login ghcr.io -u "${GH_USER}" --password-stdin 2>/dev/null || {
echo "[docker-e2e] docker login ghcr.io failed"
return 1
}
}

docker_run() {
docker run --rm \
-v "${PROJECT}:/workspace" \
-w /workspace \
-v "${HOME}/.docker:/root/.docker:ro" \
-e DOCKERCOMMS_IT_GHCR_REPO \
-e DOCKERCOMMS_IT_RECIPIENT \
-e DOCKERCOMMS_IT_DH_REPO \
-e DOCKERCOMMS_IT_LARGE_PAYLOAD \
-e DOCKERCOMMS_IT_AUTH_TAG \
-e DOCKERCOMMS_IT_SINCE \
-e GO_TEST_TIMEOUT \
"$IMAGE" \
bash -c "$1"
}

case "$MODE" in
gates)
echo "[docker-e2e] gates mode: build, test, race, lint, coverage-gate"
docker_run '
set -e
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.10.1
make build
go test ./...
go test -race ./...
golangci-lint run ./...
make coverage-gate
'
;;
integration)
echo "[docker-e2e] integration mode: host login + go test -tags=integration"
: "${DOCKERCOMMS_IT_GHCR_REPO:=ghcr.io/codethor0/dockercomms}"
: "${DOCKERCOMMS_IT_RECIPIENT:=team-b}"
export DOCKERCOMMS_IT_GHCR_REPO DOCKERCOMMS_IT_RECIPIENT
if ! host_login_ghcr; then
echo "[docker-e2e] Skipping integration: set GH_PAT or ~/.dockercomms_gh_pat"
exit 1
fi
docker_run '
set -e
make build
if [[ -z "${DOCKERCOMMS_IT_GHCR_REPO:-}" ]] || [[ -z "${DOCKERCOMMS_IT_RECIPIENT:-}" ]]; then
echo "DOCKERCOMMS_IT_GHCR_REPO and DOCKERCOMMS_IT_RECIPIENT required"
exit 1
fi
go test -tags=integration ./test/integration/... -run Test -count=1 -v -timeout "${GO_TEST_TIMEOUT:-240s}"
'
;;
cli)
echo "[docker-e2e] cli mode: send/recv round-trip, verify-failure no-materialize, resume"
: "${DOCKERCOMMS_IT_GHCR_REPO:=ghcr.io/codethor0/dockercomms}"
: "${DOCKERCOMMS_IT_RECIPIENT:=team-b}"
export DOCKERCOMMS_IT_GHCR_REPO DOCKERCOMMS_IT_RECIPIENT
if ! host_login_ghcr; then
echo "[docker-e2e] Skipping cli: set GH_PAT or ~/.dockercomms_gh_pat"
exit 1
fi
docker_run '
set -e
make build
E2E=/tmp/dockercomms-e2e
rm -rf "$E2E" && mkdir -p "$E2E" "$E2E/out" "$E2E/bad"
dd if=/dev/urandom of="$E2E/payload.bin" bs=1M count=4 2>/dev/null
echo "=== Payload SHA256 ==="
sha256sum "$E2E/payload.bin"

echo "=== 7.1 Send (no sign) ==="
./dockercomms send "$E2E/payload.bin" --repo "$DOCKERCOMMS_IT_GHCR_REPO" --recipient "$DOCKERCOMMS_IT_RECIPIENT" --sign=false --ttl-seconds 3600

echo "=== 7.2 Recv (Verify=false) ==="
rm -rf "$E2E/out" && mkdir -p "$E2E/out"
./dockercomms recv --repo "$DOCKERCOMMS_IT_GHCR_REPO" --me "$DOCKERCOMMS_IT_RECIPIENT" --out "$E2E/out" --verify=false
echo "=== Output SHA256 ==="
sha256sum "$E2E/out/payload.bin" 2>/dev/null || true
cmp -s "$E2E/payload.bin" "$E2E/out/payload.bin" && echo "OK: payloads match"

echo "=== 7.3 Verify-failure no-materialize ==="
rm -rf "$E2E/bad" && mkdir -p "$E2E/bad"
set +e
./dockercomms recv --repo "$DOCKERCOMMS_IT_GHCR_REPO" --me "$DOCKERCOMMS_IT_RECIPIENT" --out "$E2E/bad" --verify=true --trusted-root /workspace/testdata/bad-trusted-root.json 2>/dev/null
REXIT=$?
set -e
echo "recv exit: $REXIT (expect 2)"
test ! -f "$E2E/bad/payload.bin" && echo "OK: no output on verify failure"
ls -la "$E2E/bad" 2>/dev/null || true

echo "=== 7.4 Resume (send same payload twice) ==="
./dockercomms send "$E2E/payload.bin" --repo "$DOCKERCOMMS_IT_GHCR_REPO" --recipient "$DOCKERCOMMS_IT_RECIPIENT" --sign=false --chunk-bytes 1048576
./dockercomms send "$E2E/payload.bin" --repo "$DOCKERCOMMS_IT_GHCR_REPO" --recipient "$DOCKERCOMMS_IT_RECIPIENT" --sign=false --chunk-bytes 1048576
echo "OK: both sends completed (blobs reused via HEAD)"
'
;;
full)
echo "[docker-e2e] full mode: gates + integration + cli"
: "${DOCKERCOMMS_IT_GHCR_REPO:=ghcr.io/codethor0/dockercomms}"
: "${DOCKERCOMMS_IT_RECIPIENT:=team-b}"
export DOCKERCOMMS_IT_GHCR_REPO DOCKERCOMMS_IT_RECIPIENT
docker_run '
set -e
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.10.1
make build
go test ./...
go test -race ./...
golangci-lint run ./...
make coverage-gate
'
if host_login_ghcr; then
docker_run '
set -e
go test -tags=integration ./test/integration/... -run Test -count=1 -v -timeout "${GO_TEST_TIMEOUT:-240s}"
'
docker_run '
set -e
make build
E2E=/tmp/dockercomms-e2e
rm -rf "$E2E" && mkdir -p "$E2E" "$E2E/out" "$E2E/bad"
dd if=/dev/urandom of="$E2E/payload.bin" bs=1M count=4 2>/dev/null
sha256sum "$E2E/payload.bin"
./dockercomms send "$E2E/payload.bin" --repo "$DOCKERCOMMS_IT_GHCR_REPO" --recipient "$DOCKERCOMMS_IT_RECIPIENT" --sign=false --ttl-seconds 3600
rm -rf "$E2E/out" && mkdir -p "$E2E/out"
./dockercomms recv --repo "$DOCKERCOMMS_IT_GHCR_REPO" --me "$DOCKERCOMMS_IT_RECIPIENT" --out "$E2E/out" --verify=false
cmp -s "$E2E/payload.bin" "$E2E/out/payload.bin" && echo "OK: round-trip"
rm -rf "$E2E/bad" && mkdir -p "$E2E/bad"
./dockercomms recv --repo "$DOCKERCOMMS_IT_GHCR_REPO" --me "$DOCKERCOMMS_IT_RECIPIENT" --out "$E2E/bad" --verify=true --trusted-root /workspace/testdata/bad-trusted-root.json 2>/dev/null || true
test ! -f "$E2E/bad/payload.bin" && echo "OK: no materialize on verify fail"
'
else
echo "[docker-e2e] integration and cli skipped: set GH_PAT or ~/.dockercomms_gh_pat"
fi
;;
*)
echo "Usage: $0 [gates|integration|full|cli]"
exit 1
;;
esac
71 changes: 56 additions & 15 deletions scripts/login-and-run-integration.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env bash
# GHCR login + integration tests. Non-interactive when GH_PAT provided.
# Exit 3 = auth failure (login or manifest inspect).
# Usage: ./scripts/login-and-run-integration.sh [--help|--check]
# Requires: export GH_PAT="ghp_..." OR ~/.dockercomms_gh_pat (chmod 600)
set -euo pipefail
# Restrict new file perms; PAT file must already be chmod 600
umask 077

PROJECT="$(cd "$(dirname "$0")/.." && pwd)"
Expand All @@ -26,6 +26,7 @@ Required env (or defaults used):
DOCKERCOMMS_IT_RECIPIENT (default: team-b)

Optional: GH_USER (default: codethor0)
DOCKERCOMMS_IT_AUTH_TAG (default: latest; use existing tag if :latest missing)
EOF
}

Expand All @@ -40,11 +41,12 @@ preflight() {
fi

echo "[preflight] GHCR connectivity..."
if ! curl -sS -I --max-time 15 https://ghcr.io/v2/ >/dev/null 2>&1; then
echo " FAIL: Cannot reach ghcr.io (network/proxy/DNS?)"
err=1
code=$(curl -sS -o /dev/null -w "%{http_code}" --max-time 10 https://ghcr.io/v2/ 2>/dev/null) || true
if [[ "$code" == "401" ]] || [[ "$code" == "405" ]]; then
echo " OK (got $code)"
else
echo " OK"
echo " FAIL: Cannot reach ghcr.io (got ${code:-timeout})"
err=1
fi

echo "[preflight] Env vars..."
Expand All @@ -61,6 +63,24 @@ preflight() {
fi
}

auth_proof() {
local auth_err tag
: "${DOCKERCOMMS_IT_AUTH_TAG:=latest}"
tag="${DOCKERCOMMS_IT_AUTH_TAG}"
auth_err=$(DOCKER_CLIENT_TIMEOUT=20 DOCKER_HTTP_TIMEOUT=20 docker manifest inspect "${DOCKERCOMMS_IT_GHCR_REPO}:${tag}" 2>&1) || true
if echo "${auth_err}" | grep -qE "manifest unknown|not found|no such manifest"; then
echo "Login succeeded but ${DOCKERCOMMS_IT_GHCR_REPO}:${tag} not found."
echo " Set DOCKERCOMMS_IT_AUTH_TAG to an existing tag or proceed (tests will validate auth via registry operations)."
return 0
fi
if echo "${auth_err}" | grep -qE "unauthorized|denied|authentication required|insufficient_scope|insufficient scope|invalid token|invalid username/password|access to the resource is denied"; then
echo "Auth to GHCR failed (docker manifest inspect). Exiting 3."
echo " PAT must have read:packages + write:packages."
echo " Re-run ./scripts/purge-ghcr-creds.sh if old creds interfere."
exit 3
fi
}

main() {
case "${1:-}" in
--help|-h) usage; exit 0 ;;
Expand All @@ -76,14 +96,16 @@ main() {
GH_USER="${GH_USER:-codethor0}"
GH_PAT="${GH_PAT:-}"
if [[ -z "${GH_PAT}" ]] && [[ -f "${PAT_FILE}" ]]; then
GH_PAT=$(cat "${PAT_FILE}")
if [[ -r "${PAT_FILE}" ]]; then
GH_PAT=$(cat "${PAT_FILE}")
fi
fi

if [[ -z "${GH_PAT}" ]]; then
if [[ ! -t 0 ]] || [[ ! -t 1 ]]; then
echo "ERROR: Non-TTY and GH_PAT not set. Cannot prompt for credentials."
echo " Set: export GH_PAT='ghp_...'"
echo " Or: echo 'ghp_...' > ~/.dockercomms_gh_pat && chmod 600 ~/.dockercomms_gh_pat"
echo " Or: printf '%s' 'ghp_...' > ~/.dockercomms_gh_pat && chmod 600 ~/.dockercomms_gh_pat"
echo " Never paste PAT into issues or logs."
exit 1
fi
Expand All @@ -96,20 +118,39 @@ main() {

preflight

echo "[1/4] Logging out of ghcr.io (ignore errors if not logged in)..."
echo "[1/5] Logging out of ghcr.io (ignore errors if not logged in)..."
docker logout ghcr.io >/dev/null 2>&1 || true

echo "[2/4] Logging in to ghcr.io non-interactively as ${GH_USER}..."
printf '%s' "${GH_PAT}" | docker login ghcr.io -u "${GH_USER}" --password-stdin
echo "[2/5] Logging in to ghcr.io non-interactively as ${GH_USER}..."
if ! printf '%s' "${GH_PAT}" | DOCKER_CLIENT_TIMEOUT=20 DOCKER_HTTP_TIMEOUT=20 docker login ghcr.io -u "${GH_USER}" --password-stdin; then
echo "Auth to GHCR failed (docker login). Exiting 3."
echo " PAT must have read:packages + write:packages."
echo " Re-run ./scripts/purge-ghcr-creds.sh if old creds interfere."
exit 3
fi

echo "[3/4] Verifying Docker can hit GHCR with auth..."
docker pull "ghcr.io/${GH_USER}/nonexistent-verify" 2>/dev/null || true
echo "OK (auth path exercised; nonexistent image pull failure is expected)."
echo "[3/5] Auth proof (must require auth, return quickly)..."
auth_proof

echo "[4/4] Running integration script..."
echo "[4/5] Running integration script..."
cd "${PROJECT}"
chmod +x "${SCRIPT}"
"${SCRIPT}"
"${SCRIPT}" || {
e=$?
if [[ $e -ne 0 ]]; then
echo ""
echo "Integration test failed. If you saw 'context deadline exceeded',"
echo "this is almost certainly missing/invalid GHCR auth; re-run this script"
echo "after verifying PAT and purge if needed."
fi
exit $e
}

echo "[5/5] Paste-back template (no secrets):"
echo " Repo: $DOCKERCOMMS_IT_GHCR_REPO"
echo " Recipient: $DOCKERCOMMS_IT_RECIPIENT"
echo " Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo " Cosign: $(cosign version 2>/dev/null || echo 'NOT INSTALLED')"
}

main "$@"
6 changes: 3 additions & 3 deletions scripts/purge-ghcr-creds.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ echo " export GH_PAT='ghp_...'"
echo " ./scripts/login-and-run-integration.sh"
echo ""
echo "If credential helper (credsStore) still returns old creds, manually:"
echo " 1. Open Docker Desktop -> Settings -> Docker Engine"
echo " 2. Or remove creds via: docker-credential-desktop erase ghcr.io"
echo " 3. Re-run: ./scripts/login-and-run-integration.sh"
echo " - Docker Desktop: docker-credential-desktop erase ghcr.io"
echo " - macOS Keychain: delete 'ghcr.io' entry in login keychain"
echo " - Then: ./scripts/login-and-run-integration.sh"
Loading