Skip to content

Commit 0d25e88

Browse files
authored
Set namespace rolebinding behavior (#680)
* feat: Update RoleBinding subject namespaces This commit adds support to update the namespace for ServiceAccount resources which are referenced in the subjects field of RoleBinding and ClusterRoleBinding resources. Given a ServiceAccount with name sa-name is included in the ResourceList, any RoleBinding or ClusterRoleBinding subject with kind ServiceAccount and name sa-name will also have its namespace set. For the time being this functionality is being implemented in kpt rather than the upstream kustomize library. If this behavior is well received, it may be a future consideration to push this functionality into kustomize. * test: Add e2e test for set-namespace RoleBindings This test covers the behavior for how set-namespace interacts with the RoleBinding and ClusterRoleBinding resource types. * test: e2e test set-namespace/ensure-name-substring This test case covers the interaction between the ensure-name-substring and set-namespace functions, particularly when interacting with RoleBinding/ClusterRoleBinding resource types.
1 parent e8a5b7a commit 0d25e88

File tree

11 files changed

+283
-6
lines changed

11 files changed

+283
-6
lines changed

functions/go/set-namespace/README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,35 @@ target the namespace. There are a few cases that worth pointing out:
3737

3838
- If there is a `Namespace` resource, its `metadata.name` field will be updated.
3939
- If there's a `RoleBinding` or `ClusterRoleBinding` resource, the function will
40-
update the namespace in the `ServiceAccount` if and only if the subject
41-
element `name` is `default`. In the following example, the `set-namespace`
42-
function will update `subjects.namespace` since the
43-
corresponding `subjects.name` is `default`.
40+
update the namespace in the `ServiceAccount` if one of the following are true:
41+
1) the subject element `name` is `default`.
42+
2) the subject element `name` matches the name of a `ServiceAccount` resource declared in the package.
43+
44+
In the following example, the `set-namespace` function will update:
45+
- `subjects[0].namespace` since `subjects[0].name` is `default`.
46+
- `subjects[1].namespace` since `subjects[1].name` matches a `ServiceAccount`
47+
name declared in the package.
4448

