Skip to content

Latest commit

 

History

History
191 lines (144 loc) · 6.17 KB

File metadata and controls

191 lines (144 loc) · 6.17 KB

Identity & Attribution

cfgit records who changed a versioned record, when it happened, and why. The trust level of that "who" is explicit and configured per environment.

The short form is:

  • open: self-asserted author, useful for cooperative teams.
  • authenticated: cfgit verifies who used cfgit, but direct DB writes may still happen and are handled by drift detection.
  • enforced: cfgit verifies identity and the database write credentials are locked down outside cfgit so cfgit or CI is the only writer.

cfgit cannot prevent a direct DB write by code alone. Prevention is always database-side credentials and RBAC. cfgit detects bypass with status, shows it with diff, and records it with adopt.

Modes

Open

Open mode is the default and remains a first-class mode. Users can pass --author, set CFG_AUTHOR, rely on git config user.email, or fall back to the OS username.

This is attribution, not authentication. It is right for local and dev workflows where the team is cooperative and drift detection is the safety net.

[env.dev.identity]
mode = "open"

Authenticated

Authenticated mode requires cfgit to verify identity before mutating history. It does not take DB write access away; it makes cfgit's own trail trustworthy. Bypass can still happen, and cfgit still detects it as drift.

[env.staging.identity]
mode = "authenticated"
sources = ["token", "db_principal"]

Enforced

Enforced mode is the cfgit-side identity posture for production-style setups where direct database writes are also blocked by DB credentials. The database must be configured so humans and ad-hoc scripts do not hold write credentials for the versioned records.

[env.prod.identity]
mode = "enforced"
sources = ["token"]

enforced does not magically close direct DB doors. It only becomes real when the database is locked down to a cfgit service identity or CI identity.

Token Identity

Token identity is built for private human strings:

export CFGIT_IDENTITY_TOKEN='imkanyewest'

cfgit hashes the full string with SHA-256 and compares it to configured hashes. The raw token is never stored in cfgit history. The visible 4-12 character fingerprint is only for humans to distinguish identities; it is never accepted as proof.

Generate a hash without putting the token in shell history:

printf '%s' 'imkanyewest' | cfg identity-hash --stdin

Then configure the full hash:

[env.prod.identity]
mode = "authenticated"
sources = ["token"]
token_env = "CFGIT_IDENTITY_TOKEN"
fingerprint_chars = 5
tokens = [
  { author = "alice@example.com", name = "alice-main", sha256 = "sha256:..." },
]

Use memorable tokens only when the config containing hashes is private enough for your risk level. If hashes are public, short or guessable phrases can be attacked offline. Prefer longer private phrases for production.

How it works, and what it does (and doesn't) protect

A common question: if CFGIT_IDENTITY_TOKEN lives only in the user's local environment and is never stored centrally, what is it actually protecting?

It works like a password checked against a stored hash — but with no auth server and no session:

  • Setup, once. The user picks a private string. You hash it locally (cfg identity-hash) and put only the hash in .cfg.toml. The raw string is never written into cfgit — not in the config, not in history, not in logs.
  • At runtime. The user exports the raw string as CFGIT_IDENTITY_TOKEN.
  • On every command. cfgit hashes the env-var value right then and compares it to the configured hashes. A match verifies the author; a mismatch is rejected. There is no login step and no session — it re-verifies per command, statelessly.

So it is deliberately decentralized: the hash is shared (in the config), the raw token stays local, and cfgit never has to hold a secret or phone home to a server. Anyone can read Alice's hash; that does not let them act as Alice.

What this buys you, and what it doesn't:

  • It is attribution / accountability, not a hard security boundary. It proves "this caller knows Alice's token," which makes cfgit's history trail hard to spoof casually. It does not stop someone who steals Alice's raw token from her environment, and public hashes over short phrases can be brute-forced offline (hence: long, private phrases for production).
  • For a real security boundary, prefer db_principal identity (below): the database itself authenticates the connection, and cfgit maps that verified DB user to an author — no shared secret to leak. Combine with enforced mode and locked-down DB write credentials so cfgit/CI is the only writer.

Database Principal Identity

db_principal uses the authenticated database connection identity:

  • Postgres returns current_user.
  • Mongo uses connectionStatus authenticated users, or the URI username when that is all the driver can expose.

Map database principals to author names when needed:

[env.prod.identity]
mode = "authenticated"
sources = ["db_principal"]
principal_map = { "alice_db" = "alice@example.com" }

This is often the cleanest route when each person already has their own DB credential.

Permissions

[env.<name>.permissions] still controls what a resolved identity can do:

[env.prod.permissions]
mode = "restricted"
admins = ["owner@example.com"]
writers = ["*@example.com"]
admin_actions = ["init", "restore_system"]

In open identity mode, role checks match the self-asserted author string. This is a guardrail.

In authenticated or enforced identity mode, role checks use the verified identity. If --author does not match the token or DB principal identity, cfgit refuses the operation.

History Metadata

Every new history entry includes:

{
  "author": "alice@example.com",
  "meta": {
    "identity": {
      "mode": "authenticated",
      "author": "alice@example.com",
      "source": "token",
      "authenticated": true,
      "fingerprint": "abc12",
      "principal": "alice-main",
      "credential": "alice-main"
    }
  }
}

The top-level author stays simple for logs and compatibility. The nested identity object tells you how trustworthy that author is.