Policy file reference¶
The policy file says who may do what on which devices. port49 policy test loads it the way
port49d does and runs its tests:.
It is one YAML document. An unknown key, a second document (a --- separator) or a key this build
does not read is refused, and the error names the key or the line.
Paths below use [] for an item of a list and <name> for a key you choose. Every table row is a
leaf; a key whose value is a block is documented by the keys under it. Durations need a unit, for
example 60s.
Rules are written as words: * for one word, ** for any number, and /regex/ or @class for a
term. See the policy guide for the grammar. Rules are never quoted in an error: a refusal names the
list and the position.
Top-level keys¶
| Key | Type | Description |
|---|---|---|
platform_set |
string | Pins the platform set (the shipped adapters) this policy renders with, for example 1.0.0, so that a binary upgrade never changes what the policy renders. Unset means set 1.0.0. A set the binary does not ship is refused, and the error lists the sets it does ship. |
defaults¶
| Key | Type | Description |
|---|---|---|
defaults.per_login_mfa.accept |
list of string | The proofs that satisfy the requirement: a list of hardware_key_touch, idp_mfa or both, each at most once. Required when per_login_mfa is present; leave the block out for no requirement. |
defaults.per_login_mfa.reuse_for |
string | How long one proof counts for later logins, a duration from 0m (a proof at every login) to 8h. Default 15m. |
defaults.per_login_mfa.applies_to_machines |
boolean | Set true to require the proof of machine identities too. Default false. |
platforms¶
| Key | Type | Description |
|---|---|---|
platforms.<name>.supports |
list of string | The capabilities the adapter declares, a list that must include session. Replaces the inherited list. |
platforms.<name>.privilege |
map of list of string | Per privilege level, the attribute strings the reply carries at that level, for example readonly: ["priv-lvl=1"]. A level may not send an attribute twice. |
platforms.<name>.ceiling_levels |
map of integer | Per level, the priv-lvl number (0 to 15) the level stands for where the rendering sends no priv-lvl, used for the token's ceiling. |
platforms.<name>.entity_classes |
map of list of string | Named sets of device entity names, as lists of RE2 patterns, that rules reference as @class. A class name is lowercase letters, digits and _, starting with a letter, and a class may not be empty. |
platforms.<name>.expands_abbreviations |
boolean | Whether the device sends resolved command words. Needed when the platform sends per-command authorization. |
platforms.<name>.splits_interface_names |
boolean | Whether the device sends an interface name and its index as two arguments. |
platforms.<name>.pipe_token |
string | The modifier separator, one argument with no whitespace, for example \|. |
platforms.<name>.pipe_authorized |
boolean | Whether the device sends the part of a command after the pipe for authorization. |
platforms.<name>.strip_cr |
boolean | Whether commands end in <cr>, which port49 strips before matching. |
platforms.<name>.shows_server_msg |
boolean | Whether the device displays a denial's server_msg to the operator. |
platforms.<name>.privilege_attributes |
list of string | The attribute names that carry privilege on this platform. The only names an attributes: placeholder may go in, and the names whose change port49 policy diff gates. |
platforms.<name>.inert_attributes |
list of string | The attribute names held to carry no privilege. A name may not also be in privilege_attributes, and a name a level renders is refused here. |
platforms.<name>.accepted_attributes |
list of string | Where set, every attribute name the session reply may carry. A reply with any other, or a priv-lvl the levels do not render, quarantines the device. An empty list is refused: list the names or remove the key. |
platforms.<name>.role_order |
map of list of string | Per attribute that names a role, its values lowest first, where the lab measured one fencing less than another. A value not listed ranks above every listed one. priv-lvl ranks by its number and may not be listed. |
platforms.<name>.device_config |
list of string | The device configuration the adapter's renderings depend on, one printable ASCII line per item, printed by port49 platform show. |
platforms.<name>.enable_username |
string | The username the device sends in an enable exchange. The only accepted value is principal, the operator's own username. $enab{level}$ is refused at load. |
platforms.<name>.redact.[].match |
list of string | Words that select the command this pattern applies to, * for one word and ** for any number, at most two **. No regex or class. |
platforms.<name>.redact.[].after |
string | Remove every word after the first word equal to this one. One word. Give after or from, not both. |
platforms.<name>.redact.[].from |
integer | Remove every word from this position on; the first argument is 1, and 0 is refused. Give from or after, not both. |
platforms.<name>.verified |
string | Port49's own claim about its lab coverage of a built-in adapter. Refused in a policy file: an adapter written here is unverified. |
platforms.<name>.unreachable |
map of list of string | Per level, the commands the device refuses in its own parser, as rules. The dead-rule report reads them. |
platforms.<name>.unreachable_note |
map of list of string | Port49's own record of a block's unreachable rules the set did not measure. Refused in a policy file; write the rules under unreachable. |
platforms.<name>.fallback |
map of string | Per level, what the device gives a session on its own when port49 cannot answer, as text. Not empty. |
platforms.<name>.fallback_note |
map of string | Port49's own record of a block's fallback text over the set's. Refused in a policy file; write the text under fallback. |
platforms.<name>.local_accounts |
list of string | RE2 patterns, each with one capture group (the account's name), that find a device-local account in the device's configuration. Read by port49 devices check-config. |
platforms.<name>.config_checks.[].match |
string | An RE2 pattern over one configuration line that the device's configuration must not carry. Required. |
platforms.<name>.config_checks.[].says |
string | What the matching line costs, shown when it is found. Required. |
platforms.<name>.command_projection |
string | How rules render for a session-only platform. The only value this build renders is junos_regex. Only a platform the pinned set projects may set it; elsewhere it is refused. |
platforms.<name>.bits |
map of list of string | On a projected platform, each permission bit's reach in rule form. A block may add to a bit's reach. |
platforms.<name>.carve_outs |
map of list of string | On a projected platform, the commands a bit alone makes available. A block may drop one and never add one. |
platforms.<name>.bit_delivery |
string | How permission bits travel to the device: user_permissions (as a user-permissions= attribute of the session reply) or local_user_name (as the name of a template user on the device, see local_user_names). Required on a projected platform. |
platforms.<name>.local_user_names |
map of string | Under bit_delivery: local_user_name, each level's template user on the device, named p49-<name>. Never remote or a platform system user. |
platforms.<name>.rpc_entry_denies |
list of string | On a projected platform, the denies a reply adds where a wire deny is the only guard beneath a sent bit. Each is words with a trailing **. |
platforms.<name>.deny_reach_bits |
list of string | On a projected platform, the bits a reply may send where the grant permits a carve-out of the bit. |
platforms.<name>.replace |
boolean | Set true to discard the shipped adapter of this name and start from the generic adapter. Not with based_on. |
platforms.<name>.based_on |
string | Start this new platform from another platform of the pinned set. Not for a name the set already ships, and not with replace. |
privilege_levels¶
| Key | Type | Description |
|---|---|---|
privilege_levels.[].name |
string | The level's name. Required and unique. The list must include readonly, operator and admin, which keep that relative order. |
privilege_levels.[].parent |
string | The level this one renders as where a platform has no rendering for it. Must be declared, and the reserved levels have no parent. |
scopes¶
| Key | Type | Description |
|---|---|---|
scopes.[].name |
string | The scope's name. Required and unique. Grants refer to it. |
scopes.[].match |
map of string, list of string or comparison | Selects devices by inventory attribute: each key is an attribute (site, role, platform, os_version and so on), each value a string, a list of strings, or a comparison such as {lt: "21.4R3-S5"} or {re: "..."}. Empty selects every device. name takes names only, not a comparison. |
scopes.[].on_missing |
string | What to do with a device that has no value for a match key: include or exclude. Required on a scope that attaches a deny or carries a grant with a deny, and where per_login_mfa is set. Default exclude. |
scopes.[].security_log |
boolean | Whether a device joining or leaving the scope is logged as a security record. A scope that delivers a deny is logged unless this is false. |
scopes.[].attached_roles.humans |
list of string | Roles every human principal gets on the devices in the scope, without a grant. On an include scope only roles with no permit, privilege or attributes are accepted. |
scopes.[].attached_roles.machines |
list of string | Roles every machine principal gets on the devices in the scope, without a grant. Same limit on an include scope. |
scopes.[].per_login_mfa.accept |
list of string | The proofs that satisfy the requirement: a list of hardware_key_touch, idp_mfa or both, each at most once. Required when per_login_mfa is present; leave the block out for no requirement. |
scopes.[].per_login_mfa.reuse_for |
string | How long one proof counts for later logins, a duration from 0m (a proof at every login) to 8h. Default 15m. |
scopes.[].per_login_mfa.applies_to_machines |
boolean | Set true to require the proof of machine identities too. Default false. |
roles¶
| Key | Type | Description |
|---|---|---|
roles.[].name |
string | The role's name. Required and unique. baseline, reach_deny and rpc_entry are port49's own and refused. |
roles.[].privilege |
string | The privilege level the role grants. Must be a declared level. |
roles.[].reason |
string | Free text recorded with the role, for review: why it exists. |
roles.[].permit |
list of string | Command rules the role permits, as words. ** is any number of words; /regex/ and @class are terms. |
roles.[].deny |
list of string | Command rules the role denies. A permit wins over a matching deny only when it is more specific than every deny that matches (dominance; see Policy). A class used in a deny may not turn case-insensitivity off. |
roles.[].pipe.permit |
list of string | Rules for the part of a command after the pipe that the role permits. Present with an empty list means no pipe is allowed. |
roles.[].attributes |
map of list of string | Per platform (or all), attribute strings added to the reply. The key must be all, a platform port49 ships or one the inventory names. |
roles.[].max_session_age |
string | How long a session may last, a duration of at least 1m, or never. |
roles.[].per_login_mfa.accept |
list of string | The proofs that satisfy the requirement: a list of hardware_key_touch, idp_mfa or both, each at most once. Required when per_login_mfa is present; leave the block out for no requirement. |
roles.[].per_login_mfa.reuse_for |
string | How long one proof counts for later logins, a duration from 0m (a proof at every login) to 8h. Default 15m. |
roles.[].per_login_mfa.applies_to_machines |
boolean | Set true to require the proof of machine identities too. Default false. |
subjects¶
| Key | Type | Description |
|---|---|---|
subjects.[].name |
string | The subject's name. Required and unique. |
subjects.[].group |
string | A directory group of people, as a DN or a group name. Give group or identity (or machine_group), exactly one. The usernames come from the directory; do not set username. |
subjects.[].machine_group |
string | A group of workloads, resolved through the workload mapping. Not with group. Do not set username. |
subjects.[].identity |
string or list of string | A list of principals: user:<email> (lowercase), a SPIFFE ID, system:serviceaccount:<namespace>:<name> or app:<client_id>. Do not mix people and machines in one subject; a principal belongs to one subject. |
subjects.[].username |
string | The username the device sends, for an identity subject that has no directory to derive it from. Printable ASCII, already canonical (lowercase, no DOMAIN\ prefix, no @suffix), unique across subjects, and never remote, p49-* or a platform system user. |
subjects.[].enable |
string | How the subject escalates: none (default), auto or token. |
subjects.[].token_ttl |
duration | How long a device token lives, 30s to 900s. Required on a subject a grant names. |
subjects.[].api |
list of string | The broker API rights the subject holds, a list from revoke_others, explain_others, lift_revocations, forget_principals, reset_second_factor, unlock_sign_in, import_enrollments, sync, keys_show and replay. |
grants¶
| Key | Type | Description |
|---|---|---|
grants.[].subject |
string | The subject that gets the roles. Must be declared. |
grants.[].scope |
string | The scope the grant applies in. Must be declared. |
grants.[].roles |
list of string | The roles granted, at least one, each declared and listed once. Two grants with the same subject, scope and roles are refused: merge them. |
grants.[].max_privilege |
string | A ceiling on the privilege level a token from this grant may carry. A level some platform numbers. |
grants.[].expires |
string | When the grant stops matching: a date (2026-12-01, the start of that day, UTC) or an RFC 3339 time. |
grants.[].attributes |
map of list of string | Per platform (or all), attribute strings the grant adds to the reply. |
tests¶
| Key | Type | Description |
|---|---|---|
tests.[].subject |
string | The subject the test runs as. Give subject or principal, one of the two. |
tests.[].principal |
string | A principal identity the test runs as, in any form identity takes. |
tests.[].device |
string | The inventory device the test runs against. Required. |
tests.[].command |
string | The command to authorize. A test without a command is a session test. |
tests.[].expect |
string | permit or deny. Required with a command; not allowed on a session test. |
tests.[].reason |
string | Optional, with a command. The reason code the decision must carry for the test to pass, on a permit or a deny: for example no_grants_matched or permit_dominates. A code that is not on the reason codes page is refused at load. Not allowed on a session test. |
tests.[].level |
string | Session test: the privilege level the subject must be minted a token at. |
tests.[].max_privilege |
string | Session test: the ceiling the token must carry, a level or none. |
tests.[].priv_lvl |
string | Session test: the priv-lvl number (0 to 15) the reply must carry, or none. |
tests.[].enable |
string | Session test: the enable mode the subject must have, none, auto or token. |
tests.[].reply |
list of string | Session test: the exact session reply, in order, as a list of attribute strings. An empty list asserts an empty reply. |
tests.[].api |
list of string | Session test: the broker API rights the subject must hold, in any order. |
tests.[].per_login_mfa |
none or mapping | Session test: the effective per-login requirement on the device, none or a mapping of accept and reuse_for. |
acknowledge¶
| Key | Type | Description |
|---|---|---|
acknowledge.[].role |
string | Names a deny by its role. Give role and rule together. |
acknowledge.[].rule |
string | The deny rule as written in the role. It must exist. |
acknowledge.[].platform |
string | Names an adapter edit by its platform. Give platform, field and sha256 together. |
acknowledge.[].field |
string | The adapter key the edit changed. |
acknowledge.[].sha256 |
string | The digest port49 policy diff prints for the key's new value, 64 lowercase hex digits. The entry acknowledges that edit and no later one. |
acknowledge.[].reason |
string | Why the change is accepted. Required; review reads it beside the change. |
key_groups¶
| Key | Type | Description |
|---|---|---|
key_groups.[].match.name |
string | Matches one device by its inventory name. A group matches on exactly one of name, cidr or attributes. |
key_groups.[].match.cidr |
string | Matches the devices whose address is in this prefix. Written without host bits, for example 10.0.0.0/8. |
key_groups.[].match.attributes |
map of string | Matches devices whose inventory attributes equal all of these pairs. |
key_groups.[].key_ref |
string | The key (a name under keys:) that devices in the group are admitted under. Required. It names the key and never holds it. |
keys¶
| Key | Type | Description |
|---|---|---|
keys.<name>.current.version |
integer | Version number, 1 or greater and unique within the key. The key name is lowercase letters, digits and _, starting with a letter. |
keys.<name>.current.secret_ref |
secret reference | Where the shared secret is read from: env:NAME or file:/absolute/path. Required. The secret itself never appears in this file. port49d reads each version's secret once, when the version comes into force (at start, or in a policy reload that adds it); later reads, at SIGHUP and every 30s, only report a file that has changed or can no longer be read, and port49d keeps the secret it read. A changed secret goes in a new file under a new version. |
keys.<name>.current.retire_after |
string | Not allowed on the current version or a retired one. On an accept version, the last day (UTC, YYYY-MM-DD) it still admits; it is refused from midnight after that day. |
keys.<name>.accept.[].version |
integer | Version number, 1 or greater and unique within the key. The key name is lowercase letters, digits and _, starting with a letter. |
keys.<name>.accept.[].secret_ref |
secret reference | Where the shared secret is read from: env:NAME or file:/absolute/path. Required. The secret itself never appears in this file. port49d reads each version's secret once, when the version comes into force (at start, or in a policy reload that adds it); later reads, at SIGHUP and every 30s, only report a file that has changed or can no longer be read, and port49d keeps the secret it read. A changed secret goes in a new file under a new version. |
keys.<name>.accept.[].retire_after |
string | Not allowed on the current version or a retired one. On an accept version, the last day (UTC, YYYY-MM-DD) it still admits; it is refused from midnight after that day. |
keys.<name>.retired.[].version |
integer | Version number, 1 or greater and unique within the key. The key name is lowercase letters, digits and _, starting with a letter. |
keys.<name>.retired.[].secret_ref |
secret reference | Where the shared secret is read from: env:NAME or file:/absolute/path. Required. The secret itself never appears in this file. port49d reads each version's secret once, when the version comes into force (at start, or in a policy reload that adds it); later reads, at SIGHUP and every 30s, only report a file that has changed or can no longer be read, and port49d keeps the secret it read. A changed secret goes in a new file under a new version. |
keys.<name>.retired.[].retire_after |
string | Not allowed on the current version or a retired one. On an accept version, the last day (UTC, YYYY-MM-DD) it still admits; it is refused from midnight after that day. |