mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-23 03:27:50 +09:00
The shell translator rewrites test bodies and loses Bats reporting. The unit lane needs complete TAP results and JUnit reports without changing how live-cluster suites run. Sourced production helpers still need a dash pass, and unit test bodies need nounset without exporting shell options into subprocesses. Assisted-by: LLM Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
122 lines
5.8 KiB
Bash
122 lines
5.8 KiB
Bash
#!/usr/bin/env bats
|
|
# -----------------------------------------------------------------------------
|
|
# Semantic guard on what packages/system/cozystack-controller grants over core
|
|
# Secrets.
|
|
#
|
|
# The union is over every ClusterRole and Role the chart renders; bindings are
|
|
# never consulted. Today that is the same question as "what does the controller's
|
|
# own ServiceAccount hold", because the chart's ClusterRoleBinding and RoleBinding
|
|
# both name the cozystack-controller ServiceAccount and nothing else. A role added
|
|
# here for some other subject would fold into the union too, which errs toward
|
|
# refusing a grant rather than missing one.
|
|
#
|
|
# Two reconcilers share one Secret write grant: WildcardSecretReconciler
|
|
# replicates the operator wildcard TLS Secret into each tenant namespace, and
|
|
# CACertReconciler writes -- and withdraws -- the key-free "<release>.tenant-ca"
|
|
# projection. The controller is cluster-scoped, so any verb it holds on Secrets
|
|
# it holds on every Secret in every tenant namespace. That is why the set is
|
|
# pinned rather than merely bounded from above: a widening reaches all tenants,
|
|
# and a narrowing breaks both reconcilers.
|
|
#
|
|
# The check reads effective permission off the rendered manifests, not manifest
|
|
# text. A rule reaches core Secrets when its apiGroups admit "" and its
|
|
# resources admit "secrets" -- by name or via "*" on either axis -- and a "*" in
|
|
# its verbs expands to the verb set the API server defines for a namespaced
|
|
# resource. The union over every ClusterRole and Role the chart renders must
|
|
# equal the declared set exactly.
|
|
#
|
|
# Expansion is what makes the guard hold on the chart as it stands: the chart
|
|
# ends with a blanket `apiGroups: ['*'] resources: ['*'] verbs: [get,list,watch]`
|
|
# read rule, which does reach Secrets and whose verbs are already inside the
|
|
# declared set. A guard written as "no wildcard anywhere near secrets" would be
|
|
# red on an unmodified tree.
|
|
#
|
|
# The union is over the chart's default rendering: `helm template` is invoked
|
|
# with no --values, so a rule that appeared only under some values file would go
|
|
# unseen. templates/rbac.yaml carries no templating at all today, so nothing is
|
|
# gated behind a value; render the variants here too if that changes.
|
|
#
|
|
# The check lives here rather than beside the chart's helm-unittest suite because
|
|
# helm-unittest compares every field it is handed by value equality. `any: true`
|
|
# changes which fields are compared, not how they are compared, so neither the
|
|
# whole-rule form nor the `any: true` one can bound what a verbs list may hold --
|
|
# `verbs: ["get", "*"]` slips past both. That primitive is the right one for
|
|
# pinning a rule as written, which tests/rbac_test.yaml does, and it cannot
|
|
# express an upper bound: a wildcard in resources, a wildcard in apiGroups, a "*"
|
|
# appended to the declared verb list, an extra field beside the verbs, and a
|
|
# wider grant added as a separate rule while the declared one stays untouched all
|
|
# pass it.
|
|
#
|
|
# resourceNames is deliberately ignored. A rule scoped to named Secrets that
|
|
# grants "*" on them is still a wildcard verb grant, so the union counts it.
|
|
#
|
|
# An aggregated ClusterRole is the one widening this cannot see: it renders with
|
|
# no `rules` of its own, so whatever the aggregation confers stays outside the
|
|
# union. This chart declares no aggregationRule; a rule set assembled that way
|
|
# would need its own check.
|
|
#
|
|
# The assertion is an equality against a non-empty expected set, so a chart that
|
|
# fails to render, or a yq/jq breakage that yields nothing, fails the test
|
|
# rather than passing it vacuously.
|
|
#
|
|
# Requires: helm, yq (mikefarah v4+), jq -- the same three
|
|
# hack/admin-kubeconfig-invariant.bats needs to render and inspect a chart.
|
|
#
|
|
# Run with: bats hack/cozystack-controller-secret-rbac.bats
|
|
# -----------------------------------------------------------------------------
|
|
|
|
load test_helper
|
|
|
|
# What "*" in a rule's verbs means for a namespaced resource such as Secret.
|
|
VERB_UNIVERSE='["create","delete","deletecollection","get","list","patch","update","watch"]'
|
|
|
|
# The grant the chart declares, as a sorted set: read for both reconcilers plus
|
|
# the writes the two of them need. Sorted because the comparison below is on
|
|
# `unique`d jq output, which is sorted.
|
|
DECLARED_SECRET_VERBS="create,delete,get,list,patch,update,watch"
|
|
|
|
@test "cozystack-controller grants exactly the declared verb set on core Secrets in any spelling" {
|
|
chart="packages/system/cozystack-controller"
|
|
[ -d "$chart" ]
|
|
|
|
tmp=$(mktemp -d)
|
|
|
|
# helm's stderr is left alone: this chart renders silently, so there is nothing
|
|
# to suppress, and a chart that stops rendering should say why in the log
|
|
# rather than fail this line mutely.
|
|
helm template cozystack-controller "$chart" \
|
|
--namespace cozy-system \
|
|
> "$tmp/rendered.yaml"
|
|
|
|
# yq streams one JSON object per rendered document; jq -s slurps the stream so
|
|
# the rules of every ClusterRole and Role are one collection.
|
|
yq --output-format=json eval-all '.' "$tmp/rendered.yaml" \
|
|
| jq -s '
|
|
map(select(.kind == "ClusterRole" or .kind == "Role"))
|
|
| map(.rules // []) | add // []
|
|
| map(select(
|
|
((.apiGroups // []) | any(. == "" or . == "*")) and
|
|
((.resources // []) | any(. == "secrets" or . == "*"))
|
|
))
|
|
' > "$tmp/secret-rules.json"
|
|
|
|
effective=$(
|
|
jq --raw-output --argjson universe "$VERB_UNIVERSE" '
|
|
map(if ((.verbs // []) | any(. == "*")) then $universe else (.verbs // []) end)
|
|
| add // [] | unique | join(",")
|
|
' "$tmp/secret-rules.json"
|
|
)
|
|
|
|
if [ "$effective" != "$DECLARED_SECRET_VERBS" ]; then
|
|
echo "Effective verb set on core Secrets is not the declared one." >&2
|
|
echo " declared: $DECLARED_SECRET_VERBS" >&2
|
|
echo " rendered: $effective" >&2
|
|
echo "Rules reaching core Secrets:" >&2
|
|
cat "$tmp/secret-rules.json" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Effective verbs on core Secrets: $effective"
|
|
rm -rf "$tmp"
|
|
}
|