Skip to content

Latest commit

 

History

29 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JitAccess

Nobody should have permanent root. JitAccess is a just-in-time privileged access broker for Linux hosts: you request elevated access with a reason, someone else approves it, and it removes itself when the window closes.


The problem

Ask any mid-size company who has permanent sudo on production. The honest answer is usually "about twelve people, because revoking it breaks on-call."

That's how it happens. Someone needs root at 3am during an incident, gets it, and nobody ever takes it away — because taking it away is a task no one is assigned and no one is rewarded for. Access accretes. It never drains.

The cost of that shows up in three different places:

Where it hurts What it looks like
Breach blast radius One phished laptop belongs to someone with standing root on every host. The initial compromise and the total compromise are the same event.
Audit SOC 2, ISO 27001 and PCI all ask "who has privileged access and why?" Answering it with a stale spreadsheet is how findings get written.
Offboarding Someone leaves. HR disables their SSO. Their sudo group membership on 40 hosts is nobody's job to notice.

Why the usual fixes don't hold

  • "We'll review access quarterly." A review is a snapshot. Access drifts the day after, and the reviewer has no context to judge whether alice still needs wheel.
  • "Just file a ticket." Ticket queues have no notion of expiry. The ticket that granted access is closed the moment access is granted — the undo is never tracked.
  • "We have a shared break-glass account." Now the audit log says root did it and you cannot tell which human that was.

Every one of these fails for the same reason: they depend on a person remembering to undo something. People don't, especially not at 3am.

What JitAccess does

It keeps the shape of the ticket everyone already understands — request, approve, close — and makes the closing part automatic and enforced.

   request ──────approve──────> active ──────(clock)──────> expired
      │            (by          (privilege     no human      privilege
      │          someone         applied)      involved       removed
      │           else)                                          )
      │
      ├──deny──> denied
      │
      └── (while active) ──revoke──> revoked

Four ideas do the work:

  1. Approval is the grant. There is no "approved but not yet active" state, so there's never a window where privilege has been blessed but nobody is counting it.
  2. Every grant has an expiry. Not a reminder, not a policy — a timestamp, after which a reaper removes the access whether anyone is watching or not.
  3. A requester can never approve themselves. Separation of duties, enforced in code and not overridable by config.
  4. Refusals are recorded too. A blocked self-approval is a control working as designed, and it's exactly the event an investigator wants to find later.

Where this changes things

Before With JitAccess
Who has root right now? "Let me check the wiki" jitctl list
How long will they have it? Forever 30 minutes
Who approved it? Nobody remembers In the audit log, signed into a hash chain
Why did they need it? Lost in Slack Required field, stored with the grant
What happens when they leave? It lingers It already expired
Proving it to an auditor Screenshots jitctl verify

The shift is from access as a state you're in to access as an event that happened. Standing privilege becomes a thing that has to be justified each time, which is the only version of least-privilege that survives contact with an on-call rotation.

Who this is for

  • Small platform/infra teams who need defensible access control but can't buy Teleport or CyberArk.
  • Companies heading into a SOC 2 / ISO 27001 audit who need to show privileged access is time-bound and reviewed.
  • Anyone running production on a handful of Linux hosts where "everyone is in wheel" is the current design.

Concretely: an on-call engineer requests 30 minutes of host-sudo citing an incident number, the secondary approves from their phone, the engineer fixes the disk, and the access is gone before the incident review starts — with a record that answers every question that review will ask.

Design principles

These are decisions, and each one is a constraint on the code:

  • Standard library only. Every dependency in a security tool is code you are trusting on someone else's behalf.
  • All privilege-changing code lives behind one interface, so the part that could escalate privilege is small enough to read in one sitting.
  • The tool never edits /etc/sudoers. A bug in that code would be a root escalation — precisely what this exists to prevent. An administrator installs a drop-in once; JitAccess only moves people in and out of groups.
  • The audit log is append-only and hash-chained. Tamper-evident, not tamper-proof — an honest claim we can actually back.
  • Nothing is marked done until it has been run.

Usage

go build -o jitctl ./cmd/jitctl

Roles live in /etc/jit-access/config.json (override with JIT_ACCESS_CONFIG). See examples/config.json:

{
  "roles": [
    {"name": "dba", "group": "jit-dba", "max_duration": "4h", "approvers": ["alice", "bob"]}
  ]
}

The group and the approver list live in the same record so they cannot drift apart. A config that is missing, malformed, or names a role with no approvers is refused at startup rather than silently defaulted.

jitctl request -role dba -duration 1h -reason "quarterly migration rollback"
jitctl list
jitctl approve req-1787421806216733179     # never by the requester
jitctl deny    req-...
jitctl revoke  req-...                     # ends an active grant early
jitctl verify                              # audit log against its anchor
jitctl reap                                # expire what is due; run from cron
jitctl help                                # the manual: model, files, config
jitctl help reap                           # long form for any one command

jitctl help explains the model, every command, where state lives, the config format and the exit codes. Every subcommand also takes -h, which prints the same page plus its real flags.

State lives in /var/lib/jit-access (0700; override with JIT_ACCESS_STATE_DIR): requests.json, audit.jsonl, anchor.json, and a .lock that serializes concurrent commands.

Status

Feature complete — 2026-08-23. All commands work against real state on disk, and the privilege path is verified as root against real Unix groups.

Milestone 1 — the core model

  • domain — the Request type and its state machine
  • policy — role definitions, duration caps, approver rules
  • store — crash-safe persistence (atomic replace + fsync)
  • audit — the hash-chained log, plus an out-of-band anchor that detects truncation and wholesale rewrite
  • config — roles, groups, durations and approvers from one file
  • lockfile — flock, so two commands cannot interleave a read-modify-write
  • jitctl — the CLI that ties them together

Milestone 2 — real enforcement

  • Unix group executor, behind the Executor interface
  • Verified as root: real grant, real revoke, real expiry via reap
  • sudoers drop-in, validated with visudo -c
  • systemd timer, so expiry doesn't depend on someone running a command

Milestone 3 — trustworthy identity

  • Ship the audit head off-host, so the anchor is not on the disk it guards
  • Approval notifications

Later

  • AWS backend: STS session credentials instead of Unix groups

Known limits

  • Revoking group membership does not end sessions that are already open.
  • The anchor sits beside the log, written by the same process. It catches tampering by anyone who is not root on this host; it is only a hard guarantee once the head leaves the machine.

Why I'm building this

A learning project, built deliberately rather than quickly. The things I'm here to actually understand:

  • How privilege is really carried and revoked on a Unix host — groups, sudoers, and where the seams are
  • Why time-boxing beats remembering, and what breaks when a session outlives its grant
  • Modelling a security control as a state machine, so illegal transitions are unrepresentable rather than merely discouraged
  • Building a record that stays trustworthy when the person holding the pen has a motive to edit it

About

Just-in-time privileged access broker for Linux hosts - request, approve, auto-expire. Kills standing sudo.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages