diff --git a/.github/workflows/export-secrets.yml b/.github/workflows/export-secrets.yml index 3b152b6..3a6c9f2 100644 --- a/.github/workflows/export-secrets.yml +++ b/.github/workflows/export-secrets.yml @@ -1,39 +1,96 @@ -name: Export & Encrypt Secrets +# .github/workflows/export-secrets.yml +name: Export & Encrypt Secrets for Actor on: workflow_dispatch: + inputs: + environment: + description: 'Target environment prefix (e.g. dev, staging, prod)' + required: true + default: 'dev' + +permissions: + contents: read # for checkout + actions: read # to list/download runs & artifacts jobs: - build: + export: runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@v3 - - name: Export all repo secrets to env + - name: Export environment-prefixed secrets uses: oNaiPs/secrets-to-env-action@v1 with: + # Pass in all your repo’s secrets as JSON: secrets: ${{ toJSON(secrets) }} - exclude: ENV_PASSPHRASE + # Skip the GITHUB_TOKEN (and any other you inject via env vars) + exclude: GITHUB_TOKEN, GH_TOKEN + + # 2) now grab the runner-injected ENV_-prefixed ones + - name: Export DEV-environment secrets + run: | + env | grep '^ENV_' >> secrets.env + + - name: Fetch GPG keys for ${{ github.actor }} + run: | + echo "πŸ”Ž Fetching public GPG keys for ${{ github.actor }}…" + gh api /users/${{ github.actor }}/gpg_keys \ + --jq '[.[] | .raw_key]' > actor_keys.json - - name: Dump selected secrets to .env + - name: Ensure actor has β‰₯1 GPG key run: | - # adjust pattern to match your secret names - env | grep -E '^(DB_|API_|OTHER_)' > secrets.env + COUNT=$(jq 'length' actor_keys.json) + if [ "$COUNT" -eq 0 ]; then + echo "❌ No public GPG keys found for ${{ github.actor }}." + echo " Please upload a key at https://github.com/settings/keys" + exit 1 + fi - - name: Encrypt .env symmetrically + - name: Import & encrypt for actor run: | - gpg --quiet --batch \ - --yes \ - --pinentry-mode loopback \ - --passphrase "${{ secrets.ENV_PASSPHRASE }}" \ - --cipher-algo AES256 \ - --symmetric \ + # 1) Import every public key block at once + echo "πŸ”‘ Importing all public keys for ${{ github.actor }}…" + jq -r '.[]' actor_keys.json \ + | gpg --batch --import + + # 2) Gather fingerprints of all imported keys + mapfile -t FPS < <( + gpg --with-colons --list-keys \ + | awk -F: '/^fpr:/ {print $10}' + ) + + # 3) Sanity check + if [[ ${#FPS[@]} -eq 0 ]]; then + echo "❌ No GPG fingerprints found after import. Aborting." >&2 + exit 1 + fi + + # 4) Build recipient args + RECIP_ARGS=() + for fp in "${FPS[@]}"; do + RECIP_ARGS+=(--recipient "$fp") + done + + # 5) Encrypt + echo "πŸ”’ Encrypting secrets.env for ${{ github.actor }} (keys: ${FPS[*]})…" + gpg --yes --batch --quiet \ + --trust-model always \ + --encrypt \ + "${RECIP_ARGS[@]}" \ --output secrets.env.gpg \ secrets.env - - name: Upload encrypted .env + + - name: Upload encrypted-secrets uses: actions/upload-artifact@v4 with: name: encrypted-secrets path: secrets.env.gpg + retention-days: 1 + overwrite: true diff --git a/bin/sync-env.sh b/bin/sync-env.sh index 1ab3c6b..2f7b6b5 100644 --- a/bin/sync-env.sh +++ b/bin/sync-env.sh @@ -1,19 +1,40 @@ #!/usr/bin/env bash set -euo pipefail -# 1. Find the most recent export run -WORKFLOW="Export & Encrypt Secrets" -RUN_ID=$(gh run list --workflow="export-secrets.yml" --limit 1 --json databaseId --jq '.[0].databaseId') +# 1) Prompt for the passphrase if ENV_PASSPHRASE isn’t already set +if [[ -z "${ENV_PASSPHRASE-}" ]]; then + read -rsp "Enter shared ENV_PASSPHRASE: " PASSPHRASE + echo +else + PASSPHRASE="$ENV_PASSPHRASE" +fi -# 2. Download the artifact -gh run download "$RUN_ID" --name encrypted-secrets --dir . +# 2) Identify the latest run of our export workflow +WORKFLOW_FILE="export-secrets.yml" +RUN_ID=$(gh run list --workflow="$WORKFLOW_FILE" --limit 1 --json databaseId --jq '.[0].databaseId') -# 3. Decrypt to .env +if [[ -z "$RUN_ID" ]]; then + echo "❌ No workflow run found for $WORKFLOW_FILE" + exit 1 +fi + +echo "πŸ“₯ Downloading artifact from run ID $RUN_ID..." + +# 3) Prepare a clean download directory +ARTIFACT_DIR="encrypted-secrets" +rm -rf "$ARTIFACT_DIR" +mkdir -p "$ARTIFACT_DIR" + +# 4) Download the encrypted .env.gpg +gh run download "$RUN_ID" --name encrypted-secrets --dir "$ARTIFACT_DIR" + +# 5) Decrypt into .env +echo "πŸ”“ Decrypting into .env..." gpg --quiet --batch \ --yes \ --pinentry-mode loopback \ - --passphrase "${ENV_PASSPHRASE:-}" \ + --passphrase "$PASSPHRASE" \ --output .env \ - --decrypt secrets.env.gpg + --decrypt "$ARTIFACT_DIR/secrets.env.gpg" echo "βœ… .env synced and ready"