Ctrl K
ComunidadGuías…

hack/cozystack-controller-secret-rbac.bats

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/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"
}