Create a command rule from an approval
For: tenant admins and governance reviewers.
Allowlist a command from its card
When a command rule asks a person about a command, its approval card (and oz0 approval allowlist) offers, beside Approve, Deny and Allow the same command in this job for, two more answers to whoever decides approvals:
- Approve and always allow… approves the command and adds an Allow command rule, so commands like it run without asking from then on;
- Deny and always refuse… denies it and adds a Refuse command rule, so commands like it never run; the agent reads your reason.
A card is real because an edge says so. An approval lives in its job's workflow, which only an edge reads, so when you open the form the edge that shows you the card signs a receipt: the job, the approval, the tool, the agent, the digest of the arguments and you.
Management checks that receipt before it reads anything else of the rule, and refuses a rule without a valid one, so a rule's origin always names a card an edge vouched for, a person who may approve it, and those very arguments.
The edge asks the card's own deciders first, as it does for a yes: holding the permission is not enough, and someone who may only deny the card, such as its job's starter under four eyes, can deny it but draft no rule on it: both ways of drafting say so before the form, as they do when the edge has no key to sign with.
A receipt lasts an hour; past that, open the card again. It is signed only while the approval waits, so the form needs the job's node up and the content permission, as reading the whole command does.
Each choice opens a form of the rule before anything is saved.
Choose a pattern
The narrowest one is suggested: this command with every argument pinned, read from the command's parse, never its text (the programs that feed it, such as a download piped into a shell, as earlier stages).
Each token is a chip that widens one step at a time: a flag's value by its last segment (-target=module.test.vpc to module.test.*, then module.*, then *), a path to its class ([inside-workdir], [home]), a URL to its host ([host:get.k3s.io]), a branch to [default-branch], any other argument to *, and last ** for anything after.
The program and its subcommand never widen, so a rule from a card is never broader than one program and its subcommand.
A word that looks like a credential is never pinned: it starts as *.
A word that holds *, ? or [ cannot be pinned (a pattern would read it as a wildcard or a class, wider than the word), so the card says no pattern names the command exactly.
Nor does an Allow cover a command that sets its environment or writes to a file (see Patterns); the card says so, and a Refuse can still be written from it.
Choose the agents
Choose this agent (the default), the agents of one plugin, or every agent of the tenant.
Choose the duration
Choose 7 days, 30 days (the default) or no end. The rule may also start in shadow mode, deciding nothing and recording what it would have decided.
Give a reason
A reason is required, as for every change of the policy. The rule keeps it, and the agent reads it when the rule refuses.
Read the estimated impact
The form shows one line from the command decisions of the last 30 days in the audit trail: "Would have allowed at most 4 commands in the last 30 days, all terraform (cmd.cloud-delete) by deployer", with up to three examples of what else the pattern matches. The audit entries keep the program and what the defaults found, never the command, so the count is of the commands it could have matched: at most that many. It is one line, not a full preview of the change against past commands.
Rules the form refuses
What the card will not offer, and the server refuses when the rule is saved whoever sends it, each with its reason:
- No allow for a command the reader could not read. Such a command always asks (the floor), so no allow could match it; the card offers only Deny and Deny and always refuse…, which refuses every command the reader cannot read, for the scope you choose (a Refuse over
cmd.unreadable). - No pattern that names only a program a default asked about (
rm **,terraform **), and none that leaves a subcommand open (terraform * ...). For the CLIs whose subcommand is a group and a verb, both words are pinned:aws s3 * **would be every s3 operation, so it is refused. - No allow of running what a command downloads (
cmd.download-run) unless every URL is written out in full, over https, to a file, and its host is on the tenant's egress list, and every other word of the command is pinned as written too: no*,**or class anywhere in it, since an open word would let the same command fetch a second URL, or send the pinned host's name to another address, and run that.
The content behind a URL can change after you approved it: a model that was given a backdoor fetched its payload from exactly such a URL. The card suggests the safer form first: download the file, check its hash, then run it (curl -fsSLo install.sh <url> && echo '<sha256> install.sh' | sha256sum -c - && sh install.sh). - No rule that would not decide this very command as asked. An Allow is placed among the tenant's rules as any save places it and must then let the command run: one broader than the default that asked (it also matches commands the default lets run) stands below that default, which still asks, so it is refused with the default's name. A Refuse must match the command. A tenant Refuse that matches it always wins, so an Allow under it is refused too.
Apply and record the rule
A Refuse tightens the policy, so it acts at once. An Allow loosens it: it acts at once, or, where four eyes is on, waits as a change request of kind command_rule until another admin approves it; the command is approved either way.
Someone who decides approvals but may not change the policy (an operator) proposes either rule as such a change request for a tenant admin.
The rule records the approval and job it came from (origin: approval:<job>/<approval>), who proposed it, who let it act, why, when it was made and when it ends; the Commands tab of Govern › Policy lists it among the tenant's rules.
The server records the origin itself: a rule saved through the tool policy has none, whatever its client sends, and a rule keeps the origin it was saved with through later saves that leave what it decides as it was.
A rule edited in place says so (origin: edited:approval:<job>/<approval>): it no longer decides what its approval was about.
A rule's job, approval, tool and agent are those of the card its receipt names, so another agent or another tool than the card asked about is refused, and so is a job that is not the tenant's.
Every new rule's origin is approval:<job>/<approval>. named:<job>/<approval> is only read back: it is the origin of a rule saved before cards had receipts, and a change request on such a rule tells its reviewer that nothing confirms the approval.
At most 50 rules from approval cards wait for a tenant admin at once in a tenant, and at most 10 of one person's, so that someone who may only propose cannot fill the review queue: a further one is refused until an admin decides some.
An API key cannot allowlist a command: a person does. The audit trail has the tool policy's own entry (tenant.tool_policy) when it acts, and policy.command_rule.change_requested, _approved, _rejected and _applied for a rule that waited.
oz0 approval allowlist job-7f3a a-1b2c3d4e5f60 --preview # the suggestion, its chips, the checks, the impact
oz0 approval allowlist job-7f3a a-1b2c3d4e5f60 \
--pattern 'terraform destroy -target=module.test.* -auto-approve' --reason "test modules are disposable"
oz0 approval allowlist job-7f3a a-1b2c3d4e5f60 --refuse --scope tenant --for none --reason "never staging"
A chat thread's post of such an approval says to open its card in OZ for these two answers: a chat cannot show the form.