-
-
Notifications
You must be signed in to change notification settings - Fork 0
Phase G ops suite: audit, 2FA, telemetry, schedule, tunnel #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
50539a1
feat(audit): append-only audit log for destructive ops
nixrajput 299a456
feat(2fa): gate destructive ops by profile-group policy
nixrajput d98f87b
feat(telemetry): opt-in aggregate op metrics
nixrajput c373975
feat(schedule): cron-managed recurring backups
nixrajput 36ea455
feat(tunnel): SSH tunnel helper to a DB via a bastion
nixrajput 002e11d
docs: document the Phase G ops suite
nixrajput 4fcb9f7
fix(ops): apply CodeRabbit review fixes
nixrajput File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| # Operational features | ||
|
|
||
| Phase G's ops suite adds five operational capabilities: an audit log, 2FA / | ||
| group gating, opt-in telemetry, scheduled backups, and an SSH tunnel helper. | ||
| The first three share one interception point in the app layer (`guardedOp`), | ||
| which wraps the destructive verbs (backup, restore, sync, prune) exactly once. | ||
|
|
||
| ## Table of contents | ||
|
|
||
| - [Audit log](#audit-log) | ||
| - [2FA & group gating](#2fa--group-gating) | ||
| - [Telemetry](#telemetry) | ||
| - [Scheduled backups](#scheduled-backups) | ||
| - [SSH tunnel](#ssh-tunnel) | ||
|
|
||
| ## Audit log | ||
|
|
||
| An append-only JSONL trail of destructive operations — who ran what, against | ||
| which profile, when, and the outcome. Off by default. | ||
|
|
||
| ```yaml | ||
| audit: | ||
| enabled: true | ||
| path: ~/.local/state/siphon/audit.log # optional; this is the default | ||
| ``` | ||
|
|
||
| Each line records `time`, `op`, `profile`, `target`, `actor` (OS user), | ||
| `outcome` (`ok`/`error`), `error`, and `duration_ms`. Audit writes are | ||
| best-effort: a failure to write the log never fails the operation it records. | ||
|
|
||
| ## 2FA & group gating | ||
|
|
||
| A profile belongs to a **group**; a group can require a second deliberate step | ||
| before any destructive op on its profiles: | ||
|
|
||
| ```yaml | ||
| groups: | ||
| critical: | ||
| confirm_destructive: true # operator must retype the profile name | ||
| require_2fa: true # operator must enter a current TOTP code | ||
| totp_secret: env:SIPHON_PROD_TOTP # base32 RFC-6238 secret (a secret-ref) | ||
|
|
||
| profiles: | ||
| prod: | ||
| driver: postgres | ||
| group: critical | ||
| # ... | ||
| ``` | ||
|
|
||
| `confirm_destructive` prompts the operator to retype the profile name; | ||
| `require_2fa` prompts for a 6-digit TOTP verified (with ±1 step skew) against the | ||
| group's `totp_secret` — the same code your authenticator app shows. The check | ||
| runs **before** the operation, so a failed confirmation aborts before any | ||
| destructive work. `require_2fa` with no resolvable secret fails closed. The TOTP | ||
| secret is a secret-ref, so the plaintext never lives in config. | ||
|
|
||
| This is offline by design — siphon is a local CLI, so "2FA" means a TOTP | ||
| (standard, no network) rather than a push notification. | ||
|
|
||
| ## Telemetry | ||
|
|
||
| Opt-in aggregate operational metrics: per-op counts and error tallies, flushed | ||
| as JSON. Off by default. | ||
|
|
||
| ```yaml | ||
| telemetry: | ||
| enabled: true | ||
| path: ~/.local/state/siphon/telemetry.json # optional; this is the default | ||
| ``` | ||
|
|
||
| Telemetry records **only** the operation name and outcome — never profile names, | ||
| hosts, dump IDs, the actor, or any data. It is composed onto the audit seam, so | ||
| enabling it adds no new interception in the verbs. | ||
|
|
||
| ## Scheduled backups | ||
|
|
||
| `siphon schedule` manages recurring backups by maintaining a delimited, | ||
| siphon-owned block in your **crontab** — siphon does not run a scheduler daemon; | ||
| your system's cron invokes `siphon backup <profile>` on the schedule. | ||
|
|
||
| ```bash | ||
| siphon schedule add prod --cron "0 2 * * *" # nightly at 02:00 | ||
| siphon schedule list | ||
| siphon schedule remove prod | ||
| ``` | ||
|
|
||
| Entries outside the siphon-managed block are preserved. Re-adding a profile | ||
| updates its schedule in place; removing the last entry drops the managed block. | ||
| Requires the `crontab` command. | ||
|
|
||
| ## SSH tunnel | ||
|
|
||
| `siphon tunnel <profile>` opens an SSH local-forward to a profile's database | ||
| through a configured bastion, using your **system ssh client** (your ssh config, | ||
| keys, and agent all apply). It runs in the foreground and holds the tunnel open | ||
| until you press Ctrl-C. | ||
|
|
||
| ```yaml | ||
| profiles: | ||
| prod: | ||
| driver: postgres | ||
| host: db.internal | ||
| port: 5432 | ||
| tunnel: | ||
| bastion: jump@bastion.example.com | ||
| local_port: 15432 # optional; defaults to the DB port | ||
| ``` | ||
|
|
||
| ```bash | ||
| siphon tunnel prod | ||
| # tunnel open: localhost:15432 → db.internal:5432 via jump@bastion.example.com (Ctrl-C to close) | ||
| ``` | ||
|
|
||
| Run it in one terminal and point a client (or another siphon command) at the | ||
| printed local address in another. siphon delegates to `ssh -L` rather than | ||
| reimplementing SSH or holding a connection in a daemon. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package app | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| "github.com/nixrajput/siphon/internal/audit" | ||
| ) | ||
|
|
||
| // guardedOp is the single interception point wrapping every destructive verb. | ||
| // It (1) runs the authorization Gate (2FA / confirmation) before the operation | ||
| // and (2) opens an audit record. It returns a done(err) the caller defers to | ||
| // finalize the audit outcome. If the Gate blocks, it returns a non-nil error and | ||
| // a no-op done — the caller must not run the operation. | ||
| // | ||
| // Layering all destructive concerns here means each verb gains one guard call | ||
| // at entry and one deferred done(err); audit, 2FA gating, and (later) telemetry | ||
| // hook in here rather than each re-wrapping every verb. | ||
| func guardedOp(ctx context.Context, d Deps, op audit.Op, profile, target string) (done func(error), err error) { | ||
| if d.Gate != nil { | ||
| if gErr := d.Gate.Authorize(ctx, op, profile); gErr != nil { | ||
| return func(error) {}, gErr | ||
| } | ||
| } | ||
| rec := audit.Record(ctx, d.Auditor, audit.Event{ | ||
| Op: op, | ||
| Profile: profile, | ||
| Target: target, | ||
| Actor: d.Actor, | ||
| }) | ||
| return rec, nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.