SOP

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. 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. 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. 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. 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. 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. 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.
      1. 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)
      2. Schedule: set Scenario scheduling to run “Monthly on day 1, 09:00” for high‑risk and “Every 90 days, 09:00” for baseline.
    • 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)
    1. Simple text
    {"text":"Rotation due: [TIER] tokens. Open Secrets Register → filter next_due_at ≤ today. Clients: [CLIENT_LIST]. Owner: [OWNER]."}
    
    1. 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. 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. 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. 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. 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. 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. 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. 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.