Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
36d01d7
rbac: add the rbac.yaml declaration format and the role-model contrac…
Jabejixo Sep 21, 2026
b51b44f
rbac: add the coverage rule -- every CRD of the module needs a decisi…
Jabejixo Sep 21, 2026
a391a16
rbac: add the contract rule -- the platform's RBACv2 label and naming…
Jabejixo Sep 21, 2026
f2287f0
rbac: register contract and coverage, and give the declaration rules …
Jabejixo Sep 21, 2026
187090f
rbac: e2e cases for the contract and coverage rules
Jabejixo Sep 21, 2026
0f47992
rbac: add the rbac.yaml generator -- an object model and the Helm tem…
Jabejixo Sep 21, 2026
6d9af36
rbac: add the sync rule -- the rendered RBAC objects match rbac.yaml …
Jabejixo Sep 21, 2026
091c1de
rbac: e2e cases for the sync rule, rendering the generated templates …
Jabejixo Sep 21, 2026
fcf06b0
rbac: refuse unknown keys in the rbac configuration blocks
Jabejixo Sep 21, 2026
13a0e5c
rbac: document the rbac.yaml declaration and the contract, coverage a…
Jabejixo Sep 21, 2026
d4df0d8
errors: keep the autofix of an ignored finding off
Jabejixo Sep 21, 2026
3fc2f7a
rbac: close the gaps found by checking the rules against the ADR and …
Jabejixo Sep 21, 2026
404685e
rbac: start the declaration rules at warn wherever nothing sets them
Jabejixo Sep 21, 2026
7693c75
rbac: recognize the RBACv2 scheme before DKP 1.78 and the version gat…
Jabejixo Sep 21, 2026
053bcad
no-cyrillic: do not judge the localized RBAC annotations
Jabejixo Sep 21, 2026
94b62b9
rbac: close the gaps the module lifecycle scenarios showed
Jabejixo Sep 21, 2026
fed51cb
rbac: a missing generated file whose objects did not render is a dive…
Jabejixo Sep 22, 2026
1db0ec1
rbac: what the loop on cert-manager, user-authz and multitenancy-mana…
Jabejixo Sep 22, 2026
84b898f
rbac: document the capability class and the foreign-objects safeguard
Jabejixo Sep 22, 2026
9fa2e86
rbac: the declaration is the source, and an existing module enters it…
Jabejixo Sep 22, 2026
5e69030
rbac: document bootstrap, extraClusterRoles, automount, access path a…
Jabejixo Sep 22, 2026
fd05e30
rbac: preallocate in the bootstrap object filter (golangci-lint 2.8 i…
Jabejixo Sep 22, 2026
daa03e2
rbac: preallocate the remaining object slices the CI linter flags
Jabejixo Sep 22, 2026
77a5017
rbac: what the corner cases showed
Jabejixo Sep 22, 2026
9161c2a
rbac: whitespace and preallocation the CI linter asks for; document t…
Jabejixo Sep 22, 2026
92fcfd8
rbac: a blank line the CI linter asks for
Jabejixo Sep 22, 2026
be57406
rbac: log what a regeneration removes
Jabejixo Sep 22, 2026
317f3dc
rbac: orphaned generated files are deleted, scopes are written out at…
Jabejixo Sep 22, 2026
bc99053
rbac: review fixes -- exclusions, automount, rename check, orphan saf…
Jabejixo Sep 22, 2026
3d5b54e
rbac: review leftovers -- config keys, shared helpers, Check split, n…
Jabejixo Sep 22, 2026
433eab0
rbac: prometheusAccess.when gates the scraper binding; bootstrap leav…
Jabejixo Sep 23, 2026
fc4028e
rbac: review of #479 -- ownership, wildcards, injection, cross-file n…
Jabejixo Sep 23, 2026
5f1b34b
rbac: review of #479 -- levels, bootstrap, conditions, robustness
Jabejixo Sep 23, 2026
ce86638
rbac: second review of #479 -- moves, orphans on disk, levels per ADR
Jabejixo Sep 23, 2026
9427468
rbac: bootstrap keeps an account's aggregated ClusterRole hand-written
Jabejixo Sep 23, 2026
c40469a
rbac: third review of #479 -- no moves, text-based ownership, TODO va…
Jabejixo Sep 24, 2026
b33f123
rbac: regression hunt on c40469a -- text parser, duplicates, logs
Jabejixo Sep 24, 2026
c9ee764
rbac: follow the placement rule for nested paths, carry account annot…
Jabejixo Sep 24, 2026
29e6d89
rbac: bootstrap reads the template conditions around each object
Jabejixo Sep 24, 2026
1890d6f
rbac: tighten the declaration checks found by the regression hunt
Jabejixo Sep 24, 2026
8f16d2e
rbac: preallocate the object list of replacedCopies (prealloc on gola…
Jabejixo Sep 24, 2026
d0d4b43
rbac: second regression hunt -- placement names, metadata, copies, logs
Jabejixo Sep 24, 2026
389416e
rbac: second regression hunt -- template conditions that render
Jabejixo Sep 24, 2026
564dff2
rbac: write a bootstrapped declaration that does not parse, naming th…
Jabejixo Sep 24, 2026
ab822c0
rbac: keep the pilot PR to what the ADR describes
Jabejixo Sep 24, 2026
15f9c93
rbac: an unknown template action anywhere in a document makes it foreign
Jabejixo Sep 24, 2026
a5b8fff
rbac: recognize the generator's labels line only in the shapes it writes
Jabejixo Sep 24, 2026
83f55fe
rbac: bootstrap leaves library, repeated and empty objects hand-written
Jabejixo Sep 24, 2026
27c41af
rbac: refuse the names the placement rule would reject
Jabejixo Sep 24, 2026
1520624
rbac: name the kube-system account naming gap as a known limitation
Jabejixo Sep 24, 2026
3ab9bc1
rbac: note per object what a regeneration drops that the format canno…
Jabejixo Sep 24, 2026
e394b0e
rbac: review of #479, findings 41-43 and 46
Jabejixo Sep 24, 2026
c074c33
rbac: a partially rendered capability leaves a TODO reason
Jabejixo Sep 24, 2026
d62d257
rbac: document that objects sharing a file with a helm_lib include st…
Jabejixo Sep 24, 2026
547f5c9
rbac: review of #479, findings 47-51
Jabejixo Sep 24, 2026
cfd42f4
rbac: review of #479, findings 52, 53 and the 48 wording
Jabejixo Sep 24, 2026
c1d2a0a
test(e2e): link the rbac cases to the shared helm_lib archive
Jabejixo Sep 25, 2026
7d8bc44
rbac: review of #479, the orphaned TODO and the dead .tpl case
Jabejixo Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ linters:
- ineffassign
- misspell
- musttag
- nolintlint
- nonamedreturns
- prealloc
- revive
Expand All @@ -22,6 +23,12 @@ linters:
- whitespace
- wsl_v5
settings:
nolintlint:
# Whether a directive is still needed depends on the golangci-lint version (CI and
# developers differ); what every directive must have is a named linter and a reason.
allow-unused: true
require-explanation: true
require-specific: true
depguard:
rules:
logger:
Expand Down
4 changes: 4 additions & 0 deletions cmd/dmt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,9 @@ func runLint(ctx context.Context, src manager.Source) error {
return errors.New("critical errors found")
}

if flags.Fix && mng.HasFailedFixes() {
return errors.New("some fixes did not close their findings; see AutofixError")
}

return nil
}
5 changes: 5 additions & 0 deletions internal/manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,11 @@ func (m *Manager) HasCriticalErrors() bool {
return m.errors.ContainsErrors()
}

// HasFailedFixes reports whether --fix left a finding open with the reason in its FixError.
func (m *Manager) HasFailedFixes() bool {
return m.errors.ContainsFailedFixes()
}

// ApplyFixes is the single entry point for the --fix flag. It runs every fix
// attached to a collected finding. Findings whose fix succeeds are marked Fixed
// and subsequently dropped by GetErrors; findings whose fix fails are kept, and
Expand Down
2 changes: 1 addition & 1 deletion internal/metrics/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func Test_SetLinterWarningsMetrics_AddsWarningsForAllLinters(t *testing.T) {
Module: global.ModuleLinterConfig{},
NoCyrillic: global.LinterConfig{Impact: pkg.Warn.String()},
OpenAPI: global.OpenAPILinterConfig{LinterConfig: global.LinterConfig{Impact: pkg.Warn.String()}},
Rbac: global.LinterConfig{Impact: pkg.Warn.String()},
Rbac: global.RBACLinterConfig{LinterConfig: global.LinterConfig{Impact: pkg.Warn.String()}},
Templates: global.TemplatesLinterConfig{},
Documentation: global.DocumentationLinterConfig{},
},
Expand Down
28 changes: 28 additions & 0 deletions internal/modules/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,35 @@ func mapRuleSettings(linterSettings *pkg.LintersSettings, configSettings *config
// OpenAPI rules (uses global rule config + local fallback)
mapOpenAPIRules(linterSettings, configSettings, globalConfig)

// RBAC declaration rules (uses global rule config + local fallback); the four original rbac
// rules keep the linter level (see mapSimpleLinterRules)
mapRBACRules(linterSettings, configSettings, globalConfig)

// Other linter rules (use local linter-level impact)
mapSimpleLinterRules(linterSettings, configSettings)
}

// mapRBACRules configures the per-rule levels of the rbac rules added for the module RBAC
// declaration. As for every dmt linter, a per-rule level is read from the root configuration only
// and wins over the linter's impact; a rule the root leaves unset falls back to the linter's impact
// -- the module's, if it sets one -- but never above warn, the level these rules start at.
func mapRBACRules(linterSettings *pkg.LintersSettings, configSettings *config.LintersSettings, globalConfig *global.Linters) {
// The declaration rules 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. They therefore start at warn wherever nothing sets them -- like the style
// rules of the documentation linter -- and are raised to error per tree in its root
// .dmtlint.yaml once its modules are clean. The linter-level impact is intentionally not the
// fallback: impact: error on rbac means the four original rules, as it always did.
fallback := pkg.Warn.String()
if impact := configSettings.Rbac.Impact; impact != "" && pkg.ParseStringToLevel(impact) < pkg.Warn {
fallback = impact
}

linterSettings.RBAC.Rules.CoverageRule.SetLevel(globalConfig.Rbac.Rules.CoverageRule.Impact, fallback)
linterSettings.RBAC.Rules.SyncRule.SetLevel(globalConfig.Rbac.Rules.SyncRule.Impact, fallback)
linterSettings.RBAC.Rules.ContractRule.SetLevel(globalConfig.Rbac.Rules.ContractRule.Impact, fallback)
}

// mapContainerRules configures Container linter rules
func mapContainerRules(linterSettings *pkg.LintersSettings, configSettings *config.LintersSettings, globalConfig *global.Linters) {
linterSettings.Container.Rules.RecommendedLabelsRule.SetLevel(
Expand Down Expand Up @@ -582,6 +607,9 @@ func mapRBACExclusions(linterSettings *pkg.LintersSettings, configSettings *conf
excludes.BindingSubject = pkg.StringRuleExcludeList(configExcludes.BindingSubject)
excludes.Placement = configExcludes.Placement.Get()
excludes.Wildcards = configExcludes.Wildcards.Get()
excludes.Coverage = pkg.StringRuleExcludeList(configExcludes.Coverage)
excludes.Contract = configExcludes.Contract.Get()
excludes.Sync = configExcludes.Sync.Get()
}

// mapHooksSettings maps Hooks linter settings
Expand Down
85 changes: 85 additions & 0 deletions internal/modules/rbac_rules_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
Copyright 2026 Flant JSC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package modules

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/deckhouse/dmt/pkg"
"github.com/deckhouse/dmt/pkg/config"
"github.com/deckhouse/dmt/pkg/config/global"
)

// The rules added for the module RBAC declaration read their own impact from the root
// configuration; the four original rbac rules keep the linter level whatever the root says.
func TestRemapLinterSettings_RBACDeclarationRules(t *testing.T) {
t.Run("per-rule levels from the root configuration, warn as the fallback", func(t *testing.T) {
settings := remapLinterSettings(
&config.LintersSettings{Rbac: config.RbacSettings{Impact: pkg.Error.String()}},
&global.Linters{Rbac: global.RBACLinterConfig{
LinterConfig: global.LinterConfig{Impact: pkg.Error.String()},
Rules: global.RBACRules{
CoverageRule: global.RuleConfig{Impact: pkg.Warn.String()},
SyncRule: global.RuleConfig{Impact: pkg.Ignored.String()},
},
}},
)

require.Equal(t, pkg.Warn, *settings.RBAC.Rules.CoverageRule.GetLevel())
require.Equal(t, pkg.Ignored, *settings.RBAC.Rules.SyncRule.GetLevel())
require.Equal(t, pkg.Warn, *settings.RBAC.Rules.ContractRule.GetLevel(), "unset starts at warn, whatever the linter level says")

// SC5: the original rules are untouched by the per-rule block.
for _, rule := range []*pkg.RuleConfig{
&settings.RBAC.Rules.UserAuthRule, &settings.RBAC.Rules.BindingRule, &settings.RBAC.Rules.PlacementRule, &settings.RBAC.Rules.WildcardsRule,
} {
require.Equal(t, pkg.Error, *rule.GetLevel())
}
})

t.Run("the linter's impact is the fallback below warn, and the root's per-rule level wins", func(t *testing.T) {
settings := remapLinterSettings(
&config.LintersSettings{Rbac: config.RbacSettings{Impact: pkg.Ignored.String()}},
&global.Linters{Rbac: global.RBACLinterConfig{Rules: global.RBACRules{
SyncRule: global.RuleConfig{Impact: pkg.Error.String()},
}}},
)

require.Equal(t, pkg.Ignored, *settings.RBAC.Rules.CoverageRule.GetLevel(), "impact: ignored on the linter silences an unset rule")
require.Equal(t, pkg.Error, *settings.RBAC.Rules.SyncRule.GetLevel(), "a module cannot lower what the root sets")

settings = remapLinterSettings(&config.LintersSettings{Rbac: config.RbacSettings{Impact: pkg.Error.String()}}, &global.Linters{})
require.Equal(t, pkg.Warn, *settings.RBAC.Rules.ContractRule.GetLevel(), "error on the linter does not raise the unset rules above warn")
})

t.Run("module-level exclusions for the three rules", func(t *testing.T) {
settings := remapLinterSettings(
&config.LintersSettings{Rbac: config.RbacSettings{ExcludeRules: config.RBACExcludeRules{
Coverage: config.StringRuleExcludeList{"deckhouse.io/internals"},
Contract: config.KindRuleExcludeList{{Kind: "ClusterRole", Name: "d8:namespace-capability:x:view"}},
Sync: config.KindRuleExcludeList{{Kind: "ClusterRole", Name: "d8:user-authz:x:user"}},
}}},
&global.Linters{},
)

require.Equal(t, pkg.StringRuleExcludeList{"deckhouse.io/internals"}, settings.RBAC.ExcludeRules.Coverage)
require.Len(t, settings.RBAC.ExcludeRules.Contract.Get(), 1)
require.Len(t, settings.RBAC.ExcludeRules.Sync.Get(), 1)
})
}
1 change: 1 addition & 0 deletions internal/modules/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func RunRender(m *Module, vals chartutil.Values, objectStore *storage.Unstructur
Values: vals,
ExtraAPIVersions: render.ExtraAPIVersions(),
OnDrop: func(templatePath, cause string) {
objectStore.MarkDropped(templatePath, cause)
errorList.WithModule(m.GetName()).WithFilePath(templatePath).WithValue(cause).
Warnf("template %q failed to render and was skipped; the rest of the chart was still linted", templatePath)
},
Expand Down
15 changes: 14 additions & 1 deletion internal/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,22 @@ func (s *StoreObject) Identity() string {

type UnstructuredObjectStore struct {
Storage map[ResourceIndex]StoreObject
// Dropped holds the templates the tolerant render skipped, by path relative to the module, with
// the render error. Their objects are missing from Storage without being absent from the chart.
Dropped map[string]string
}

func NewUnstructuredObjectStore() *UnstructuredObjectStore {
return &UnstructuredObjectStore{Storage: make(map[ResourceIndex]StoreObject)}
return &UnstructuredObjectStore{Storage: make(map[ResourceIndex]StoreObject), Dropped: make(map[string]string)}
}

// MarkDropped records a template the render skipped.
func (s *UnstructuredObjectStore) MarkDropped(path, cause string) {
if s.Dropped == nil {
s.Dropped = make(map[string]string)
}

s.Dropped[path] = cause
}

func (s *UnstructuredObjectStore) Put(path, shortPath string, object map[string]any, raw []byte) error {
Expand Down Expand Up @@ -431,6 +443,7 @@ func (s *UnstructuredObjectStore) Close() {
// does not drop the backing map, which is what makes reuse cheap.
func (s *UnstructuredObjectStore) Reset() {
clear(s.Storage)
clear(s.Dropped)
}

func NewSHA256(data []byte) string {
Expand Down
6 changes: 6 additions & 0 deletions pkg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,18 @@ type RBACLinterRules struct {
BindingRule RuleConfig
PlacementRule RuleConfig
WildcardsRule RuleConfig
CoverageRule RuleConfig
SyncRule RuleConfig
ContractRule RuleConfig
}

type RBACExcludeRules struct {
BindingSubject StringRuleExcludeList
Placement KindRuleExcludeList
Wildcards KindRuleExcludeList
Coverage StringRuleExcludeList
Contract KindRuleExcludeList
Sync KindRuleExcludeList
}
type HooksLinterConfig struct {
LinterConfig
Expand Down
17 changes: 16 additions & 1 deletion pkg/config/global/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Linters struct {
Module ModuleLinterConfig `mapstructure:"module"`
NoCyrillic LinterConfig `mapstructure:"no-cyrillic"`
OpenAPI OpenAPILinterConfig `mapstructure:"openapi"`
Rbac LinterConfig `mapstructure:"rbac"`
Rbac RBACLinterConfig `mapstructure:"rbac"`
Templates TemplatesLinterConfig `mapstructure:"templates"`
Documentation DocumentationLinterConfig `mapstructure:"documentation"`
}
Expand Down Expand Up @@ -71,6 +71,21 @@ type ContainerRules struct {
SysCgroupMountRule RuleConfig `mapstructure:"sys-cgroup-mount"`
}

// RBACLinterConfig carries the linter-level impact of rbac and the per-rule impacts of the rules
// added for the module RBAC declaration. The four original rules (user-authz, binding-subject,
// placement, wildcards) have never had per-rule levels and keep the linter's: wiring them up
// would change the severity of existing findings.
type RBACLinterConfig struct {
LinterConfig `mapstructure:",squash"`
Rules RBACRules `mapstructure:"rules"`
}

type RBACRules struct {
CoverageRule RuleConfig `mapstructure:"coverage"`
SyncRule RuleConfig `mapstructure:"sync"`
ContractRule RuleConfig `mapstructure:"contract"`
}

type ImagesLinterConfig struct {
LinterConfig `mapstructure:",squash"`
Rules ImageRules `mapstructure:"rules"`
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/linters_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ type RBACExcludeRules struct {
BindingSubject StringRuleExcludeList `mapstructure:"binding-subject"`
Placement KindRuleExcludeList `mapstructure:"placement"`
Wildcards KindRuleExcludeList `mapstructure:"wildcards"`
// Coverage lists "group/resource" keys of CRDs the declaration deliberately leaves out.
Coverage StringRuleExcludeList `mapstructure:"coverage"`
Contract KindRuleExcludeList `mapstructure:"contract"`
Sync KindRuleExcludeList `mapstructure:"sync"`
}

type TemplatesSettings struct {
Expand Down
Loading
Loading