DocsAuthorizationCardinal 0.7.0

Policy

One Cedar policy set decides web access, application sign-in, SSH logins, sudo, provisioning and the directory itself.

llms.txt

Most identity estates have three or four policy languages that cannot see each other: LDAP ACLs, HBAC and sudo rules, and whatever the SSO product calls mappers. None are diffable, none are testable, and none can answer why somebody was refused.

Cardinal has one, Cedar, and it is the decision point everywhere:

Decision point The question
Traefik forwardAuth May they reach this URL?
OIDC authorization May they sign in to this application?
SSH certificate issuance May they log into this host, as which local accounts?
The assignment an agent fetches Does their name belong in sudoers there?
SCIM May this provisioner create and change accounts?
The admin API May they change this directory object?

That last row is the one that surprises people: the directory's own access control is the same engine as everything else. There is no separate, arcane ACL syntax for administering Cardinal itself.

Permit, forbid, and which wins

Cedar is default-deny: nothing is permitted unless a rule permits it. A forbid beats any permit, which is what makes the guardrails in the shipped set guarantees rather than conventions. See administration tiers.

That also makes a particular mistake invisible. A rule naming a group that does not exist never matches, which looks exactly like the rule working. Everything that publishes a policy checks for it and says so.

Groups and policy

Coming from a system where you bind a group to an application when you create it (the application carries an "allowed groups" field, and a user gets in when they are in a bound group), the shape here is different, and the difference is worth being explicit about:

  • A group is a set of members in the directory. It grants nothing on its own.
  • A policy is the Cedar rules. A rule names a group and a resource: "principals in group grafana-users may reach application grafana." The rule is the binding, and it lives in the versioned policy, not on the application.

So "group G may use application X" is a rule:

cardinal group create grafana-users
cardinal grant grafana-users alice
cardinal policy rule add web-access -group grafana-users -app grafana

The binding being a rule rather than a field on the application is the point of policy-as-code: it is diffable, testable and reviewable before it governs anything, and it can say things a checkbox cannot. The cost is that it is less immediately obvious than a setting on the application. The rule builder closes most of that gap: it writes exactly this kind of per-application rule, in the console or the CLI, without editing the policy file, so the everyday act is close to binding a group while what lands is a reviewable version.

The default is least-privilege. A fresh install ships no blanket access rule: no application is reachable until a rule names it, the same default-deny the rest of the directory has. Grant each application to the group that may use it. For an application genuinely open to everyone who signs in, put it in the staff-apps group and enable the bucket rule the default file ships commented out. Existing installs keep whatever policy they already activated; the default is what a new install seeds.

What policy does not decide

Cardinal decides whether you may reach a thing. What you may do once inside it is the application's own model, deliberately (ADR 0019). Cardinal has no concept of a role inside somebody else's application, and adding one would mean every application's permission model living here.

What it costs

Cedar is a real language and somebody has to learn it. A rule set is code, with the review, the testing and the blast radius of code. The rule builder narrows that to four common shapes; it does not remove it.