docs: fix \n in Mermaid diagrams — replace with <br/> and flatten edg… #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" ] |