Skip to content

[dmt] feat: rbac.yaml module declaration with contract, coverage and sync rules - #479

Draft
Jabejixo wants to merge 30 commits into
mainfrom
feat/rbac-yaml
Draft

Jabejixo wants to merge 30 commits into
mainfrom
feat/rbac-yaml

Conversation

@Jabejixo

@Jabejixo Jabejixo commented Sep 21, 2026

Copy link
Copy Markdown

Description

One declaration per module, modules/<module>/rbac.yaml, describes the user-facing access to every resource the module ships in both role models (RBACv2 capabilities and legacy user-authz roles), the rights of its ServiceAccounts and the access it grants to others. Three new rules of the rbac linter work with it; the templates are generated from it by --fix.

  • contract — runs on every module, rbac.yaml or not. It holds the rendered ClusterRoles under templates/rbacv2/ to the platform's label and naming contract (d8: prefix, rbac.deckhouse.io/{kind,scope,capability,aggregate-to-<lineage>-as,use-role,delegatable}, the four en|ru.meta.deckhouse.io/* annotations, per-lineage levels, the module label). It is a port of the platform's testing/rbacv2 validation, so a module outside the platform repository is checked the same way. One check beyond the platform test: a cluster-scoped resource inside a namespace capability is a warning — such a rule grants nothing through a RoleBinding.
  • coverage — with rbac.yaml: every CRD under crds/ (recursively, by kind) has an entry that grants levels or denies access with a reason. noAccess: "TODO" is an error of its own: a decision is owed. --fix appends an undecided stub for a missing entry and deliberately leaves the run red — the stub is not a decision.
  • sync — with rbac.yaml: the rendered objects and the declaration say the same thing, in both directions, compared as (apiGroup, resource, resourceName, verb) tuples plus aggregation edges, roleRefs and subjects. It owns three classes of rendered objects (legacy roles by annotation, module capabilities by kind and module label, objects whose names the generator builds); everything else in the render is neither generated nor reported. --fix regenerates a file from the declaration with two safeguards: a file without the generator header is maintained by hand (the generated text lands beside it as <file>.generated), and a regeneration that would drop a right the render grants today is refused and names the right — removing access is always a person's decision. Under --matrix the fix runs once per file and judges the union of every variant's render.
  • The declaration format (rbac.deckhouse.io/v1alpha1) is validated before anything is compared or generated: explicit verb lists, namespace levels only for namespaced resources, a scope for resources the module ships no CRD for, a reason for whole-group entries and for namespaced resources granted cluster-wide, localized texts for capabilities outside the view/edit convention, when conditions that parse as Helm expressions. Unknown keys are errors.
  • Configuration: global.linters-settings.rbac.rules.{contract,coverage,sync}.impact sets each rule's level on its own; linters-settings.rbac.exclude-rules.{contract,coverage,sync} excludes objects or group/resource keys. The rbac configuration blocks refuse unknown keys. The three rules start at warn wherever nothing sets them, whatever the linter's impact is; the four original rules keep their level and their output untouched. One change outside the linter: --fix no longer runs the autofix of a finding at the ignored level.
  • The three states a module can be in when this dmt first runs are handled and covered by e2e cases: only the RBACv2 scheme before DKP 1.78 (kind: use|managecontract gives one "migrate" finding per object instead of failing every check; with an rbac.yaml, sync names the legacy template as the cause), only the 1.78 scheme, and both behind the version gate rbacv2-migrate-module.sh writes (the linter's values answer the gate with the new model, the legacy branch is neither judged nor "extra", and --fix never rewrites a gated file).
  • no-cyrillic/files no longer judges the ru.meta.deckhouse.io/title|description lines: the role model requires that Russian text and the contract rule enforces it, so the two rules contradicted each other -- 41 in-tree modules carry an identical exclude-rules entry for templates/rbacv2 to reconcile them, and every external module would have needed one. Cyrillic anywhere else in the same file is still reported.
  • Verified against the changes a module goes through (a CRD added, removed, renamed or rescoped; a verb dropped; a hand-added rule; a deleted file or declaration; subsystems changed; subresources, whole-group entries, rules under when), on a regenerated copy of an external module. Three findings from that pass are in: a noAccess entry whose group lost every CRD is a coverage warning unless it names a scope; a generator-owned file (header present) is compared as text with what the declaration renders, so a rule under when that is false today still reaches the template through --fix; the copy written beside a hand-maintained file is _<file>.generated, because Helm renders every file under templates/. A fourth came from the full loop on an external module (every RBAC template deleted and regenerated from the declaration): a file whose objects are all under when was never written back, because their absence from the render is not a divergence (D4) -- a declared file that does not exist while an object it holds is absent from the render is now reported and created. The loop ends with 646 rules, 26 bindings and ServiceAccounts and 18 role-model labels identical before and after, modulo the renames the generator's naming forces.
  • The same loop on three platform modules (cert-manager, user-authz, multitenancy-manager) added two more: sync owns only the module's own namespace/system capabilities by name -- the project lineage of multitenancy-manager and the platform-wide capabilities of user-authz are not the declaration's and stay hand-written; and a generated file that also holds objects the declaration does not produce (cert-manager's webhook:requester, user-authz's permission-browser ClusterRoles) is never rewritten, since the generator writes the whole file. no-cyrillic skips rbac.yaml as it skips module.yaml: the declaration carries the Russian capability texts the role model requires.
  • Fifteen corner cases on a synthetic module (a module without subsystems or with a non-d8 namespace, a marker past 63 characters, an account named unlike its directory, an object rendered from another file than the declaration places it, braces in when, core-group resources, BOM and CRLF, an rbac.yaml of the earlier shape, duplicate objects and subjects, a conditional object off by default at bootstrap) closed nine more gaps; the generator now refuses what the declaration alone cannot know is wrong -- system levels without a subsystem, a marker too long for a label, an account the placement rule would reject -- and built-in Kubernetes resources need no scope.
  • Docs: pkg/linters/rbac/README.md has the format, the three rules and the developer loop (rbac.yamldmt lint --linter rbac --fixdmt lint). e2e cases under test/e2e/testdata/rbac/.

What the findings look like on the platform tree today (--linter rbac, 47 modules; the base dmt reports nothing there):

🐒 [contract (#rbac)]
     Message:      capability "d8:namespace-capability:cert-manager:view" grants cert-manager.io/clusterissuers, a
                   cluster-scoped resource, in a namespace capability: bound through a RoleBinding the rule grants
                   nothing; move it to a system capability
     Module:       cert-manager
     Object:       ClusterRole/d8:namespace-capability:cert-manager:view
     FilePath:     templates/rbacv2/use/view.yaml

🐒 [sync (#rbac)]
     Message:      parse rbac.yaml: yaml: unmarshal errors: ...; nothing is compared or generated until the declaration parses
     Module:       cert-manager
     FilePath:     rbac.yaml

Under --matrix the same 10 findings appear; every other rule's output on the tree, default render and matrix alike, is identical to the base. On two external modules still on the pre-1.78 scheme (operator-trivy, security-profiles) the run gives four "legacy scheme" warnings each and no errors.

The sync warnings come from six rbac.yaml files of an older, never consumed shape that still sit in the platform tree; their removal is a separate platform PR.

How an existing module enters the declaration

Without rbac.yaml, sync reports the declaration missing and --fix writes it from the RBAC objects the module renders today: the file a person would have transcribed from the templates, with a TODO wherever a decision is still theirs (a resource without a CRD whose scope the linter cannot know, a CRD nobody grants, a namespaced resource granted cluster-wide) and a note on top for every object the generator will name differently or cannot describe. The person reviews it, resolves the TODOs, and runs --fix again: the templates are regenerated from the declaration, which is the source from then on -- a right removed from it leaves the template, a CRD added to the module gets an undecided stub, a new ServiceAccount is declared and generated.

The format grew what the platform modules needed for this: serviceAccounts[].extraClusterRoles (further ClusterRoles in the account's file, bound to it or not), serviceAccounts[].automountServiceAccountToken (the import keeps the token mounted where it was, so no pod loses it), access[].path (grants kept in a component's files).

Verified end to end on four modules -- operator-trivy after the platform's migration script (both RBACv2 schemes behind the version gate), cert-manager, user-authz and multitenancy-manager: every RBAC template regenerated from the written declaration, the rendered rights identical before and after (646, 339→336 for the dead clusterissuers rule, 2367 and 206 rule tuples), only the object names the generator's convention forces changed; the project-lineage capabilities of multitenancy-manager and the platform-wide roles and capabilities of user-authz stay hand-written and are listed as such.

Why do we need it, and what problem does it solve?

A module's RBAC lives in four places that nobody keeps in step: RBACv2 capability templates, legacy user-authz roles, rbac-for-us.yaml and rbac-to-us.yaml. On the platform tree 66 of 181 CRDs have no user-facing access at all, and nothing says whether that is a decision or an omission. When the platform's role contract changes, every module is edited by hand, and the platform test that checks the contract sees only in-tree modules — external modules drift silently until aggregation breaks in a cluster.

The declaration makes the decision explicit per resource and the templates a derivative of it. The linter is where this belongs: dmt already renders the chart, so sync compares rendered objects rather than template text, the developer keeps the freedom to add conditions and comments, and an external module gets the same contract check as an in-tree one from the tool it already runs.

Design: platform-security/2026-04-27-module-rbac-yaml.md (architecture-decision-records, branch rbac-v2, revision ccd35e0 of 2026-09-22 -- the ADR text now describes exactly this implementation: the bootstrap from the render, the declaration as the source, the three owned classes, extraClusterRoles / automountServiceAccountToken / path / reason, the version gate, the no-cyrillic exemptions and the warn default). A regeneration logs the rights and objects it removes, so a --fix run without a preceding dmt lint does not remove rights in silence.

…t constants

rbacyaml loads and validates modules/<module>/rbac.yaml (rbac.deckhouse.io/v1alpha1): strict keys, one
entry per resource with the levels of both role models or a documented denial, scope from the module's
CRDs or declared for external resources, verbs listed explicitly, localized texts required for
capabilities outside the view/edit convention, and a reason wherever the declaration widens access
beyond what the resource alone implies (a whole group, a namespaced resource at a system level).
Normalization gives the generator and the sync rule a canonical order.

rbaccontract holds the lineages, per-lineage levels, legacy access levels, label keys and the
conventional localized texts, each with its source in the deckhouse repository.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…on in rbac.yaml

coverage runs only when the module has an rbac.yaml. It reads the CRDs under crds/ at any depth,
selecting them by kind, and requires an entry for each: levels of either role model, or noAccess with
the reason. A CRD without an entry gets an autofix that appends an undecided stub (noAccess: "TODO")
and then still reports the finding -- the stub is not a decision, and a --fix run that wrote stubs
must not end green. A stub left as TODO is an error without a fix; a resource of a known group that no
CRD spells is a warning.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
… contract on rendered objects

contract ports the first part of deckhouse/testing/rbacv2/rbacv2_templates_validation_test.go to the
rendered ClusterRoles under templates/rbacv2/, so that a module outside the platform repository is
held to the same contract: the d8: prefix, the four localized annotations, kind and scope labels,
the shape of roles and capabilities, aggregation labels with a lineage and a level of that lineage,
delegatable only on namespace/project roles. New here, as a warning for now: a cluster-scoped
resource inside a namespace capability grants nothing through the RoleBinding it is bound with. The
scope comes from the module's CRDs or its rbac.yaml; a resource the run knows nothing about is not
judged. The rule needs no rbac.yaml.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…per-rule levels and exclusions

The root configuration now accepts linters-settings.rbac.rules.{coverage,sync,contract}.impact, and
the module configuration exclude-rules.{coverage,contract,sync}; the levels fall back to the
linter's. The four original rbac rules keep the linter level as before: wiring them up would change
the severity of existing findings. Both new rules are registered in the static scope.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…plates rendered from it

generate.Build derives the RBAC objects a declaration produces (namespace and system capabilities,
legacy roles, ServiceAccount rights, external access) with their names, labels, localized texts and
rules, following the ADR table; generate.Render writes them as Helm templates under a header the
sync autofix recognizes. The model is what the sync rule compares the rendered chart against, so the
comparison and the generated text can never disagree. Golden files for the cert-manager example
pin the output; rendering is a pure function of the model.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…in both directions, and --fix regenerates the templates

sync compares the rendered objects of the three classes it owns (legacy roles, the module's
capabilities, the objects the generator names) with the model built from the declaration: rules as
(group, resource, name, verb) tuples, aggregation edges of capabilities, binding subjects and role
references, the marker and module/namespace labels the generator writes. A rule under when that did
not render is not a divergence; a rule without when that did not render is. Findings are one per
template file and carry the fix command; an object the declaration does not produce in a file it
does not generate is a person's decision.

The autofix regenerates a file from the declaration with two safeguards: a file without the
generator header is maintained by hand and gets the generated text beside it as <file>.generated;
a regeneration that would drop a right or an aggregation edge the render grants today is refused
with the list of what would be lost. Everything the fix needs is captured while the render exists.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…through helm_lib

The cases vendor deckhouse_lib_helm as the container cases do, and every one asserts that the module
rendered (expectPass on the manager linter): without it a render failure would satisfy any expectPass.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
viper drops an unknown key without a word; for the per-rule levels and exclusions of the rbac linter
that silence leaves a rule at full strength -- or off -- with nobody noticing. The two rbac blocks
(global.linters-settings.rbac with its rules, linters-settings.rbac with its exclude-rules) are held
to their known keys; the other linters keep the lenient behaviour. Adds the mapping test for the
per-rule levels: coverage, sync and contract read their own level from the root configuration and
fall back to the linter's, the four original rules keep the linter level.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…nd sync rules

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
A rule switched off with impact: ignore still had its fix closures collected
and run by --fix, so files could change on behalf of findings nobody sees.
GetFixes now skips findings at the ignored level.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…spec

- contract: the module label of a framework role or capability must be the
  module's name (R21); helpers without a kind label, such as d8:dict, are not
  judged.
- sync: a generated file whose header names another contract version is a
  divergence and is regenerated (R40); an rbac.yaml inside an edition overlay
  (ee/modules, ee/be/modules, ...) is an error, the declaration lives in
  modules/<module>/ only (R8a, D7).
- coverage and sync autofixes run once per target however many render
  variants reported it (R36); the render variants record what they grant at
  lint time and the sync fix judges the union, so a right rendered only under
  some values is not dropped (D3).
- rbac.yaml: a when condition must parse as a Helm expression (R13c).
- e2e: the hand-labelled contract cases carry the module label.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
contract, coverage and sync are new to every tree: a module without rbac.yaml
sees only contract, and the platform tree still carries six dead rbac.yaml
files of an older shape and rules the contract flags. Like the style rules of
the documentation linter they now default to warn, whatever impact the rbac
linter has, and a tree raises them to error in its root .dmtlint.yaml once its
modules are clean. The four original rules keep the linter level.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
@Jabejixo Jabejixo added enhancement New feature or request go Pull requests that update go code labels Sep 21, 2026
…e that serves both

A module meets the new rules in one of three states: only the manage/use
scheme that preceded the 1.78 role model (an external module not yet
migrated), only the 1.78 scheme, or both behind the version gate that
rbacv2-migrate-module.sh writes (include "<module>.rbacv2_new_scheme").

- contract: a legacy object (kind: use or manage) gets one finding -- migrate
  -- instead of failing every check of the contract; a legacy object rendered
  from a gated template is not reported, the module serves both models on
  purpose.
- sync: when a generated file's template renders the legacy scheme, the
  finding names that as the cause once instead of listing the absent objects
  as a mystery; the legacy objects are not reported as extra.
- sync --fix: a gated template is never regenerated -- regenerating it would
  drop the legacy branch -- and the refusal says so; the gate is detected by
  its helper name, not only by a literal deckhouseVersion. The check runs
  before the header guard, so a gated file is not called hand-maintained.
- e2e: one case per state (scheme-legacy-only, scheme-legacy-with-declaration,
  scheme-dual, the last written by the platform's migration script).

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
The RBACv2 role model requires a Russian title and description on every
role and capability (ru.meta.deckhouse.io/*), and the rbac contract rule
enforces them; the files rule then reported the same lines as Cyrillic in
source. 41 in-tree modules carry an identical exclude-rules entry for
templates/rbacv2 to reconcile the two, and every external module would need
one too. The files rule now skips those annotation lines; Cyrillic anywhere
else in the same file is still reported.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
Thirteen changes a module goes through were run against the rules: a CRD
added, removed, renamed or rescoped; a verb dropped from the declaration; a
rule added to a template by hand; a generated file or the declaration
deleted; subsystems changed; a subresource and a whole-group entry; a rule
under when. Three did not hold:

- coverage: a noAccess entry whose group has no CRD left in the module was
  silent -- a removed CRD is indistinguishable from an external resource
  nobody grants. It is a warning now unless the entry names a scope.
- sync: a rule under when whose condition is false today is absent from the
  render without being a divergence (D4), so it never reached the template
  through --fix. For a file that carries the generator header the text is
  now compared with what the declaration renders; a file without the header
  is still judged by its render only. A file of another contract version is
  the same check.
- sync --fix: the copy written beside a hand-maintained file lived inside
  templates/, and Helm renders every file there whatever its extension, so
  the module rendered a second set of objects. The copy is _<file>.generated
  now: an underscore-prefixed file is a partial to Helm.

The drop-guard refusal names the way out: delete the file and run --fix
again to regenerate it without the right.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…rgence

The full loop on an external module -- every RBAC template deleted, --fix
asked to write them back -- lost three ServiceAccounts and 88 rules without
a finding: the files held only objects under when, the conditions were true
and the objects rendered before, and once the files were gone their absence
from the render read as "conditional, not rendered" (D4). The render cannot
tell a false condition from a template nobody wrote; the text can. A file
the declaration produces that does not exist while an object it holds is
absent from the render is now reported and created by --fix. A file that
exists is judged as before.

The sync tests write the generated files to disk before simulating the
render, as a module that rendered them would have them.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…ger showed

Running the whole cycle -- declaration from the rendered objects, every RBAC
template regenerated -- on three platform modules found three things:

- sync owned every capability that carried the module label, including the
  ones the declaration cannot produce: the project lineage of
  multitenancy-manager and the platform-wide capabilities of user-authz,
  which are named after a lineage rather than the module. They were about
  to be reported as objects the declaration does not produce. The
  capability class is now the module's own namespace and system
  capabilities by name; the rest stays hand-written and unreported (D2).
- a generated file is written whole, so an object in it that the
  declaration does not produce -- a controller ClusterRole beside a declared
  ServiceAccount (cert-manager's webhook, user-authz's permission browser)
  -- would vanish with the rewrite, and the advice to delete the file and
  run --fix again would lose it too. The fix now refuses first of all when
  the file holds such objects and names them; every render variant's list
  is joined.
- no-cyrillic judged rbac.yaml for the Russian titles and descriptions the
  declaration requires of capabilities outside the view/edit convention;
  it is the module's documentation, like module.yaml, and is skipped.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
… from its render

Decided with the platform owner on 2026-09-22, after the loop on four modules:

- bootstrap: a module without rbac.yaml gets a sync finding, and --fix writes
  the declaration from the RBAC objects the module renders today -- the file
  a person would have transcribed from the templates, with a TODO wherever a
  decision is still theirs (a resource with no CRD and an unknown scope, a
  CRD nobody grants, a namespaced resource granted cluster-wide) and a note
  for every object the generator will name differently or cannot describe.
  From then on rbac.yaml is the source and the templates follow it. This
  replaces R22 ("contract only without a declaration") and the ADR's "coverage
  creates the file".
- the declaration wins: a right it no longer names leaves the template on
  --fix, the finding that led there having listed it. This replaces D3
  ("autofix never removes a right"), whose danger -- the first run deleting
  what nobody had declared yet -- is gone once the declaration starts from
  the render.
- serviceAccounts[].extraClusterRoles: further ClusterRoles in the account's
  file, bound to it or not (cert-manager's per-controller roles, an
  aggregated apiserver's requester role), named d8:<module>:<account>:<name>
  or exactly as given when they start with d8:.
- serviceAccounts[].automountServiceAccountToken: the generator no longer
  forces false; the import keeps true where the account mounted its token,
  so no pod loses it.
- access[].path: cluster and namespace grants may live in a component's
  rbac-for-us.yaml / rbac-to-us.yaml, where the module kept them.
- the foreign-objects safeguard recognizes an object the generator produces
  under another name -- a binding with the same roleRef and subjects, a role
  with the same rules -- as replaced, not lost.

On operator-trivy, cert-manager, user-authz and multitenancy-manager the flow
now runs end to end: every RBAC template regenerated from the written
declaration, the rendered rights identical before and after up to the dead
clusterissuers rule of cert-manager and the renames the generator's naming
forces.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…nd the declaration as the source

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…n CI)

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
Fifteen corner cases were run against the rules on a synthetic module (a
module without subsystems or with a non-d8 namespace, a marker past 63
characters, an account named unlike its directory and nested directories,
a declared object rendered from another file, when conditions with braces,
core-group resources and wildcard verbs, BOM and CRLF in rbac.yaml and in a
generated file, an rbac.yaml of the earlier shape, duplicate objects, a
bootstrap with a conditional object off by default, a read-only template,
duplicate subjects). Nine did not hold:

- system levels on a module that aggregates into no subsystem produced
  capabilities nobody aggregates; the generator refuses and asks for
  subsystems in rbac.yaml.
- a capability marker longer than a label value (a module name of 32
  characters and up with a namespace superadmin level) was written and
  would fail the contract; the generator refuses.
- an account named unlike its directory, or in a nested directory, produced
  objects the placement rule rejects; the generator refuses with the names
  placement wants.
- an object the declaration produces but rendered from another file was
  called foreign without saying where it belongs; the refusal now names the
  file the declaration puts it in.
- built-in Kubernetes resources (""/configmaps, apps/deployments, ...)
  needed an explicit scope; the validator and the importer share one table
  of well-known scopes, and an unknown resource still asks for one.
- an rbac.yaml of the earlier, never consumed shape gave a bare parse error;
  it is named for what it is, with the way out.
- under --matrix the first declaration was written from whichever variant
  ran its fix first; it is written from the union of every variant, and the
  file's header says that objects off by default need a run with
  --values-file before the first regeneration.
- a generated file with CRLF line endings lost its header and became
  hand-maintained in silence; the header is recognized and the file
  regenerated.
- duplicate subjects of an access entry passed validation.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…he corner-case behaviour

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
The ADR (ccd35e0) names it among the drawbacks: a --fix run without a
preceding dmt lint would remove rights the declaration no longer names with
nothing said. The autofix now logs, per regenerated file, the rights and
objects the render had and the declaration did not name, and logs the plain
regeneration otherwise.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
… bootstrap

A third round of corner cases on a synthetic module and on the edition
overlay of admission-policy-engine:

- a generated file the declaration produces nothing for any more -- every
  namespace level dropped, the legacy section gone -- stayed with a finding
  only a person could close. It is the generator's file and the declaration
  wins: --fix deletes it when it holds nothing but objects of the owned
  classes, and logs the deletion; a foreign object or the version gate keeps
  it, and the finding says which.
- the bootstrap left the scope implicit for resources whose CRD is in the
  module. A lint of one edition directory does not see the CRDs of the other
  editions (ratify of admission-policy-engine lives in ee/se-plus), and an
  entry without a scope then stopped the whole validation instead of raising
  one coverage warning. Every entry now carries its scope, denied ones too.
- a generator header with trailing whitespace handed the file over to a
  person in silence; the header is recognized.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…ety, quoting

sync
- exclude-rules.sync silences an object's findings without forgetting the object:
  the exclusion is applied when findings are emitted, so an excluded declared
  object is no longer reported as absent.
- ServiceAccount objects are compared: a token the render mounts and the
  declaration does not is a divergence, since the regeneration would drop it.
- A rendered binding under another name counts as a rename only when it points
  at the same role, or at a role rendered in the same file whose rules the
  produced role carries. A binding to cluster-admin is a foreign object.
- The orphan-file fix judges the union of render variants and re-reads the
  header and the gate before deleting; a file another variant placed a foreign
  object in stays.
- The gate is detected by the migration helper or a deckhouseVersion test in a
  template action that the declaration itself did not produce, so a declared
  `when` on the platform version no longer blocks regeneration forever.
- Files are written through a temporary file and a rename; the aside copy gets
  the permissions of a source file.

validation and generation
- resources require apiGroups (the core group is "").
- Generated names are quoted unless they are plain scalars; YAML 1.1 words
  (no, yes, on, off, null) and numbers are quoted.
- Two declared roles that map to one generated binding name are refused.
- Validation errors, CRD ordering and the bootstrap input have one order.

contract
- The level in a role name is checked against the lineage of its scope.
- One warning per cluster-scoped group/resource in a namespace capability.
- ProjectLevels no longer shares its backing array with NamespaceLevels.

bootstrap
- A resource every grant of which carried resourceNames is written as an
  undecided noAccess entry instead of being widened to every object.

coverage
- exclude-rules.coverage also silences the misspelling warning of the entry.

Tests cover each change; the READMEs describe the current behaviour.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
…olintlint

- The rbac config blocks are checked down to a rule's impact and the kind/name
  of an exclusion entry, and every unknown key is reported in one error.
- global.RbacLinterConfig/RbacRules follow the RBAC initialism of pkg/config.
- BindingSuffix and LevelOfAction live in rbaccontract; bootstrap and generate
  share them. ResourceVerbs is the verb list without the wildcard.
- sortedKeys, indexByte, IsSubresource and FullName use the standard library.
- SyncRule.Check is three steps: compareRender, compareText, report.
- fixOnce holds its own lock while the fix runs; the records keep theirs.
- legacyFiles names one kind per file whatever order the storage yields.
- The bootstrap builder has one blank line between methods; a redundant
  condition is gone; fmt.Errorf without a format is errors.New.
- no-cyrillic skips the module's own rbac.yaml only, not every file of that name.
- nolintlint requires a named linter and a reason on every directive; the
  unused check stays off because it depends on the golangci-lint version.
- rbaccontract has unit tests; the loader tests cover the nested keys.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant