Solo Secrets SOP: Per‑Client Scoping, 90‑Day Rotation Baseline, and Centralized Logs (with Make/n8n Slack reminder snippets)
A one‑page, paste‑ready SOP for solo automation consultants: pick a single vault, scope secrets per client, automate monthly/90‑day Slack reminders in Make or n8n, centralize audit logs, and route LLM/API calls through a proxy so real keys stay out of workflows.
Use this SOP to standardize secrets for a solo automation practice working across Make/n8n and custom API work. It gives you a single vault choice, per‑client scoping, rotation cadences, Slack reminders you can paste into Make or n8n, and a simple Runs table to centralize audit logs. Follow the steps in order the first time; after that, you’ll use Steps 5–10 as your recurring rhythm.
- 1
Choose your vault lane (pick one today)
Options that meet the baseline (per‑client scoping, exportable logs):
- Password manager lane: 1Password Business or Bitwarden Teams/Enterprise.
- Developer‑first lane: Doppler or Infisical. Decision criteria:
- Price/seat vs. features you’ll actually use (rotation, log retention/streaming).
- How cleanly it fits Make/n8n and your code repos (service tokens, CLI/API).
- Log export/streaming so you can centralize access history. Outcome: You commit to one vault for all clients and write it in your internal README.
- 2
Create per‑client containers and least‑privilege roles
Do this once per client.
- 1Password/Bitwarden: create an Organization → per‑client Vault/Collection. Grant just‑enough access (you, plus client‑visible items only if needed). Prefer “service” or “integration” accounts for automation over your personal account.
- Doppler/Infisical: create a Project per client → Environments (prod/stage) → Service Token(s) scoped to that project/environment with read‑only access for automations. Keep write/update rights on your operator identity only. Outcome: Every secret lives in a client‑scoped container with clear RBAC and no cross‑client bleed.
- 3
Isolate automation platforms by client
- Make: create a Team per client. Build scenarios only inside that Team. Store each app connection with a name prefix like “[CLIENT] – Stripe (svc)”. Do not reuse connections across Teams.
- n8n: enable Projects and keep one Project per client. If available, bind the Project to a unique External Secrets namespace in your vault lane. Set a custom encryption key at the host level and store it outside the n8n host. Outcome: If a workflow is compromised, blast radius is limited to one client.
- 4
Catalog your secrets (Secrets Register)
Create a lightweight register where you’ll track rotation. Use a simple CSV/DB with these columns:
- client, secret_name, provider, environment, risk_tier (high|normal), rotation_interval_days (30 for high, 90 for normal), last_rotated_at (ISO), next_due_at (ISO), owner, notes. Seed the list from your vault and automation connections. This fuels your reminders and audits.
- 5
Adopt a two‑tier rotation policy you can defend
- High‑risk (monthly/30 days): LLM/API provider keys, admin API keys, OAuth client secrets, any key used from insecure surfaces.
- Baseline (90 days): standard service tokens and API keys. Notes: This aligns to common cloud defaults (90 days) while staying risk‑based. Document any exception with a reason and a compensating control (e.g., short‑lived OAuth, IP allowlisting).
- 6
Wire Slack reminders in Make (monthly and 90‑day)
Build two tiny scenarios (one monthly for high‑risk; one every 90 days for baseline). Common setup
- Channel: #security‑ops (or DM yourself while testing).
- Option A (fastest): Slack Incoming Webhook + HTTP module.
- Tools → HTTP "Make a request":
- Method: POST
- URL: [SLACK_WEBHOOK_URL]
- Headers: Content‑Type: application/json
- Body type: Raw
- Body (paste one of the payloads below)
- Schedule: set Scenario scheduling to run “Monthly on day 1, 09:00” for high‑risk and “Every 90 days, 09:00” for baseline.
- Tools → HTTP "Make a request":
- Option B: Slack > Create a Message module with a Slack app connection; paste the text version below into “Text,” and set channel. Copy‑paste payloads (choose one style)
- Simple text
{"text":"Rotation due: [TIER] tokens. Open Secrets Register → filter next_due_at ≤ today. Clients: [CLIENT_LIST]. Owner: [OWNER]."}- Block Kit (richer)
{ "blocks": [ {"type":"header","text":{"type":"plain_text","text":"[TIER] rotation window is open"}}, {"type":"section","text":{"type":"mrkdwn","text":"• Open the Secrets Register and rotate any items with `next_due_at ≤ today`\n• Scope: [TIER_DESC]\n• Owner: [OWNER]\n• Clients: [CLIENT_LIST]"}}, {"type":"context","elements":[{"type":"mrkdwn","text":"Ref: Vault policy – 30d high‑risk, 90d baseline."}]} ] }Replace placeholders: [SLACK_WEBHOOK_URL], [TIER] (High‑risk/Baseline), [TIER_DESC] (e.g., LLM/admin vs. standard), [CLIENT_LIST], [OWNER]. Outcome: You get a predictable Slack nudge without touching each workflow.
- 7
Wire Slack reminders in n8n (daily check or fixed cadence)
Option A: fixed cadence (fastest)
- Nodes: Cron → HTTP Request
- Cron: Monthly on day 1 at 09:00 (high‑risk). Duplicate workflow with “every 90 days” for baseline.
- HTTP Request: POST to [SLACK_WEBHOOK_URL], Content‑Type application/json, JSON Body = one of the payloads in Step 6. Option B: daily check (dynamic)
- Nodes: Cron (daily 09:00) → HTTP Request (GET your Secrets Register JSON/CSV) → Function (filter
next_due_at ≤ today) → IF (any due?) → HTTP Request (POST Slack payload with a bullet list of due items). - Function example (paste into Function node):
const rows = items[0].json.rows || []; const today = new Date().toISOString().slice(0,10); const due = rows.filter(r => (r.next_due_at||'').slice(0,10) <= today); return due.map(d => ({ json: { text: `Rotate: ${d.client} • ${d.secret_name} (${d.environment}) • owner ${d.owner}` }}));Outcome: Slack only fires when something is actually due.
- 8
Enable and plan audit log export/streaming
- Bitwarden: turn on Organization Event Logs; schedule a monthly export or API pull.
- 1Password Business: ensure usage/audit events are enabled; export monthly (or via API if available on your plan).
- Doppler: confirm Activity Log retention on your plan; enable Log Forwarding on higher tiers if you need longer retention.
- Infisical: enable Audit Logs; set up Audit Log Streams to S3/SIEM if available.
- Make: on Enterprise, enable Audit Logs and plan a weekly export via API for the Teams you use.
- n8n: if your tier supports log streaming, forward audit/AI logs; otherwise export server logs weekly. Outcome: Every system that touches secrets produces an export you can land in one place.
- 9
Create a central Runs table (destination for all logs)
Use any warehouse/DB you already have (SQLite, Postgres, Airtable, Notion DB). Recommended SQL schema:
CREATE TABLE runs ( id TEXT PRIMARY KEY, ts TIMESTAMPTZ NOT NULL, source TEXT NOT NULL, -- bitwarden|1password|doppler|infisical|make|n8n|custom client TEXT, -- derived from team/project name or tag project TEXT, -- automation project or scenario name actor TEXT, -- user, service token, or system action TEXT NOT NULL, -- e.g., secret.read, secret.write, conn.authorize resource_type TEXT, -- vault_item|service_token|connection|workflow resource_id TEXT, ip TEXT, user_agent TEXT, team TEXT, -- Make team or n8n project metadata JSONB, -- raw vendor event for traceability risk_score INT DEFAULT 0, correlation_id TEXT ); CREATE INDEX runs_ts_idx ON runs (ts DESC); CREATE INDEX runs_client_idx ON runs (client); CREATE INDEX runs_action_idx ON runs (action);Normalization tips:
- Map vendor event fields into action/resource_type (e.g., Bitwarden “Cipher Viewed” → secret.read).
- Derive client/team from naming conventions: “[CLIENT] – …”. Weekly check query:
SELECT client, action, COUNT(*) AS events, MIN(ts) AS first_seen, MAX(ts) AS last_seen FROM runs WHERE ts >= now() - interval '7 days' GROUP BY 1,2 ORDER BY events DESC;Outcome: One table to review who touched what, when, and where.
- 10
Plumb exports into the Runs table
- Start simple: drop vendor CSVs into a folder and run a weekly import (Make/n8n scheduled) that upserts into runs.
- Better: call vendor APIs (Make Audit Logs, Bitwarden/Infisical/Doppler) nightly and stream JSON straight into runs.
- Always keep the raw vendor payload in metadata for forensics. Outcome: A rolling, queryable audit trail without extra headcount.
- 11
Adopt a BYO‑key/proxy pattern for LLM/API calls
Goal: keep provider “real” keys out of Make/n8n. Pattern:
- Store provider key in your vault.
- Expose a tiny proxy endpoint that accepts a short‑lived token and forwards the call using the real key server‑side.
- Issue per‑client tokens with least‑privilege scopes. Minimal example (Node/Express):
// env: PROVIDER_KEY in your vault; TOKEN_SECRET for HMAC app.post('/proxy/openai', (req,res) => { const token = req.headers['x-proxy-token'] || ''; if (!isValid(token)) return res.status(401).end(); fetch('https://api.openai.com/v1/chat/completions', { method:'POST', headers:{ 'Authorization':`Bearer ${process.env.PROVIDER_KEY}`, 'Content-Type':'application/json' }, body: JSON.stringify(req.body) }).then(r=>r.text()).then(t=>res.type('json').send(t)); });Rotation win: you rotate PROVIDER_KEY once in the vault; Make/n8n never see it.
- 12
Test windows, cutover, and rollback
- Before rotating, create the new key/token and validate in a test scenario.
- Add a health check step to your reminder workflows that pings a known‑good endpoint; fail → alert.
- Keep the old key for 24 hours (or your provider’s grace) with reduced scope; then revoke.
- Document breakage fixes in the Secrets Register notes. Outcome: Rotations are boring and reversible.
- 13
Quarterly review (30 minutes)
- Verify every client has: vault project/collection, Make Team, n8n Project, Secrets Register row(s).
- Spot‑check Runs table for gaps (no events from a source? fix exporters).
- Re‑rank risk tiers if usage changed (e.g., an LLM key now gates production load). Outcome: Your posture stays aligned to reality without over‑rotating.