Skip to content

docs: fix \n in Mermaid diagrams — replace with <br/> and flatten edg… #7

docs: fix \n in Mermaid diagrams — replace with <br/> and flatten edg…

docs: fix \n in Mermaid diagrams — replace with <br/> and flatten edg… #7

Workflow file for this run

name: Integration
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
integration:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Build
run: make build
- name: Install age
run: |
GOBIN=/usr/local/bin go install filippo.io/age/cmd/age-keygen@v1.3.1
GOBIN=/usr/local/bin go install filippo.io/age/cmd/age@v1.3.1
# -----------------------------------------------------------------------
# Setup: generate keys, config, and sealed secrets
# -----------------------------------------------------------------------
- name: Generate age identity
run: age-keygen -o /tmp/identity.txt
- name: Write botlockbox config
run: |
cat > /tmp/botlockbox.yaml <<'EOF'
listen: "127.0.0.1:8080"
secrets_file: /tmp/secrets.age
rules:
- name: github-api
match:
hosts:
- "api.github.com"
inject:
headers:
Authorization: "Bearer {{secrets.github_token}}"
EOF
# Seal via --identity (X25519 path).
- name: Seal GITHUB_TOKEN into secrets.age via --identity
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
printf 'github_token: "%s"\n' "$GITHUB_TOKEN" \
| ./bin/botlockbox seal \
--config /tmp/botlockbox.yaml \
--identity /tmp/identity.txt
# Seal via --recipient (plugin-key path, used with age-plugin-se on macOS).
# age-keygen writes "# public key: age1..." as a comment; extract it and
# re-seal using the public key string directly.
- name: Seal GITHUB_TOKEN into secrets.age via --recipient
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PUBKEY=$(grep '# public key:' /tmp/identity.txt | awk '{print $NF}')
printf 'github_token: "%s"\n' "$GITHUB_TOKEN" \
| ./bin/botlockbox seal \
--config /tmp/botlockbox.yaml \
--recipient "$PUBKEY"
# -----------------------------------------------------------------------
# Start proxy via --identity-stdin (key never touches disk after this point)
# -----------------------------------------------------------------------
- name: Start botlockbox serve via --identity-stdin
run: |
cat /tmp/identity.txt | ./bin/botlockbox serve \
--config /tmp/botlockbox.yaml \
--identity-stdin \
--ca-cert /tmp/botlockbox-ca.pem \
--pidfile /tmp/botlockbox.pid &
- name: Wait for proxy to be ready
run: |
for i in $(seq 1 40); do
if nc -z 127.0.0.1 8080 2>/dev/null; then
echo "Proxy is ready (attempt $i)"
exit 0
fi
sleep 0.25
done
echo "Proxy did not become ready in time" >&2
exit 1
- name: Trust botlockbox CA system-wide
run: |
sudo cp /tmp/botlockbox-ca.pem /usr/local/share/ca-certificates/botlockbox.crt
sudo update-ca-certificates
# -----------------------------------------------------------------------
# Baseline assertions (no proxy)
# -----------------------------------------------------------------------
# Without auth the endpoint returns 401 — confirms it's auth-gated.
- name: Baseline - no auth returns 401
run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://api.github.com/user)
echo "Status without auth: $STATUS"
[ "$STATUS" = "401" ]
# A dummy/invalid token is also rejected with 401 — confirms our test
# credential is genuinely bad and the injection is what makes it work.
- name: Baseline - dummy token without proxy returns 401
run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer dummy-credential" \
https://api.github.com/user)
echo "Status with dummy token (no proxy): $STATUS"
[ "$STATUS" = "401" ]
# -----------------------------------------------------------------------
# Injection proof
# -----------------------------------------------------------------------
# Send a dummy Authorization header through the proxy. botlockbox
# overwrites it with the sealed real token. The response must not be 401:
# 401 = injection failed (dummy token reached GitHub)
# 403 = injection worked (real Actions token reached GitHub — authenticated
# but /user requires read:user scope the Actions token lacks)
- name: Injection proof - dummy auth through proxy is not 401
run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
--proxy http://127.0.0.1:8080 \
--cacert /tmp/botlockbox-ca.pem \
-H "Authorization: Bearer dummy-credential" \
https://api.github.com/user)
echo "Status with dummy auth through proxy: $STATUS (401=failed, 403=injected)"
[ "$STATUS" != "401" ]
# -----------------------------------------------------------------------
# gh CLI through the proxy (proves MITM TLS works end-to-end)
# -----------------------------------------------------------------------
# gh makes an HTTPS request through the MITM proxy. The system CA store
# trusts the botlockbox ephemeral cert. The proxy injects the real token
# (overwriting whatever gh sends). The repo endpoint is accessible with
# the Actions token and returns the repo name.
- name: gh CLI through proxy returns repo name
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HTTPS_PROXY: http://127.0.0.1:8080
run: |
NAME=$(gh api /repos/${{ github.repository }} --jq '.name')
echo "Repo name via gh through proxy: $NAME"
[ "$NAME" = "botlockbox" ]
# -----------------------------------------------------------------------
# Reload test
# -----------------------------------------------------------------------
# Re-seal (simulates a credential rotation) then send SIGHUP.
# After reload the proxy must continue to serve requests correctly.
- name: Reload secrets via SIGHUP
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
printf 'github_token: "%s"\n' "$GITHUB_TOKEN" \
| ./bin/botlockbox seal \
--config /tmp/botlockbox.yaml \
--identity /tmp/identity.txt
./bin/botlockbox reload --pidfile /tmp/botlockbox.pid
sleep 1
# Proxy must still inject correctly after reload
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
--proxy http://127.0.0.1:8080 \
--cacert /tmp/botlockbox-ca.pem \
-H "Authorization: Bearer dummy-credential" \
https://api.github.com/user)
echo "Status after reload (dummy auth through proxy): $STATUS"
[ "$STATUS" != "401" ]