Wardengate
Guides

Identity-bound policy for PAM: a practical primer

If your PAM policy lives in spreadsheets and firewall rules, this is where to start. A walkthrough of the core primitives, with examples you can lift into your own environment.

Rhea Okonkwo9 min read

Most privileged access controls were not designed — they accreted. A rule in a firewall here, a sudoers entry there, a group in the IdP that nobody remembers creating. The result works, in the sense that sessions happen and auditors are placated, but it does not answer basic questions. Who can reach the production database tier tomorrow at 02:00? Nobody can say without running three queries against three systems.

Identity-bound policy, expressed as code, replaces that archaeology with a single readable rule set. This primer walks through the primitives we see work in production: subjects, targets, conditions, and obligations. We keep the examples generic enough that you can port them to any PAM control plane that accepts structured policy.

The four primitives

Almost every useful privileged access rule is a tuple of four things. Name the four primitives explicitly and most of the rule set writes itself.

  • Subject: the identity asking for access. Not a host, not an IP, not a key — a person or a workload with a federated identity.
  • Target: the thing being accessed. A set of hosts, a database, a Kubernetes namespace, a control-plane endpoint.
  • Conditions: the context under which the rule is true. Time windows, MFA freshness, device posture, an approval record, membership in a specific change ticket.
  • Obligations: the controls the gateway must enforce if the rule matches. Record the session, inject a broker-held credential, deny specific commands, require re-auth every 30 minutes.

A first rule

A good starter rule is the one that grants your SRE on-call group the ability to reach production Linux hosts with session recording enabled. It is small enough to read in one breath and exercises all four primitives.

policy "sre-oncall-prod-linux" {
  subject {
    idp_group = "sre-oncall"
    require_mfa_within = "15m"
  }

  target {
    host_tag = "env:prod"
    host_tag = "os:linux"
  }

  condition {
    time_window = "anytime"
    device_posture = "managed"
  }

  obligation {
    record_session = true
    max_duration = "4h"
    inject_credential = "broker:linux-admin"
  }
}

Notice what is not in the rule. There is no IP allowlist, because the subject is an identity not a network location. There is no shared key, because the credential is injected by the broker at connect time. There is no standing authorization, because the rule expires the session after four hours and re-checks MFA. Those three absences are what make the rule auditable.

From a rule to a rule set

One rule is easy. Fifty rules are where teams start to lose track. The trick is to keep the shape of every rule identical and to resist the urge to add clever exceptions inside a rule. If you need an exception, write a second rule with its own name and let the evaluation order make the precedence explicit.

A practical rule-set structure looks like this: a small number of baseline rules that grant ordinary day-to-day access, a smaller number of break-glass rules that require approval and leave a loud audit trail, and a denial-by-default at the bottom. If you find yourself writing a rule that combines baseline and break-glass semantics, split it.

Testing policy before it ships

Policy-as-code only pays off if you test it. A rule is a function: given a subject, a target, and a context, does it return allow or deny, and with which obligations? That is testable. Build a small fixture set of realistic (subject, target, context) triples and assert the expected decision. Run the tests in CI. When a rule changes, you find out which scenarios moved before you merge.

- case: oncall reaches prod linux during shift
  subject:
    groups: [sre-oncall]
    mfa_age: 5m
  target:
    tags: { env: prod, os: linux }
  context:
    device_posture: managed
  expect:
    decision: allow
    obligations: [record_session, inject_credential]

- case: oncall reaches prod linux from unmanaged device
  subject:
    groups: [sre-oncall]
    mfa_age: 5m
  target:
    tags: { env: prod, os: linux }
  context:
    device_posture: unmanaged
  expect:
    decision: deny

Common failure modes

A few failure modes show up in nearly every PAM policy effort, and they are worth naming so you can spot them early.

  • Conflating roles with policies. The IdP owns roles. PAM policy consumes them. If you find yourself editing an IdP group as part of writing a PAM rule, stop — the rule is too coupled to organizational shape.
  • Overloaded break-glass. 'Emergency access' rules that nobody reviews drift into ordinary access. Break-glass should be loud, logged, and rare.
  • Hidden network-based exceptions. A rule that reads cleanly in policy language but depends on a firewall exception you did not put in policy is not actually policy — it is policy plus a secret.

Where this leaves you

Identity-bound policy is not a silver bullet, but it does give you something concrete: a single readable artifact that answers 'who can do what, when, under what controls.' Every other PAM capability — session recording, JIT elevation, audit evidence — is more effective when the rules that trigger it are expressed this way. Start with four rules, test them, and grow the set from there.