4549
```yaml
50+
apiVersion: v1
51+
kind: ServiceAccount
52+
metadata:
53+
name: service-account
54+
namespace: original-namespace
55+
---
4656
kind: RoleBinding
4757
apiVersion: rbac.authorization.k8s.io/v1
4858
metadata:
4959
...
5060
subjects:
5161
- kind: ServiceAccount
52-
name: default # <======== using name default here
62+
name: default # <================== name default is used
63+
namespace: original-namespace # <== this will be updated
64+
- kind: ServiceAccount
65+
name: service-account # <========== name matches above ServiceAccount
66+
namespace: original-namespace # <== this will be updated
67+
- kind: ServiceAccount
68+
name: other-service-account # <==== this will NOT be updated
5369
namespace: original-namespace
5470
roleRef:
5571
kind: Role

functions/go/set-namespace/namespace_transformer.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,20 @@ import (
99

1010
"sigs.k8s.io/kustomize/api/filters/namespace"
1111
"sigs.k8s.io/kustomize/api/resmap"
12+
"sigs.k8s.io/kustomize/api/resource"
1213
"sigs.k8s.io/kustomize/api/types"
1314
"sigs.k8s.io/kustomize/kyaml/filtersutil"
15+
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
1416
"sigs.k8s.io/yaml"
1517
)
1618

19+
const (
20+
subjectsField = "subjects"
21+
serviceAccountKind = "ServiceAccount"
22+
roleBindingKind = "RoleBinding"
23+
clusterRoleBindingKind = "ClusterRoleBinding"
24+
)
25+
1726
// Change or set the namespace of non-cluster level resources.
1827
type plugin struct {
1928
// Desired namespace.
@@ -48,6 +57,8 @@ func (p *plugin) Transform(m resmap.ResMap) error {
4857
if len(p.Namespace) == 0 {
4958
return nil
5059
}
60+
sal := serviceAccountLookup{}
61+
sal.FromResMap(m)
5162
for _, r := range m.Resources() {
5263
if r.IsNilOrEmpty() {
5364
// Don't mutate empty objects?
@@ -60,6 +71,67 @@ func (p *plugin) Transform(m resmap.ResMap) error {
6071
if err != nil {
6172
return err
6273
}
74+
if err = p.roleBindingHack(r, sal); err != nil {
75+
return err
76+
}
6377
}
6478
return nil
6579
}
80+
81+
// roleBindingHack extends the behavior of the upstream kustomize filter
82+
// This extension adds the following behavior:
83+
// Given a ServiceAccount is present in the ResourceList, additionally set the
84+
// namespace for that ServiceAccount where it is referenced in the "subjects"
85+
// field of a RoleBinding or ClusterRoleBinding
86+
func (p *plugin) roleBindingHack(r *resource.Resource, sal serviceAccountLookup) error {
87+
kind := r.GetKind()
88+
if kind != roleBindingKind && kind != clusterRoleBindingKind {
89+
return nil
90+
}
91+
subjects, err := r.Pipe(kyaml.Lookup(subjectsField))
92+
if err != nil || kyaml.IsMissingOrNull(subjects) {
93+
return err
94+
}
95+
96+
err = subjects.VisitElements(func(o *kyaml.RNode) error {
97+
subjectKind := o.GetKind()
98+
if subjectKind != serviceAccountKind {
99+
return nil
100+
}
101+
var nameRN *kyaml.RNode
102+
nameRN, err = o.Pipe(kyaml.Lookup("name"))
103+
if err != nil || kyaml.IsMissingOrNull(nameRN) {
104+
return err
105+
}
106+
nameStr := kyaml.GetValue(nameRN)
107+
if !sal.HasServiceAccount(nameStr) {
108+
return nil
109+
}
110+
v := kyaml.NewScalarRNode(p.Namespace)
111+
return o.PipeE(
112+
kyaml.LookupCreate(kyaml.ScalarNode, "namespace"),
113+
kyaml.FieldSetter{Value: v},
114+
)
115+
})
116+
return err
117+
}
118+
119+
// ServiceAccountLookup provides an API for tracking ServiceAccount resources
120+
type serviceAccountLookup struct {
121+
serviceAccountMap map[string]bool
122+
}
123+
124+
// FromResMap reads through a ResMap to populate ServiceAccountLookup
125+
func (sal *serviceAccountLookup) FromResMap(m resmap.ResMap) {
126+
sal.serviceAccountMap = make(map[string]bool)
127+
for _, r := range m.Resources() {
128+
if r.GetKind() == serviceAccountKind {
129+
sal.serviceAccountMap[r.GetName()] = true
130+
}
131+
}
132+
}
133+
134+
// HasServiceAccount returns whether a ServiceAccount with the provided name is present
135+
func (sal *serviceAccountLookup) HasServiceAccount(name string) bool {
136+
return sal.serviceAccountMap[name]
137+
}

functions/go/set-namespace/namespace_transformer_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ subjects:
162162
namespace: test
163163
- kind: ServiceAccount
164164
name: service-account
165-
namespace: system
165+
namespace: test
166166
- kind: ServiceAccount
167167
name: another
168168
namespace: random
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
diff --git a/resources.yaml b/resources.yaml
2+
index 0315d44..3a16469 100644
3+
--- a/resources.yaml
4+
+++ b/resources.yaml
5+
@@ -1,21 +1,21 @@
6+
apiVersion: v1
7+
kind: ServiceAccount
8+
metadata:
9+
- name: service-account
10+
- namespace: system
11+
+ name: dev-service-account
12+
+ namespace: example-ns
13+
---
14+
apiVersion: rbac.authorization.k8s.io/v1
15+
kind: RoleBinding
16+
metadata:
17+
- name: manager-rolebinding
18+
- namespace: system
19+
+ name: dev-manager-rolebinding
20+
+ namespace: example-ns
21+
subjects:
22+
- kind: ServiceAccount
23+
name: default
24+
- namespace: system
25+
+ namespace: example-ns
26+
- kind: ServiceAccount
27+
- name: service-account
28+
- namespace: system
29+
+ name: dev-service-account
30+
+ namespace: example-ns
31+
- kind: ServiceAccount
32+
name: another
33+
namespace: random
34+
@@ -23,14 +23,14 @@ subjects:
35+
apiVersion: rbac.authorization.k8s.io/v1
36+
kind: ClusterRoleBinding
37+
metadata:
38+
- name: manager-cluster-rolebinding
39+
+ name: dev-manager-cluster-rolebinding
40+
subjects:
41+
- kind: ServiceAccount
42+
name: default
43+
- namespace: system
44+
+ namespace: example-ns
45+
- kind: ServiceAccount
46+
- name: service-account
47+
- namespace: system
48+
+ name: dev-service-account
49+
+ namespace: example-ns
50+
- kind: ServiceAccount
51+
name: another
52+
namespace: random
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.expected
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: kpt.dev/v1
2+
kind: Kptfile
3+
metadata:
4+
name: example
5+
pipeline:
6+
mutators:
7+
- image: gcr.io/kpt-fn/ensure-name-substring:unstable
8+
configMap:
9+
prepend: dev-
10+
- image: gcr.io/kpt-fn/set-namespace:unstable
11+
configMap:
12+
namespace: example-ns
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
apiVersion: v1
2+
kind: ServiceAccount
3+
metadata:
4+
name: service-account
5+
namespace: system
6+
---
7+
apiVersion: rbac.authorization.k8s.io/v1
8+
kind: RoleBinding
9+
metadata:
10+
name: manager-rolebinding
11+
namespace: system
12+
subjects:
13+
- kind: ServiceAccount
14+
name: default
15+
namespace: system
16+
- kind: ServiceAccount
17+
name: service-account
18+
namespace: system
19+
- kind: ServiceAccount
20+
name: another
21+
namespace: random
22+
---
23+
apiVersion: rbac.authorization.k8s.io/v1
24+
kind: ClusterRoleBinding
25+
metadata:
26+
name: manager-cluster-rolebinding
27+
subjects:
28+
- kind: ServiceAccount
29+
name: default
30+
namespace: system
31+
- kind: ServiceAccount
32+
name: service-account
33+
namespace: system
34+
- kind: ServiceAccount
35+
name: another
36+
namespace: random
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
diff --git a/resources.yaml b/resources.yaml
2+
index 0315d44..78167d3 100644
3+
--- a/resources.yaml
4+
+++ b/resources.yaml
5+
@@ -2,20 +2,20 @@ apiVersion: v1
6+
kind: ServiceAccount
7+
metadata:
8+
name: service-account
9+
- namespace: system
10+
+ namespace: example-ns
11+
---
12+
apiVersion: rbac.authorization.k8s.io/v1
13+
kind: RoleBinding
14+
metadata:
15+
name: manager-rolebinding
16+
- namespace: system
17+
+ namespace: example-ns
18+
subjects:
19+
- kind: ServiceAccount
20+
name: default
21+
- namespace: system
22+
+ namespace: example-ns
23+
- kind: ServiceAccount
24+
name: service-account
25+
- namespace: system
26+
+ namespace: example-ns
27+
- kind: ServiceAccount
28+
name: another
29+
namespace: random
30+
@@ -27,10 +27,10 @@ metadata:
31+
subjects:
32+
- kind: ServiceAccount
33+
name: default
34+
- namespace: system
35+
+ namespace: example-ns
36+
- kind: ServiceAccount
37+
name: service-account
38+
- namespace: system
39+
+ namespace: example-ns
40+
- kind: ServiceAccount
41+
name: another
42+
namespace: random
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.expected
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: kpt.dev/v1
2+
kind: Kptfile
3+
metadata:
4+
name: example
5+
pipeline:
6+
mutators:
7+
- image: gcr.io/kpt-fn/set-namespace:unstable
8+
configMap:
9+
namespace: example-ns

0 commit comments

Comments
 (0)