From 07b9329213f1e070ab5a3c30035ec3c1603769b9 Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Fri, 4 Sep 2026 19:53:35 +0300 Subject: [PATCH] fix rbac placement Signed-off-by: Pavel Okhlopkov --- ...er_check_read_only_root_filesystem_test.go | 365 ++++++++++++++++++ pkg/linters/rbac/rules/placement.go | 8 +- pkg/linters/rbac/rules/placement_test.go | 215 +++++++++++ .../rbac/root-placement/expected.yaml | 19 + .../rbac/root-placement/module/module.yaml | 2 + .../module/openapi/config-values.yaml | 2 + .../root-placement/module/openapi/values.yaml | 4 + .../module/templates/rbac-for-us.yaml | 5 + .../module/templates/rbac-to-us.yaml | 13 + .../templates/rbac/service-account.yaml | 5 + 10 files changed, 636 insertions(+), 2 deletions(-) create mode 100644 pkg/linters/container/rules/container_check_read_only_root_filesystem_test.go create mode 100644 pkg/linters/rbac/rules/placement_test.go create mode 100644 test/e2e/testdata/rbac/root-placement/expected.yaml create mode 100644 test/e2e/testdata/rbac/root-placement/module/module.yaml create mode 100644 test/e2e/testdata/rbac/root-placement/module/openapi/config-values.yaml create mode 100644 test/e2e/testdata/rbac/root-placement/module/openapi/values.yaml create mode 100644 test/e2e/testdata/rbac/root-placement/module/templates/rbac-for-us.yaml create mode 100644 test/e2e/testdata/rbac/root-placement/module/templates/rbac-to-us.yaml create mode 100644 test/e2e/testdata/rbac/root-placement/module/templates/rbac/service-account.yaml diff --git a/pkg/linters/container/rules/container_check_read_only_root_filesystem_test.go b/pkg/linters/container/rules/container_check_read_only_root_filesystem_test.go new file mode 100644 index 000000000..386781bf5 --- /dev/null +++ b/pkg/linters/container/rules/container_check_read_only_root_filesystem_test.go @@ -0,0 +1,365 @@ +/* +Copyright 2025 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 rules + +import ( + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/deckhouse/dmt/internal/storage" + "github.com/deckhouse/dmt/pkg" + "github.com/deckhouse/dmt/pkg/errors" +) + +// readOnlyObject builds the rendered object the rule inspects. Only kind and +// name matter to it: the containers arrive separately, already extracted. +func readOnlyObject(kind, name string) storage.StoreObject { + return storage.StoreObject{ + AbsPath: "test.yaml", + Unstructured: unstructured.Unstructured{ + Object: map[string]any{ + "kind": kind, + "metadata": map[string]any{"name": name}, + }, + }, + } +} + +func TestCheckReadOnlyRootFilesystemRule_ContainerReadOnlyRootFilesystem(t *testing.T) { + tests := []struct { + name string + kind string + containers []corev1.Container + expectedErrors []string + }{ + { + name: "unsupported kind should be ignored", + kind: "Service", + containers: []corev1.Container{{ + Name: "test", + }}, + expectedErrors: []string{}, + }, + { + name: "missing security context should error", + kind: "Deployment", + containers: []corev1.Container{{ + Name: "test-container", + }}, + expectedErrors: []string{ + "Container's SecurityContext is missing", + }, + }, + { + name: "missing readOnlyRootFilesystem should error", + kind: "Deployment", + containers: []corev1.Container{{ + Name: "test-container", + SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: boolPtr(true), + }, + }}, + expectedErrors: []string{ + "Container's SecurityContext missing parameter ReadOnlyRootFilesystem", + }, + }, + { + name: "readOnlyRootFilesystem false should error", + kind: "Deployment", + containers: []corev1.Container{{ + Name: "test-container", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: boolPtr(false), + }, + }}, + expectedErrors: []string{ + "Container's SecurityContext has `ReadOnlyRootFilesystem: false`, but it must be `true`", + }, + }, + { + name: "readOnlyRootFilesystem true should pass", + kind: "Deployment", + containers: []corev1.Container{{ + Name: "test-container", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: boolPtr(true), + }, + }}, + expectedErrors: []string{}, + }, + { + name: "multiple containers with mixed settings", + kind: "Pod", + containers: []corev1.Container{ + { + Name: "good-container", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: boolPtr(true), + }, + }, + { + Name: "bad-container", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: boolPtr(false), + }, + }, + { + Name: "missing-parameter", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: nil, + }, + }, + { + Name: "missing-context", + }, + }, + expectedErrors: []string{ + "Container's SecurityContext has `ReadOnlyRootFilesystem: false`, but it must be `true`", + "Container's SecurityContext missing parameter ReadOnlyRootFilesystem", + "Container's SecurityContext is missing", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errorList := errors.NewLintRuleErrorsList() + + obj := readOnlyObject(tt.kind, "test-obj") + + NewCheckReadOnlyRootFilesystemRule([]pkg.ContainerRuleExclude{}, oneObject(obj, tt.containers), errorList).Check(t.Context()) + errs := errorList.GetErrors() + + if len(tt.expectedErrors) == 0 { + assert.Empty(t, errs, "Expected no errors") + } else { + assert.Len(t, errs, len(tt.expectedErrors), "Expected %d errors", len(tt.expectedErrors)) + + for i, expectedError := range tt.expectedErrors { + assert.Contains(t, errs[i].Text, expectedError, "Error %d should contain expected text", i) + } + } + }) + } +} + +// TestCheckReadOnlyRootFilesystemRule_Kinds pins the kind gate: the six workload +// kinds the rule claims are checked, and anything else — ReplicaSet included, +// even though container extraction supports it — is passed over. +func TestCheckReadOnlyRootFilesystemRule_Kinds(t *testing.T) { + checked := []string{"Deployment", "DaemonSet", "StatefulSet", "Pod", "Job", "CronJob"} + skipped := []string{"ReplicaSet", "Service", "ConfigMap", "CustomResourceDefinition", ""} + + // A container that fails every stage of the check, so the only reason for + // silence can be the kind gate. + failing := []corev1.Container{{ + Name: "test-container", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: boolPtr(false), + }, + }} + + for _, kind := range checked { + t.Run("checked/"+kind, func(t *testing.T) { + errorList := errors.NewLintRuleErrorsList() + + NewCheckReadOnlyRootFilesystemRule( + []pkg.ContainerRuleExclude{}, + oneObject(readOnlyObject(kind, "test-obj"), failing), + errorList, + ).Check(t.Context()) + + assert.Len(t, errorList.GetErrors(), 1, "%s must be checked", kind) + }) + } + + for _, kind := range skipped { + t.Run("skipped/"+kind, func(t *testing.T) { + errorList := errors.NewLintRuleErrorsList() + + NewCheckReadOnlyRootFilesystemRule( + []pkg.ContainerRuleExclude{}, + oneObject(readOnlyObject(kind, "test-obj"), failing), + errorList, + ).Check(t.Context()) + + assert.Empty(t, errorList.GetErrors(), "%q must not be checked", kind) + }) + } +} + +// TestCheckReadOnlyRootFilesystemRule_InitContainers guards that init containers +// are held to the same requirement: the rule reads the All slice, which carries +// regular and init containers together. +func TestCheckReadOnlyRootFilesystemRule_InitContainers(t *testing.T) { + regular := corev1.Container{ + Name: "app", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: boolPtr(true), + }, + } + init := corev1.Container{ + Name: "init-app", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: boolPtr(false), + }, + } + + errorList := errors.NewLintRuleErrorsList() + + objects := []ObjectContainers{{ + Object: readOnlyObject("Deployment", "test-obj"), + All: []corev1.Container{regular, init}, + NotInit: []corev1.Container{regular}, + }} + + NewCheckReadOnlyRootFilesystemRule([]pkg.ContainerRuleExclude{}, objects, errorList).Check(t.Context()) + errs := errorList.GetErrors() + + assert.Len(t, errs, 1, "The failing init container must be reported") + assert.Contains(t, errs[0].Text, "Container's SecurityContext has `ReadOnlyRootFilesystem: false`, but it must be `true`") + assert.Contains(t, errs[0].ObjectID, "container = init-app") +} + +// TestCheckReadOnlyRootFilesystemRule_SkippedObjectDoesNotStopRule is the +// regression guard for the early returns in checkObject: they must end the +// check for one object, not for the whole rule. An unsupported kind and an +// object with no containers both come first, so an inlined early return would +// hide the failing Deployment behind them. +func TestCheckReadOnlyRootFilesystemRule_SkippedObjectDoesNotStopRule(t *testing.T) { + failing := []corev1.Container{{ + Name: "test-container", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: boolPtr(false), + }, + }} + + errorList := errors.NewLintRuleErrorsList() + + objects := []ObjectContainers{ + {Object: readOnlyObject("Service", "some-service"), All: failing, NotInit: failing}, + {Object: readOnlyObject("Deployment", "empty-deployment")}, + {Object: readOnlyObject("Deployment", "failing-deployment"), All: failing, NotInit: failing}, + } + + NewCheckReadOnlyRootFilesystemRule([]pkg.ContainerRuleExclude{}, objects, errorList).Check(t.Context()) + errs := errorList.GetErrors() + + assert.Len(t, errs, 1, "The failing Deployment must still be reported") + assert.Contains(t, errs[0].ObjectID, "failing-deployment") +} + +func TestCheckReadOnlyRootFilesystemRule_WithExclusions(t *testing.T) { + failing := corev1.Container{ + Name: "excluded-container", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: boolPtr(false), // This would normally fail + }, + } + + tests := []struct { + name string + excludeRules []pkg.ContainerRuleExclude + kind string + objectName string + containers []corev1.Container + expectErrors int + }{ + { + name: "matching kind, name and container is excluded", + excludeRules: []pkg.ContainerRuleExclude{{ + Kind: "Deployment", + Name: "excluded-deployment", + Container: "excluded-container", + }}, + kind: "Deployment", + objectName: "excluded-deployment", + containers: []corev1.Container{failing}, + expectErrors: 0, + }, + { + name: "empty container field excludes every container of the object", + excludeRules: []pkg.ContainerRuleExclude{{ + Kind: "Deployment", + Name: "excluded-deployment", + }}, + kind: "Deployment", + objectName: "excluded-deployment", + containers: []corev1.Container{ + failing, + { + Name: "another-container", + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: boolPtr(false), + }, + }, + }, + expectErrors: 0, + }, + { + name: "exclusion for another container still reports this one", + excludeRules: []pkg.ContainerRuleExclude{{ + Kind: "Deployment", + Name: "excluded-deployment", + Container: "some-other-container", + }}, + kind: "Deployment", + objectName: "excluded-deployment", + containers: []corev1.Container{failing}, + expectErrors: 1, + }, + { + name: "exclusion for another object name still reports", + excludeRules: []pkg.ContainerRuleExclude{{ + Kind: "Deployment", + Name: "some-other-deployment", + Container: "excluded-container", + }}, + kind: "Deployment", + objectName: "excluded-deployment", + containers: []corev1.Container{failing}, + expectErrors: 1, + }, + { + name: "exclusion for another kind still reports", + excludeRules: []pkg.ContainerRuleExclude{{ + Kind: "DaemonSet", + Name: "excluded-deployment", + Container: "excluded-container", + }}, + kind: "Deployment", + objectName: "excluded-deployment", + containers: []corev1.Container{failing}, + expectErrors: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errorList := errors.NewLintRuleErrorsList() + + obj := readOnlyObject(tt.kind, tt.objectName) + + NewCheckReadOnlyRootFilesystemRule(tt.excludeRules, oneObject(obj, tt.containers), errorList).Check(t.Context()) + + assert.Len(t, errorList.GetErrors(), tt.expectErrors) + }) + } +} diff --git a/pkg/linters/rbac/rules/placement.go b/pkg/linters/rbac/rules/placement.go index fa2a928d1..2e13f91b5 100644 --- a/pkg/linters/rbac/rules/placement.go +++ b/pkg/linters/rbac/rules/placement.go @@ -63,7 +63,7 @@ const ( UserAuthzClusterRolePath = "templates/user-authz-cluster-roles.yaml" RootRBACForUsPath = "templates/rbac-for-us.yaml" RootRBACToUsPath = "templates/rbac-to-us.yaml" - RBACv2Path = "templates/rbac" + RBACv2Path = "templates/rbac" // a directory, not a file ) // TODO: remove entries after 'd8-system' after fixing RBAC objects names @@ -88,8 +88,12 @@ func (r *PlacementRule) Check(_ context.Context) { continue } + // RBACv2Path names the RBAC v2 *directory*, so the skip must match a whole + // path segment. A bare prefix match on "templates/rbac" also swallows the + // root "templates/rbac-for-us.yaml" and "templates/rbac-to-us.yaml" — the + // very files this rule exists to check. shortPath := object.ShortPath() - if shortPath == UserAuthzClusterRolePath || strings.HasPrefix(shortPath, RBACv2Path) { + if shortPath == UserAuthzClusterRolePath || strings.HasPrefix(shortPath, RBACv2Path+"/") { continue } diff --git a/pkg/linters/rbac/rules/placement_test.go b/pkg/linters/rbac/rules/placement_test.go new file mode 100644 index 000000000..017d6bcd8 --- /dev/null +++ b/pkg/linters/rbac/rules/placement_test.go @@ -0,0 +1,215 @@ +/* +Copyright 2025 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 rules + +import ( + "testing" + + "github.com/gojuno/minimock/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/deckhouse/dmt/internal/mocks" + "github.com/deckhouse/dmt/internal/storage" + "github.com/deckhouse/dmt/pkg" + "github.com/deckhouse/dmt/pkg/errors" +) + +const ( + testModuleName = "security-events-manager" + testModuleNamespace = "d8-security-events-manager" +) + +// placementObject describes one rendered manifest as the object store sees it: +// a chart-relative template path plus the object's identity. +type placementObject struct { + shortPath string + kind string + name string + namespace string +} + +func placementStorage(t *testing.T, objects ...placementObject) map[storage.ResourceIndex]storage.StoreObject { + t.Helper() + + store := storage.NewUnstructuredObjectStore() + + for _, o := range objects { + metadata := map[string]any{"name": o.name} + if o.namespace != "" { + metadata["namespace"] = o.namespace + } + + content := map[string]any{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": o.kind, + "metadata": metadata, + } + + require.NoError(t, store.Put("/module/"+o.shortPath, o.shortPath, content, []byte(o.shortPath+o.name))) + } + + return store.Storage +} + +func runPlacementRule(t *testing.T, objects ...placementObject) []pkg.LinterError { + t.Helper() + + mc := minimock.NewController(t) + + mod := mocks.NewModuleMock(mc) + mod.GetStorageMock.Return(placementStorage(t, objects...)) + mod.GetNameMock.Optional().Return(testModuleName) + mod.GetNamespaceMock.Optional().Return(testModuleNamespace) + + errorList := errors.NewLintRuleErrorsList() + NewPlacementRule(nil, mod, errorList).Check(t.Context()) + + return errorList.GetErrors() +} + +// Regression: RBACv2Path ("templates/rbac") used to be matched with a bare +// strings.HasPrefix, which also swallowed the root "templates/rbac-for-us.yaml" +// and "templates/rbac-to-us.yaml" — so a module keeping all of its RBAC at the +// root was never checked at all. +func TestPlacementRule_RootRBACFilesAreChecked(t *testing.T) { + tests := []struct { + name string + object placementObject + wantMsg string + }{ + { + name: "ServiceAccount in root rbac-for-us.yaml", + object: placementObject{ + shortPath: RootRBACForUsPath, + kind: "ServiceAccount", + name: "wrong-name", + namespace: testModuleNamespace, + }, + wantMsg: `Name of ServiceAccount in "templates/rbac-for-us.yaml" should be equal to Chart Name (security-events-manager)`, + }, + { + name: "ClusterRole in root rbac-for-us.yaml", + object: placementObject{ + shortPath: RootRBACForUsPath, + kind: "ClusterRole", + name: "wrong-name", + }, + wantMsg: `Name of ClusterRole in "templates/rbac-for-us.yaml" should start with "d8:security-events-manager"`, + }, + { + name: "RoleBinding in root rbac-to-us.yaml", + object: placementObject{ + shortPath: RootRBACToUsPath, + kind: "RoleBinding", + name: "wrong-name", + namespace: testModuleNamespace, + }, + wantMsg: `RoleBinding in "templates/rbac-to-us.yaml" should start with "access-to-security-events-manager"`, + }, + { + name: "unexpected kind in root rbac-for-us.yaml", + object: placementObject{ + shortPath: RootRBACForUsPath, + kind: "ConfigMap", + name: "some-config", + namespace: testModuleNamespace, + }, + wantMsg: "kind ConfigMap not allowed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + lintErrors := runPlacementRule(t, tt.object) + + require.Len(t, lintErrors, 1) + assert.Equal(t, tt.wantMsg, lintErrors[0].Text) + assert.Equal(t, tt.object.shortPath, lintErrors[0].FilePath) + }) + } +} + +func TestPlacementRule_RootRBACFilesAccepted(t *testing.T) { + lintErrors := runPlacementRule(t, + placementObject{ + shortPath: RootRBACForUsPath, + kind: "ServiceAccount", + name: testModuleName, + namespace: testModuleNamespace, + }, + placementObject{ + shortPath: RootRBACForUsPath, + kind: "ClusterRole", + name: "d8:" + testModuleName + ":rbac-proxy", + }, + placementObject{ + shortPath: RootRBACToUsPath, + kind: "RoleBinding", + name: "access-to-" + testModuleName, + namespace: testModuleNamespace, + }, + ) + + assert.Empty(t, lintErrors) +} + +// Objects under the RBAC v2 directory are validated elsewhere and must stay skipped. +func TestPlacementRule_RBACv2DirectorySkipped(t *testing.T) { + lintErrors := runPlacementRule(t, + placementObject{ + shortPath: RBACv2Path + "/module.yaml", + kind: "ServiceAccount", + name: "totally-wrong", + namespace: "kube-system", + }, + placementObject{ + shortPath: RBACv2Path + "/nested/roles.yaml", + kind: "ClusterRole", + name: "totally-wrong-cluster-role", + }, + placementObject{ + shortPath: UserAuthzClusterRolePath, + kind: "ClusterRole", + name: "totally-wrong-user-authz-role", + }, + ) + + assert.Empty(t, lintErrors) +} + +func TestPlacementRule_NestedRBACFiles(t *testing.T) { + lintErrors := runPlacementRule(t, + placementObject{ + shortPath: "templates/collector/rbac-for-us.yaml", + kind: "ServiceAccount", + name: "collector", + namespace: testModuleNamespace, + }, + placementObject{ + shortPath: "templates/parser/rbac-for-us.yaml", + kind: "ServiceAccount", + name: "wrong-name", + namespace: testModuleNamespace, + }, + ) + + require.Len(t, lintErrors, 1) + assert.Equal(t, + `Name of ServiceAccount should be equal to "parser" or "security-events-manager-parser"`, + lintErrors[0].Text) +} diff --git a/test/e2e/testdata/rbac/root-placement/expected.yaml b/test/e2e/testdata/rbac/root-placement/expected.yaml new file mode 100644 index 000000000..46bb529c5 --- /dev/null +++ b/test/e2e/testdata/rbac/root-placement/expected.yaml @@ -0,0 +1,19 @@ +description: > + RBAC kept at the module root must be checked by the placement rule: + "templates/rbac-for-us.yaml" and "templates/rbac-to-us.yaml" share a prefix + with the RBAC v2 directory "templates/rbac", and used to be skipped along + with it. Objects under "templates/rbac/" itself must still be skipped. +module: module +expect: + - linter: rbac + rule: placement + level: error + textContains: 'Name of ServiceAccount in "templates/rbac-for-us.yaml" should be equal to Chart Name (e2e-rbac-root)' + - linter: rbac + rule: placement + level: error + textContains: 'RoleBinding in "templates/rbac-to-us.yaml" should start with "access-to-e2e-rbac-root"' +expectAbsent: + - linter: rbac + rule: placement + textContains: 'ServiceAccount should be in "templates/rbac-for-us.yaml"' diff --git a/test/e2e/testdata/rbac/root-placement/module/module.yaml b/test/e2e/testdata/rbac/root-placement/module/module.yaml new file mode 100644 index 000000000..200f65474 --- /dev/null +++ b/test/e2e/testdata/rbac/root-placement/module/module.yaml @@ -0,0 +1,2 @@ +name: e2e-rbac-root +namespace: d8-e2e-rbac-root diff --git a/test/e2e/testdata/rbac/root-placement/module/openapi/config-values.yaml b/test/e2e/testdata/rbac/root-placement/module/openapi/config-values.yaml new file mode 100644 index 000000000..03b0d8bfe --- /dev/null +++ b/test/e2e/testdata/rbac/root-placement/module/openapi/config-values.yaml @@ -0,0 +1,2 @@ +type: object +properties: {} diff --git a/test/e2e/testdata/rbac/root-placement/module/openapi/values.yaml b/test/e2e/testdata/rbac/root-placement/module/openapi/values.yaml new file mode 100644 index 000000000..47180da56 --- /dev/null +++ b/test/e2e/testdata/rbac/root-placement/module/openapi/values.yaml @@ -0,0 +1,4 @@ +x-extend: + schema: config-values.yaml +type: object +properties: {} diff --git a/test/e2e/testdata/rbac/root-placement/module/templates/rbac-for-us.yaml b/test/e2e/testdata/rbac/root-placement/module/templates/rbac-for-us.yaml new file mode 100644 index 000000000..95dadfbd9 --- /dev/null +++ b/test/e2e/testdata/rbac/root-placement/module/templates/rbac-for-us.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: wrong-service-account-name + namespace: d8-e2e-rbac-root diff --git a/test/e2e/testdata/rbac/root-placement/module/templates/rbac-to-us.yaml b/test/e2e/testdata/rbac/root-placement/module/templates/rbac-to-us.yaml new file mode 100644 index 000000000..767293b89 --- /dev/null +++ b/test/e2e/testdata/rbac/root-placement/module/templates/rbac-to-us.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: wrong-role-binding-name + namespace: d8-e2e-rbac-root +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: access-to-e2e-rbac-root +subjects: + - kind: ServiceAccount + name: wrong-service-account-name + namespace: d8-e2e-rbac-root diff --git a/test/e2e/testdata/rbac/root-placement/module/templates/rbac/service-account.yaml b/test/e2e/testdata/rbac/root-placement/module/templates/rbac/service-account.yaml new file mode 100644 index 000000000..ef2bf5877 --- /dev/null +++ b/test/e2e/testdata/rbac/root-placement/module/templates/rbac/service-account.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: rbac-v2-service-account + namespace: d8-e2e-rbac-root