52 lines
2.5 KiB
Text
52 lines
2.5 KiB
Text
|
|
# schemas/lib/keeper_policy.ncl — Keeper auto-sign policy schema (ADR-038)
|
||
|
|
#
|
||
|
|
# Declarative-only closed shape parsed by the keeper-daemon Rust matcher.
|
||
|
|
# Policy files (policy-<workspace>/policy.ncl) MUST conform to PolicyDef and
|
||
|
|
# MUST NOT contain Nickel function definitions or imports beyond this schema.
|
||
|
|
# Constraint: policy-files-are-declarative-only (ADR-038).
|
||
|
|
#
|
||
|
|
# Usage:
|
||
|
|
# let kp = import "schemas/lib/keeper_policy.ncl" in
|
||
|
|
# { policy | kp.PolicyDef = { auto_sign = [...], require_manual = [...] } }
|
||
|
|
|
||
|
|
# Op type wildcard contract — superset of ops_contract.ncl OpsType that also accepts "*"
|
||
|
|
let OpTypeOrAny =
|
||
|
|
std.contract.custom (
|
||
|
|
fun label =>
|
||
|
|
fun value =>
|
||
|
|
let valid = ["deploy", "scale", "restart", "secret_update", "drain", "*"] in
|
||
|
|
if std.array.any (fun x => x == value) valid then
|
||
|
|
'Ok value
|
||
|
|
else
|
||
|
|
'Error {
|
||
|
|
message = "Invalid op_type '%{value}'.\nValid values: deploy | scale | restart | secret_update | drain | *"
|
||
|
|
}
|
||
|
|
)
|
||
|
|
in
|
||
|
|
|
||
|
|
# A single match rule — all fields are glob patterns applied by the Rust matcher.
|
||
|
|
# Absent / defaulted-to-"*" field means "match any value for this dimension".
|
||
|
|
# The matcher evaluates rules top-to-bottom; first matching rule wins.
|
||
|
|
let _MatchRule = {
|
||
|
|
op_type | OpTypeOrAny | doc "Op type this rule applies to; '*' matches any op type" | default = "*",
|
||
|
|
image_patterns | Array String | doc "Glob patterns matched against OCI image reference in the op payload (deploy ops only)" | default = ["*"],
|
||
|
|
target_patterns | Array String | doc "Glob patterns matched against the op target name (e.g., 'staging-*', 'vapora')" | default = ["*"],
|
||
|
|
scope_patterns | Array String | doc "Glob patterns matched against JWT scope entries (<op_type>:<target_pattern>)" | default = ["*"],
|
||
|
|
} in
|
||
|
|
|
||
|
|
# Top-level policy file schema. Evaluation order: auto_sign rules checked first (top-to-bottom),
|
||
|
|
# then require_manual. If no rule matches, the op is held pending for manual review.
|
||
|
|
let _PolicyDef = {
|
||
|
|
version | Number | doc "Schema version — keeper-daemon rejects files with unknown versions" | default = 1,
|
||
|
|
auto_sign | Array _MatchRule | doc "Rules for operations the keeper-daemon may sign automatically" | default = [],
|
||
|
|
require_manual | Array _MatchRule | doc "Rules for operations that must be signed interactively via keeper-cli" | default = [],
|
||
|
|
} in
|
||
|
|
|
||
|
|
{
|
||
|
|
OpTypeOrAny = OpTypeOrAny,
|
||
|
|
MatchRule = _MatchRule,
|
||
|
|
PolicyDef = _PolicyDef,
|
||
|
|
|
||
|
|
make_policy | not_exported = fun data => data | _PolicyDef,
|
||
|
|
}
|