- secrets/bookkeeping-sa.json.enc: team service-account key, encrypted with AES-256-CBC + PBKDF2(100k iter) using a 48-char random passphrase. Safe to commit to a public repo; the passphrase lives in the team password manager. - scripts/decrypt-key.sh: one-liner that decrypts to ~/.config/gcp/ (mode 600) and prints the service-account email so users know which address to share their Sheet with. - secrets/README.md: explains the crypto, decrypt flow, and rotation procedures (passphrase rotation vs underlying GCP key rotation). - README + DEPLOY.md + setup.md: install flow updated. Users no longer wait for the admin to send a JSON; they git clone, run decrypt-key.sh with the passphrase from the team password manager, and continue. Cuts one out-of-band file transfer from the user experience. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
82 lines
2.8 KiB
Markdown
82 lines
2.8 KiB
Markdown
# secrets/
|
|
|
|
This directory holds the team's shared Google Cloud service-account key,
|
|
encrypted with **AES-256-CBC + PBKDF2 (100k iterations)** using a single
|
|
team passphrase.
|
|
|
|
| File | What it is |
|
|
|---|---|
|
|
| `bookkeeping-sa.json.enc` | The SA private key, encrypted. Safe to commit. |
|
|
|
|
## To decrypt (end users)
|
|
|
|
```bash
|
|
bash scripts/decrypt-key.sh
|
|
```
|
|
|
|
You'll be prompted for the team passphrase. Ask your admin if you don't have
|
|
it — it's stored in the team password manager (1Password / Bitwarden), never
|
|
in this repo or in chat history.
|
|
|
|
On success the decrypted JSON lands at `~/.config/gcp/bookkeeping-sa.json`
|
|
with mode 600, and the script prints the service-account email you need to
|
|
share your Sheet with.
|
|
|
|
## To rotate the passphrase (admin)
|
|
|
|
```bash
|
|
# Decrypt with old passphrase
|
|
openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -d \
|
|
-in secrets/bookkeeping-sa.json.enc \
|
|
-out /tmp/sa.json
|
|
|
|
# Re-encrypt with new passphrase
|
|
openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -salt \
|
|
-in /tmp/sa.json \
|
|
-out secrets/bookkeeping-sa.json.enc
|
|
|
|
shred -u /tmp/sa.json # or: rm -P on macOS
|
|
|
|
git add secrets/bookkeeping-sa.json.enc
|
|
git commit -m "secrets: rotate passphrase"
|
|
git push
|
|
```
|
|
|
|
Then update the passphrase in the team password manager and notify users.
|
|
|
|
## To rotate the SA key itself (admin)
|
|
|
|
When the underlying GCP key needs replacement (key leak, periodic rotation,
|
|
team member departure):
|
|
|
|
1. In GCP Console → Service Accounts → `bookkeeping@autoacct...` → Keys → **Add Key** → JSON → download.
|
|
2. **Delete the old key** in the same panel.
|
|
3. Re-encrypt:
|
|
```bash
|
|
openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -salt \
|
|
-in ~/Downloads/<new-key>.json \
|
|
-out secrets/bookkeeping-sa.json.enc
|
|
```
|
|
4. `git add` + `commit` + `push`.
|
|
5. Tell users to `git pull` and re-run `bash scripts/decrypt-key.sh`.
|
|
|
|
## Why this works
|
|
|
|
- AES-256 + PBKDF2 with 100k iterations makes brute-forcing infeasible for a
|
|
strong (>32 char) random passphrase, even with the ciphertext public.
|
|
- Encrypted blob in git history means anyone with the passphrase can install
|
|
with just `git clone` + run the decrypt script — no out-of-band file
|
|
transfer needed.
|
|
- Passphrase distribution is the only remaining out-of-band step; that
|
|
belongs in a password manager.
|
|
|
|
## Threats this does NOT protect against
|
|
|
|
- Anyone with the passphrase can read/write **all team sheets** shared with
|
|
the service account. The passphrase is the team's collective trust boundary.
|
|
- If the passphrase leaks publicly: rotate both passphrase **and** the
|
|
underlying GCP key (see above). Don't just rotate the passphrase.
|
|
- A user who has previously decrypted the key has a plaintext copy on their
|
|
machine. Removing them from the team requires rotating the GCP key (the
|
|
passphrase rotation is not enough — they already have a decrypted JSON).
|