secrets: ship encrypted SA key, switch install to git-clone + decrypt
- 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>
This commit is contained in:
82
DEPLOY.md
82
DEPLOY.md
@@ -16,15 +16,14 @@ You will keep your own Google Sheet (only you can read it). A small "service acc
|
||||
|
||||
## Before you start: a 30-second checklist
|
||||
|
||||
Make sure you have all five of these. If any is missing, stop and get it before continuing.
|
||||
Make sure you have all four of these. If any is missing, stop and get it before continuing.
|
||||
|
||||
| You need | How to check |
|
||||
|---|---|
|
||||
| **A Google account** | You can sign in to gmail.com |
|
||||
| **A Mac or Linux computer** | This guide uses Mac. Linux is identical. **Windows users**: use WSL (Windows Subsystem for Linux), or ask your AI agent for the Windows-equivalent commands |
|
||||
| **OpenClaw (or another AI client) installed** | You already use it to chat with an AI |
|
||||
| **A JSON file your admin sent you** | A file usually named `autoacct-sa.json` — typically delivered through a password manager, encrypted email, or Slack DM. **If you don't have this, message your admin first.** |
|
||||
| **A service-account email your admin gave you** | Looks like `autoacct@something.iam.gserviceaccount.com`. Your admin sent this together with the JSON. |
|
||||
| **A passphrase from your admin** | A ~48-character random string, typically delivered through a password manager (1Password / Bitwarden). **If you don't have it, message your admin first.** |
|
||||
| **About 5 minutes** | Don't start during a phone call. |
|
||||
|
||||
---
|
||||
@@ -82,48 +81,57 @@ pip install google-api-python-client google-auth
|
||||
|
||||
---
|
||||
|
||||
# Part 2 — Put your admin's key file in the right place (1 minute)
|
||||
# Part 2 — Unlock the bundled service-account key (1 minute)
|
||||
|
||||
## Step 3. Find the JSON file your admin sent you
|
||||
The repo ships with the team's service-account key already encrypted (AES-256). You don't need any extra file — you just need the passphrase from your admin to unlock it.
|
||||
|
||||
Locate the file (e.g. `autoacct-sa.json`). It is typically in:
|
||||
- Your **Downloads** folder (if downloaded from email or a link)
|
||||
- A folder you exported from a password manager (1Password / Bitwarden)
|
||||
- An attachment on your desktop
|
||||
## Step 3. Have the passphrase ready
|
||||
|
||||
**Treat this file like a password.** Anyone who has it can write to any Google Sheet you've shared with your admin's service account. Don't email it to yourself, don't post it anywhere, don't put it on a USB stick.
|
||||
The passphrase is a ~48-character random string. It looks something like:
|
||||
|
||||
## Step 4. Move the JSON file to its permanent home
|
||||
|
||||
Paste this into the Terminal (assuming the file is in your Downloads folder and named `autoacct-sa.json`):
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/gcp
|
||||
mv ~/Downloads/autoacct-sa.json ~/.config/gcp/autoacct-sa.json
|
||||
chmod 600 ~/.config/gcp/autoacct-sa.json
|
||||
```
|
||||
d03wb3gAnXyo2N8e6FYGIUTNUd3-rFu-UxuEYbVWgOOZxZnG
|
||||
```
|
||||
|
||||
**What you should see:** nothing visible. The Terminal returns silently — that means it worked.
|
||||
Your admin will have stored it in the team password manager (1Password / Bitwarden). Open that, find the entry called something like "AutoACCT decrypt passphrase", and **copy it now**. You'll paste it in the next step.
|
||||
|
||||
**If you see "No such file or directory":** the file isn't in `~/Downloads`. Find where it actually is, and adjust the `mv` command. For example, if it's on your Desktop:
|
||||
If you can't find it, message your admin before continuing.
|
||||
|
||||
## Step 4. Run the decrypt script
|
||||
|
||||
Paste this into the Terminal:
|
||||
|
||||
```bash
|
||||
mv ~/Desktop/autoacct-sa.json ~/.config/gcp/autoacct-sa.json
|
||||
bash ~/.openclaw/workspace/skills/AutoACCT/scripts/decrypt-key.sh
|
||||
```
|
||||
|
||||
**If the file has a different name** (e.g. `autoacct-project-12345.json`): use that name in the `mv` command, but **rename it to `autoacct-sa.json` while moving**:
|
||||
The Terminal prompts:
|
||||
|
||||
```bash
|
||||
mv ~/Downloads/autoacct-project-12345.json ~/.config/gcp/autoacct-sa.json
|
||||
```
|
||||
enter AES-256-CBC decryption password:
|
||||
```
|
||||
|
||||
**Verify:** paste this into the Terminal:
|
||||
**Paste the passphrase from Step 3.** (Right-click → Paste, or Cmd+V. The characters won't appear on screen — that's normal; it's hiding your password.) Press **Enter**.
|
||||
|
||||
**What success looks like:**
|
||||
```
|
||||
Decrypted to /Users/you/.config/gcp/bookkeeping-sa.json
|
||||
|
||||
Service-account email: bookkeeping@autoacct.iam.gserviceaccount.com
|
||||
Next step: share your Google Sheet with this email (Editor).
|
||||
```
|
||||
|
||||
**Write down the service-account email** — you'll paste it in Step 8.
|
||||
|
||||
**If you see `bad decrypt`:** the passphrase is wrong. Either you mistyped it, or your admin sent a different one. Try again with a fresh paste.
|
||||
|
||||
**Verify:**
|
||||
|
||||
```bash
|
||||
ls -la ~/.config/gcp/
|
||||
```
|
||||
|
||||
You should see one line containing `autoacct-sa.json` with permissions `-rw-------`.
|
||||
You should see one line containing `bookkeeping-sa.json` with permissions `-rw-------`.
|
||||
|
||||
---
|
||||
|
||||
@@ -156,14 +164,14 @@ You'll paste this exact name into `config.json` in Step 10. **Tab name and confi
|
||||
|
||||
**Verify:** Column A says `Date`, Column N (scroll right if needed) says `Logged At`.
|
||||
|
||||
## Step 8. Share the sheet with your admin's service account
|
||||
## Step 8. Share the sheet with the service account
|
||||
|
||||
This is the step that lets the skill write into your sheet.
|
||||
|
||||
1. Click the green **Share** button (top right of the sheet).
|
||||
2. In the "Add people, groups, and calendar events" box, **paste the service-account email your admin gave you**. It looks something like:
|
||||
2. In the "Add people, groups, and calendar events" box, **paste the service-account email that `decrypt-key.sh` printed back in Step 4**. It looks like:
|
||||
```
|
||||
autoacct@your-project-12345.iam.gserviceaccount.com
|
||||
bookkeeping@autoacct.iam.gserviceaccount.com
|
||||
```
|
||||
3. Make sure the role on the right says **Editor** (not Viewer / Commenter).
|
||||
4. **Uncheck "Notify people"** — there's no real person on the other end of that email.
|
||||
@@ -212,7 +220,7 @@ Nothing visible happens — that's expected. The Terminal just made a copy of th
|
||||
{
|
||||
"sheet_id": "PASTE_YOUR_GOOGLE_SHEET_URL_OR_ID_HERE",
|
||||
"worksheet": "Sheet1",
|
||||
"service_account_path": "~/.config/gcp/autoacct-sa.json",
|
||||
"service_account_path": "~/.config/gcp/bookkeeping-sa.json",
|
||||
"hkd_fx_provider": "frankfurter"
|
||||
}
|
||||
```
|
||||
@@ -229,7 +237,7 @@ Nothing visible happens — that's expected. The Terminal just made a copy of th
|
||||
{
|
||||
"sheet_id": "https://docs.google.com/spreadsheets/d/1abcDEF...xyz123/edit#gid=0",
|
||||
"worksheet": "Sheet1",
|
||||
"service_account_path": "~/.config/gcp/autoacct-sa.json",
|
||||
"service_account_path": "~/.config/gcp/bookkeeping-sa.json",
|
||||
"hkd_fx_provider": "frankfurter"
|
||||
}
|
||||
```
|
||||
@@ -266,10 +274,10 @@ Switch to your Google Sheet — there's a new row with `TEST` as the merchant. *
|
||||
|
||||
| You see | What it means | How to fix |
|
||||
|---|---|---|
|
||||
| `HTTP 403` or `The caller does not have permission` | You forgot Step 8 (sharing the sheet with the service-account email), or the email was typed incorrectly | Re-share the sheet with the exact email your admin gave you, role Editor. |
|
||||
| `HTTP 403` or `The caller does not have permission` | You forgot Step 8 (sharing the sheet with the service-account email), or the email was typed incorrectly | Re-run `bash scripts/decrypt-key.sh` to print the email again, then re-share the sheet with that exact address, role Editor. |
|
||||
| `HTTP 400: Unable to parse range` | The `worksheet` in `config.json` doesn't match the actual tab name | Open `config.json`, fix it to exactly match the tab name at the bottom-left of your sheet (`Sheet1` or `工作表1`). |
|
||||
| `HTTP 404` or `Requested entity was not found` | The `sheet_id` in `config.json` is wrong | Open your sheet in the browser, copy the **full URL** from the address bar, paste it into `sheet_id` (replacing whatever's there). |
|
||||
| `FileNotFoundError ... autoacct-sa.json` | The JSON file isn't where `config.json` says it is | Run `ls -la ~/.config/gcp/` — confirm the file is there with the exact name `autoacct-sa.json`. If it has a different name, either rename it or update `service_account_path` in `config.json`. |
|
||||
| `FileNotFoundError ... bookkeeping-sa.json` | The JSON file isn't where `config.json` says it is | Run `ls -la ~/.config/gcp/` — confirm the file is there with the exact name `bookkeeping-sa.json`. If it has a different name, either rename it or update `service_account_path` in `config.json`. |
|
||||
| `ImportError: No module named 'googleapiclient'` | You skipped or partially failed the `pip install` step | Re-run `pip install google-api-python-client google-auth`. If that fails, try `pip3` or `python3 -m pip install ...`. |
|
||||
| `config.json not found` | You skipped Step 10 | Run `cp config.example.json config.json` from inside the skill folder. |
|
||||
| `Expecting value: line 1 column 1` (JSON error) | Your `config.json` has smart quotes or is otherwise malformed | Re-open with TextEdit in plain-text mode (Format → Make Plain Text), or ask your AI agent to repair it. |
|
||||
@@ -284,16 +292,16 @@ If your problem is not in this table: copy the **exact error text** and paste it
|
||||
|---|---|
|
||||
| Use a different sheet | Repeat Steps 5–9 on the new sheet (including the Share-with-service-account step), then update `sheet_id` and `worksheet` in `config.json`. |
|
||||
| Stop the skill writing to a sheet | Open the sheet → Share → find the service-account email → click trash icon → Save. The skill will get HTTP 403 on the next attempt. |
|
||||
| Move to a new computer | Repeat Parts 1, 2, and 4 on the new computer (re-clone, copy the JSON, copy `config.json`). Your sheet and its sharing don't change. |
|
||||
| You think your JSON file leaked | Message your admin immediately. They can revoke the key on the GCP side; nothing they revoke can come back to bite you afterwards. |
|
||||
| Move to a new computer | Repeat Parts 1, 2, and 4 on the new computer (re-clone, re-run `decrypt-key.sh`, copy `config.json`). Your sheet and its sharing don't change. |
|
||||
| You think the decrypted JSON or passphrase leaked | Message your admin immediately. They will rotate both the passphrase **and** the underlying GCP key; you'll then `git pull` + re-run `decrypt-key.sh` with the new passphrase. |
|
||||
|
||||
---
|
||||
|
||||
# Summary of what just happened (so you understand what you set up)
|
||||
|
||||
1. **Your computer** has the AutoACCT skill in `~/.openclaw/workspace/skills/AutoACCT/`. When you drop a receipt photo into your AI chat, the AI runs `scripts/append_row.py`, which is a small Python program.
|
||||
2. **The JSON file** at `~/.config/gcp/autoacct-sa.json` is the key that authenticates the Python program as a "service account" your admin created.
|
||||
2. **The repo ships an encrypted JSON file** (`secrets/bookkeeping-sa.json.enc`) — the service-account key locked with AES-256 + your team's passphrase. `decrypt-key.sh` unlocks it into `~/.config/gcp/bookkeeping-sa.json` (mode 600). The unlocked JSON authenticates the Python program as the service account your admin created in Google Cloud.
|
||||
3. **The service account** is allowed to edit Google Sheets only when those sheets have been shared with its email. You shared *your* sheet with it in Step 8 — so it can only write to your sheet (and any other sheet you might share with it later).
|
||||
4. **Other users on your team** have the **same JSON file** but their own sheets. The service account can write to each of their sheets only because each user has shared *their own* sheet with it. They cannot see your sheet, and you cannot see theirs.
|
||||
4. **Other users on your team** decrypt the **same JSON file** with the **same passphrase** but write to **their own sheets**. The service account can write to each of their sheets only because each user has shared *their own* sheet with it. They cannot see your sheet, and you cannot see theirs — unless someone explicitly shares.
|
||||
|
||||
That's it. Welcome to AutoACCT.
|
||||
|
||||
33
README.md
33
README.md
@@ -16,7 +16,7 @@ Intended to be invoked manually inside OpenClaw today, and wired up to a WhatsAp
|
||||
|
||||
## Install (end users)
|
||||
|
||||
Your admin will have given you **two things**: a service-account JSON key file (e.g. `autoacct-sa.json`) and a service-account email (e.g. `autoacct@your-project.iam.gserviceaccount.com`). If you don't have them, ask your admin first.
|
||||
The repo bundles the team's Google service-account key, encrypted with AES-256. Ask your admin for **one thing**: the passphrase (it's in your team's password manager).
|
||||
|
||||
Follow the 4 steps below. Takes ~5 minutes.
|
||||
|
||||
@@ -26,18 +26,17 @@ Follow the 4 steps below. Takes ~5 minutes.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/CharlesZhang2023/AutoACCT.git ~/.openclaw/workspace/skills/AutoACCT
|
||||
cd ~/.openclaw/workspace/skills/AutoACCT
|
||||
pip install google-api-python-client google-auth
|
||||
```
|
||||
|
||||
### Step 2 — Drop the admin's JSON key into `~/.config/gcp/`
|
||||
### Step 2 — Decrypt the bundled service-account key
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/gcp
|
||||
mv ~/Downloads/autoacct-sa.json ~/.config/gcp/autoacct-sa.json
|
||||
chmod 600 ~/.config/gcp/autoacct-sa.json
|
||||
bash scripts/decrypt-key.sh
|
||||
```
|
||||
|
||||
(Replace `~/Downloads/autoacct-sa.json` with wherever you saved the file your admin sent.)
|
||||
You'll be prompted for the passphrase. On success the script writes the JSON to `~/.config/gcp/bookkeeping-sa.json` (mode 600) and prints the **service-account email** — copy it; you'll paste it into Step 3.
|
||||
|
||||
### Step 3 — Create your Google Sheet and share it with the service account
|
||||
|
||||
@@ -48,7 +47,7 @@ chmod 600 ~/.config/gcp/autoacct-sa.json
|
||||
```
|
||||
Date Merchant Category Amount Currency Amount (HKD) FX Rate FX Date Payment Method Line Items Raw OCR Note Receipt Logged At
|
||||
```
|
||||
5. Click **Share** (top right) → paste the **service-account email** your admin gave you → role **Editor** → **Send** (you can uncheck "Notify people").
|
||||
5. Click **Share** (top right) → paste the **service-account email** that `decrypt-key.sh` printed in Step 2 → role **Editor** → **Send** (you can uncheck "Notify people").
|
||||
6. **Copy the full URL from your browser's address bar.** Something like:
|
||||
`https://docs.google.com/spreadsheets/d/1abc...xyz/edit#gid=0`
|
||||
(The script extracts the sheet ID for you — either the full URL or just the bare ID works.)
|
||||
@@ -66,7 +65,7 @@ Open `config.json` and fill in **sheet_id** (paste the URL from Step 3.6) and **
|
||||
{
|
||||
"sheet_id": "https://docs.google.com/spreadsheets/d/1abc...xyz/edit",
|
||||
"worksheet": "Sheet1",
|
||||
"service_account_path": "~/.config/gcp/autoacct-sa.json",
|
||||
"service_account_path": "~/.config/gcp/bookkeeping-sa.json",
|
||||
"hkd_fx_provider": "frankfurter"
|
||||
}
|
||||
```
|
||||
@@ -85,11 +84,12 @@ If you hit an error, see [`scripts/setup.md`](scripts/setup.md) for troubleshoot
|
||||
|
||||
## Admin setup (one time, done by you before distributing)
|
||||
|
||||
Before users can run the steps above, **you** (the admin) create one shared service account and distribute the JSON to users. See [`scripts/setup.md`](scripts/setup.md) for the full admin guide — short version:
|
||||
See [`scripts/setup.md`](scripts/setup.md) for the full admin guide and [`secrets/README.md`](secrets/README.md) for the encryption mechanics. Short version:
|
||||
|
||||
1. Create a GCP project, enable Sheets API, create a service account, download the JSON key.
|
||||
2. Distribute the JSON file + the service-account email to your users via a secure channel (1Password / Bitwarden / encrypted email — **never commit to git**).
|
||||
3. Tell users to follow the 4 steps above.
|
||||
2. Encrypt the JSON with a strong random passphrase and commit `secrets/bookkeeping-sa.json.enc` to the repo (see `secrets/README.md` for the openssl one-liner).
|
||||
3. Store the passphrase in your team password manager. Tell users to follow the 4 install steps above.
|
||||
4. Rotate the passphrase when team members leave; rotate the underlying GCP key when the passphrase or any decrypted JSON might have leaked.
|
||||
|
||||
## Use
|
||||
|
||||
@@ -105,10 +105,13 @@ Caption is optional; use it to add context (payment method, split, category hint
|
||||
| `categories.md` | Fixed category list (14 categories) |
|
||||
| `schema.md` | Google Sheet column order (A–N) |
|
||||
| `config.example.json` | Template → copy to `config.json` (gitignored) |
|
||||
| `scripts/fx_convert.py` | Currency → HKD via frankfurter.app |
|
||||
| `scripts/append_row.py` | Writes one row to Google Sheets |
|
||||
| `scripts/setup.md` | Admin setup guide + troubleshooting |
|
||||
| `DEPLOY.md` | Step-by-step install guide for non-technical users |
|
||||
| `scripts/fx_convert.py` | Currency → HKD via frankfurter.app |
|
||||
| `scripts/append_row.py` | Writes one row to Google Sheets |
|
||||
| `scripts/decrypt-key.sh` | Decrypts bundled SA key to `~/.config/gcp/` |
|
||||
| `scripts/setup.md` | Admin setup guide + troubleshooting |
|
||||
| `secrets/bookkeeping-sa.json.enc` | Team SA key, AES-256 encrypted (safe to commit) |
|
||||
| `secrets/README.md` | How the encryption works + rotation procedures |
|
||||
| `DEPLOY.md` | Step-by-step install guide for non-technical users |
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
## 安装(用户端)
|
||||
|
||||
管理员(admin)会给你**两样东西**:一个 service-account JSON 密钥文件(如 `autoacct-sa.json`),以及一个 service-account 邮箱(形如 `autoacct@your-project.iam.gserviceaccount.com`)。没拿到先找管理员要。
|
||||
仓库里**自带了团队的 Google service-account 密钥**,已用 AES-256 加密。管理员只需给你**一样东西**:解密 passphrase(一般在团队密码管理器里)。
|
||||
|
||||
按下面 4 步操作,约 5 分钟。
|
||||
|
||||
@@ -26,18 +26,17 @@
|
||||
|
||||
```bash
|
||||
git clone https://github.com/CharlesZhang2023/AutoACCT.git ~/.openclaw/workspace/skills/AutoACCT
|
||||
cd ~/.openclaw/workspace/skills/AutoACCT
|
||||
pip install google-api-python-client google-auth
|
||||
```
|
||||
|
||||
### Step 2 — 把管理员发的 JSON 放到 `~/.config/gcp/`
|
||||
### Step 2 — 解密内置的 service-account 密钥
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/gcp
|
||||
mv ~/Downloads/autoacct-sa.json ~/.config/gcp/autoacct-sa.json
|
||||
chmod 600 ~/.config/gcp/autoacct-sa.json
|
||||
bash scripts/decrypt-key.sh
|
||||
```
|
||||
|
||||
(`~/Downloads/autoacct-sa.json` 改成你实际保存文件的路径。)
|
||||
会提示你输入 passphrase。成功后脚本会把解出的 JSON 写到 `~/.config/gcp/bookkeeping-sa.json`(权限 600),并打印 **service-account 邮箱** —— 复制下来,Step 3 要用。
|
||||
|
||||
### Step 3 — 建你自己的 Google Sheet 并把它 share 给 service account
|
||||
|
||||
@@ -48,7 +47,7 @@ chmod 600 ~/.config/gcp/autoacct-sa.json
|
||||
```
|
||||
Date Merchant Category Amount Currency Amount (HKD) FX Rate FX Date Payment Method Line Items Raw OCR Note Receipt Logged At
|
||||
```
|
||||
5. 右上角 **Share** → 粘贴管理员给你的 **service-account 邮箱** → 权限选 **Editor** → **Send**("Notify people" 可以不勾)
|
||||
5. 右上角 **Share** → 粘贴 Step 2 `decrypt-key.sh` 打印出来的 **service-account 邮箱** → 权限选 **Editor** → **Send**("Notify people" 可以不勾)
|
||||
6. **从浏览器地址栏直接复制 sheet 的完整 URL**,类似:
|
||||
`https://docs.google.com/spreadsheets/d/1abc...xyz/edit#gid=0`
|
||||
(脚本会自动从 URL 里抽出 sheet ID,所以完整链接或裸 ID 都行。)
|
||||
@@ -66,7 +65,7 @@ cp config.example.json config.json
|
||||
{
|
||||
"sheet_id": "https://docs.google.com/spreadsheets/d/1abc...xyz/edit",
|
||||
"worksheet": "Sheet1",
|
||||
"service_account_path": "~/.config/gcp/autoacct-sa.json",
|
||||
"service_account_path": "~/.config/gcp/bookkeeping-sa.json",
|
||||
"hkd_fx_provider": "frankfurter"
|
||||
}
|
||||
```
|
||||
@@ -83,13 +82,14 @@ echo '{"date":"2026-04-20","merchant":"TEST","category":"Other","amount":1,"curr
|
||||
|
||||
遇到报错可以参考 [`scripts/setup.md`](scripts/setup.md) 的故障排查。
|
||||
|
||||
## 管理员一次性配置(你做一遍,再把 JSON 发给用户)
|
||||
## 管理员一次性配置
|
||||
|
||||
用户能跑上面 4 步之前,**你(管理员)**先建好一个共享的 service account,把 JSON 发给用户。完整管理员指南见 [`scripts/setup.md`](scripts/setup.md),简版流程:
|
||||
完整管理员指南见 [`scripts/setup.md`](scripts/setup.md),加密机制说明见 [`secrets/README.md`](secrets/README.md)。简版:
|
||||
|
||||
1. 建 GCP 项目 → 启用 Sheets API → 建 service account → 下载 JSON key
|
||||
2. 通过安全渠道把 JSON 文件 + service-account 邮箱发给每个用户(1Password / Bitwarden / 加密邮件 ——**绝不能 commit 到 git**)
|
||||
3. 让用户按上面 4 步装
|
||||
2. 用强随机 passphrase 加密 JSON,把 `secrets/bookkeeping-sa.json.enc` commit 进仓库(openssl 一行命令见 `secrets/README.md`)
|
||||
3. 把 passphrase 存到团队密码管理器,告诉用户按上面 4 步装
|
||||
4. 成员离职时轮换 passphrase;passphrase 或解密后的 JSON 有泄露风险时,轮换底层 GCP key
|
||||
|
||||
## 使用
|
||||
|
||||
@@ -105,10 +105,13 @@ echo '{"date":"2026-04-20","merchant":"TEST","category":"Other","amount":1,"curr
|
||||
| `categories.md` | 固定的 14 个分类列表 |
|
||||
| `schema.md` | Google Sheet 列顺序(A–N) |
|
||||
| `config.example.json` | 配置模板 → 复制为 `config.json`(已 gitignore) |
|
||||
| `scripts/fx_convert.py` | 原币种 → HKD 换算(frankfurter.app) |
|
||||
| `scripts/append_row.py` | 向 Google Sheet 写入一行 |
|
||||
| `scripts/setup.md` | 管理员配置指南 + 故障排查 |
|
||||
| `DEPLOY.md` | 面向非技术用户的逐步安装指南(英文) |
|
||||
| `scripts/fx_convert.py` | 原币种 → HKD 换算(frankfurter.app) |
|
||||
| `scripts/append_row.py` | 向 Google Sheet 写入一行 |
|
||||
| `scripts/decrypt-key.sh` | 解密内置 SA key 到 `~/.config/gcp/` |
|
||||
| `scripts/setup.md` | 管理员配置指南 + 故障排查 |
|
||||
| `secrets/bookkeeping-sa.json.enc` | 团队 SA key,AES-256 加密(可安全 commit) |
|
||||
| `secrets/README.md` | 加密机制说明 + 轮换流程 |
|
||||
| `DEPLOY.md` | 面向非技术用户的逐步安装指南(英文) |
|
||||
|
||||
## License
|
||||
|
||||
|
||||
35
scripts/decrypt-key.sh
Executable file
35
scripts/decrypt-key.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
# decrypt-key.sh — decrypt the bundled service-account key into
|
||||
# ~/.config/gcp/bookkeeping-sa.json. You'll be prompted for the passphrase
|
||||
# (ask your admin; it's stored in the team password manager).
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/decrypt-key.sh
|
||||
#
|
||||
# Idempotent: re-running overwrites the existing decrypted file.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SRC="$REPO_DIR/secrets/bookkeeping-sa.json.enc"
|
||||
DEST_DIR="$HOME/.config/gcp"
|
||||
DEST="$DEST_DIR/bookkeeping-sa.json"
|
||||
|
||||
if [[ ! -f "$SRC" ]]; then
|
||||
echo "error: encrypted key not found at $SRC" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$DEST_DIR"
|
||||
|
||||
# AES-256-CBC + PBKDF2 (100k iter) + salt. Passphrase read interactively.
|
||||
openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -d -in "$SRC" -out "$DEST"
|
||||
|
||||
chmod 600 "$DEST"
|
||||
echo "Decrypted to $DEST"
|
||||
|
||||
SA_EMAIL=$(python3 -c "import json; print(json.load(open('$DEST'))['client_email'])" 2>/dev/null || true)
|
||||
if [[ -n "$SA_EMAIL" ]]; then
|
||||
echo ""
|
||||
echo "Service-account email: $SA_EMAIL"
|
||||
echo "Next step: share your Google Sheet with this email (Editor)."
|
||||
fi
|
||||
@@ -23,39 +23,65 @@ The shared-key model: you create **one** Google Cloud service account and distri
|
||||
5. **Copy the service account's email** (e.g. `autoacct@<project>.iam.gserviceaccount.com`).
|
||||
6. Rename the downloaded file to `autoacct-sa.json` (recommended — DEPLOY.md assumes this name).
|
||||
|
||||
### A.2 — Distribute the key + email to users
|
||||
### A.2 — Encrypt the JSON and commit it to the repo
|
||||
|
||||
The JSON is a private key. Use a secure channel:
|
||||
Generate a strong random passphrase (48 chars; alphanumeric + `-_`):
|
||||
|
||||
| Channel | Verdict |
|
||||
|---|---|
|
||||
| **Password manager shared vault** (1Password, Bitwarden, Vaultwarden) | **Recommended.** Easy to revoke, no copies floating in inboxes. |
|
||||
| Encrypted email / Signal / private DM | OK for small teams. |
|
||||
| Cloud drive with strict per-user ACLs | OK if your org already uses one. |
|
||||
| **Plain email** | Do not. |
|
||||
| **Git repo (even private)** | Do not — `.gitignore` already excludes `*-sa.json`. |
|
||||
```bash
|
||||
python3 -c "import secrets, string; print(''.join(secrets.choice(string.ascii_letters + string.digits + '_-') for _ in range(48)))"
|
||||
```
|
||||
|
||||
Encrypt with openssl (AES-256-CBC + PBKDF2, 100k iterations):
|
||||
|
||||
```bash
|
||||
PASSPHRASE='<paste-passphrase-here>' openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -salt \
|
||||
-pass env:PASSPHRASE \
|
||||
-in ~/Downloads/<downloaded-key>.json \
|
||||
-out secrets/bookkeeping-sa.json.enc
|
||||
|
||||
git add secrets/bookkeeping-sa.json.enc
|
||||
git commit -m "secrets: add encrypted SA key"
|
||||
git push
|
||||
```
|
||||
|
||||
Then **store the passphrase in your team password manager** (1Password / Bitwarden shared vault). The passphrase is the only out-of-band thing your users need.
|
||||
|
||||
Move the plaintext key out of the repo dir and protect it on your own machine:
|
||||
|
||||
```bash
|
||||
mv ~/Downloads/<downloaded-key>.json ~/.config/gcp/bookkeeping-sa.json
|
||||
chmod 600 ~/.config/gcp/bookkeeping-sa.json
|
||||
```
|
||||
|
||||
Tell each user:
|
||||
- The JSON file (attach / share)
|
||||
- The service-account email (so they know who to share their sheet with)
|
||||
- Pointer to [`DEPLOY.md`](../DEPLOY.md) if they want hand-holding, or [`README.md`](../README.md) if they're comfortable with the terminal
|
||||
- A pointer to the repo (`git clone https://github.com/CharlesZhang2023/AutoACCT.git`)
|
||||
- The passphrase (via 1Password share — **never via plain email / chat**)
|
||||
- Link to [`DEPLOY.md`](../DEPLOY.md) for hand-holding or [`README.md`](../README.md) if they're comfortable in the terminal
|
||||
|
||||
### A.3 — Verify your own install first
|
||||
|
||||
Before sending the JSON to anyone, run through the user-side install yourself (Steps 1–11 of `DEPLOY.md`) to confirm everything works end-to-end. It's also a chance to catch any GCP-side misconfiguration before users hit it.
|
||||
Before announcing it to anyone, run through the user-side install yourself (`DEPLOY.md` Parts 1–4) on a clean directory to confirm `git clone` + `decrypt-key.sh` + sheet creation + smoke test all work end-to-end. Catches any GCP-side misconfiguration before users hit it.
|
||||
|
||||
### A.4 — Key rotation (when you need to)
|
||||
### A.4 — Rotation
|
||||
|
||||
Rotate the JSON key when:
|
||||
- A user leaves the team (so you stop trusting their copy of the key)
|
||||
- You suspect a leak
|
||||
- Every 6–12 months as routine hygiene
|
||||
**Passphrase rotation** (when a user leaves, or every 6–12 months):
|
||||
1. Generate a new passphrase as in A.2.
|
||||
2. Decrypt with the old passphrase, re-encrypt with the new one:
|
||||
```bash
|
||||
openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -d \
|
||||
-in secrets/bookkeeping-sa.json.enc -out /tmp/sa.json
|
||||
PASSPHRASE='<new>' openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -salt \
|
||||
-pass env:PASSPHRASE -in /tmp/sa.json -out secrets/bookkeeping-sa.json.enc
|
||||
shred -u /tmp/sa.json # rm -P on macOS
|
||||
```
|
||||
3. Commit + push the new `.enc`. Update the passphrase entry in the team password manager. Users `git pull` + re-run `decrypt-key.sh`.
|
||||
|
||||
To rotate:
|
||||
1. In GCP Console → Service Accounts → keys → **Add Key** (creates a new one) → download.
|
||||
2. **Delete the old key** in the same panel. (After deletion, any existing copies stop working.)
|
||||
3. Re-distribute the new JSON to all current users via your secure channel.
|
||||
4. Users replace their `~/.config/gcp/autoacct-sa.json` with the new file. No other changes needed — `config.json`, sheet sharing, etc. all stay intact.
|
||||
**Underlying GCP key rotation** (when the passphrase leaks, or a user with a decrypted copy leaves):
|
||||
- Passphrase rotation alone is **not enough** if someone already has the decrypted JSON on their machine — they retain a working credential.
|
||||
- GCP Console → Service Accounts → Keys → **Add Key** (download new) → **Delete old**. The deleted key stops working immediately, globally.
|
||||
- Re-encrypt the new JSON (A.2 flow), commit, push. Users pull + decrypt.
|
||||
|
||||
See [`secrets/README.md`](../secrets/README.md) for the same procedures with copy-pasteable commands.
|
||||
|
||||
---
|
||||
|
||||
@@ -70,13 +96,16 @@ The `worksheet` value in `config.json` doesn't match the actual tab name. Most c
|
||||
### `HTTP 404` / `Requested entity was not found`
|
||||
`sheet_id` in `config.json` is wrong. Tell user to re-copy the long string from `/d/.../edit` in their browser's URL bar.
|
||||
|
||||
### `FileNotFoundError ... autoacct-sa.json`
|
||||
The JSON file isn't where `config.json` expects. Common causes:
|
||||
- User saved with a different filename (e.g. `autoacct-project-12345.json`) and never renamed it.
|
||||
- User skipped the `mv` step and the file is still in Downloads.
|
||||
### `FileNotFoundError ... bookkeeping-sa.json`
|
||||
User skipped `bash scripts/decrypt-key.sh`, or decryption failed and they didn't notice. Have them re-run it and confirm the success line `Decrypted to ~/.config/gcp/bookkeeping-sa.json`.
|
||||
|
||||
Run `ls -la ~/.config/gcp/` to check.
|
||||
|
||||
### `bad decrypt` from openssl
|
||||
Wrong passphrase. Most common causes:
|
||||
- They pasted the wrong entry from the password manager.
|
||||
- The passphrase has been rotated since last time. Have them check the password manager for the latest version.
|
||||
|
||||
### `ImportError: No module named 'googleapiclient'`
|
||||
Python deps not installed. Run `pip install google-api-python-client google-auth`. If `pip` is missing, try `pip3` or `python3 -m pip install ...`.
|
||||
|
||||
|
||||
81
secrets/README.md
Normal file
81
secrets/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# 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).
|
||||
BIN
secrets/bookkeeping-sa.json.enc
Normal file
BIN
secrets/bookkeeping-sa.json.enc
Normal file
Binary file not shown.
Reference in New Issue
Block a user