Skip to content

Commit dbd8dd4

Browse files
committed
Avoid reordering Rules
Currently, the RBAC generator may change the order of Rules defined by the "kubebuilder:rbac" markers, which may break the RBAC and should be avoided. This commit stops reordering Rules for the correctness of RBAC. Two notes: 1) each field of a given Rule will still be ordered for the purpose of deduplicating Rules; 2) all the Rules having the same ruleKey will be merged into the first Rule Signed-off-by: Haiyan Meng <[email protected]>
1 parent c4d99a1 commit dbd8dd4

File tree

6 files changed

+186
-10
lines changed

6 files changed

+186
-10
lines changed

pkg/rbac/parser.go

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,45 +121,64 @@ func (Generator) RegisterMarkers(into *markers.Registry) error {
121121
return into.Register(RuleDefinition)
122122
}
123123

124-
func (g Generator) Generate(ctx *genall.GenerationContext) error {
125-
rules := make(map[ruleKey]*Rule)
124+
// GenerateClusterRole generates a rbacv1.ClusterRole object
125+
func GenerateClusterRole(ctx *genall.GenerationContext, roleName string) (*rbacv1.ClusterRole, error) {
126+
// Using a slice here to preserve the order of Rules
127+
var ruleSlice []Rule
128+
// ruleKeyToIndexMap maps a ruleKey to its index in ruleSlice
129+
ruleKeyToIndexMap := make(map[ruleKey]int)
126130
for _, root := range ctx.Roots {
127131
markerSet, err := markers.PackageMarkers(ctx.Collector, root)
128132
if err != nil {
129133
root.AddError(err)
130134
}
131135

136+
// all the Rules having the same ruleKey will be merged into the first Rule
132137
for _, markerValue := range markerSet[RuleDefinition.Name] {
133138
rule := markerValue.(Rule)
134139
key := rule.key()
135-
if _, ok := rules[key]; !ok {
136-
rules[key] = &rule
140+
if _, ok := ruleKeyToIndexMap[key]; !ok {
141+
ruleKeyToIndexMap[key] = len(ruleSlice)
142+
ruleSlice = append(ruleSlice, rule)
137143
continue
138144
}
139-
rules[key].addVerbs(rule.Verbs)
145+
ruleSlice[ruleKeyToIndexMap[key]].addVerbs(rule.Verbs)
140146
}
141147
}
142148

143149
var policyRules []rbacv1.PolicyRule
144-
for _, rule := range rules {
150+
for _, rule := range ruleSlice {
145151
policyRules = append(policyRules, rule.ToRule())
146152

147153
}
148154

149155
if len(policyRules) == 0 {
150-
return nil
156+
return nil, nil
151157
}
152158

153-
if err := ctx.WriteYAML("role.yaml", rbacv1.ClusterRole{
159+
return &rbacv1.ClusterRole{
154160
TypeMeta: metav1.TypeMeta{
155161
Kind: "ClusterRole",
156162
APIVersion: rbacv1.SchemeGroupVersion.String(),
157163
},
158164
ObjectMeta: metav1.ObjectMeta{
159-
Name: g.RoleName,
165+
Name: roleName,
160166
},
161167
Rules: policyRules,
162-
}); err != nil {
168+
}, nil
169+
}
170+
171+
func (g Generator) Generate(ctx *genall.GenerationContext) error {
172+
clusterRole, err := GenerateClusterRole(ctx, g.RoleName)
173+
if err != nil {
174+
return err
175+
}
176+
177+
if clusterRole == nil {
178+
return nil
179+
}
180+
181+
if err := ctx.WriteYAML("role.yaml", *clusterRole); err != nil {
163182
return err
164183
}
165184

pkg/rbac/parser_integration_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package rbac_test
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
7+
"github.com/google/go-cmp/cmp"
8+
. "github.com/onsi/ginkgo"
9+
. "github.com/onsi/gomega"
10+
"sigs.k8s.io/yaml"
11+
12+
rbacv1 "k8s.io/api/rbac/v1"
13+
14+
"sigs.k8s.io/controller-tools/pkg/genall"
15+
"sigs.k8s.io/controller-tools/pkg/loader"
16+
"sigs.k8s.io/controller-tools/pkg/markers"
17+
"sigs.k8s.io/controller-tools/pkg/rbac"
18+
)
19+
20+
var _ = Describe("ClusterRole generated by the RBAC Generator", func() {
21+
It("should match the expected result", func() {
22+
By("switching into testdata to appease go modules")
23+
cwd, err := os.Getwd()
24+
Expect(err).NotTo(HaveOccurred())
25+
Expect(os.Chdir("./testdata")).To(Succeed()) // go modules are directory-sensitive
26+
defer func() { Expect(os.Chdir(cwd)).To(Succeed()) }()
27+
28+
By("loading the roots")
29+
pkgs, err := loader.LoadRoots(".")
30+
Expect(err).NotTo(HaveOccurred())
31+
32+
By("registering RBAC rule marker")
33+
reg := &markers.Registry{}
34+
Expect(reg.Register(rbac.RuleDefinition)).To(Succeed())
35+
36+
By("creating GenerationContext")
37+
ctx := &genall.GenerationContext{
38+
Collector: &markers.Collector{Registry: reg},
39+
Roots: pkgs,
40+
}
41+
42+
By("generating a ClusterRole")
43+
clusterRole, err := rbac.GenerateClusterRole(ctx, "manager-role")
44+
Expect(err).NotTo(HaveOccurred())
45+
46+
By("loading the desired YAML")
47+
expectedFile, err := ioutil.ReadFile("role.yaml")
48+
Expect(err).NotTo(HaveOccurred())
49+
50+
By("parsing the desired YAML")
51+
var role rbacv1.ClusterRole
52+
Expect(yaml.Unmarshal(expectedFile, &role)).To(Succeed())
53+
54+
By("comparing the two")
55+
Expect(*clusterRole).To(Equal(role), "type not as expected, check pkg/rbac/testdata/README.md for more details.\n\nDiff:\n\n%s", cmp.Diff(*clusterRole, role))
56+
})
57+
})

pkg/rbac/rbac_suite_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package rbac_test
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/ginkgo"
23+
. "github.com/onsi/gomega"
24+
)
25+
26+
func TestCRDGeneration(t *testing.T) {
27+
RegisterFailHandler(Fail)
28+
RunSpecs(t, "RBAC Generation Suite")
29+
}

pkg/rbac/testdata/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# RBAC Integration Test testdata
2+
3+
This contains a tiny module used for testdata for the RBAC integration
4+
test. The directory should always be called testdata, so Go treats it
5+
specially.
6+
7+
If you add a new marker, re-generate the golden output file,
8+
`role.yaml`, with:
9+
10+
```bash
11+
$ /path/to/current/build/of/controller-gen rbac:roleName=manager-role paths=. output:dir=.
12+
```
13+
14+
Make sure you review the diff to ensure that it only contains the desired
15+
changes!
16+
17+
If you didn't add a new marker and this output changes, make sure you have
18+
a good explanation for why generated output needs to change!

pkg/rbac/testdata/controller.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package controller
2+
3+
// +kubebuilder:rbac:groups=batch.io,resources=cronjobs,verbs=get;watch;create
4+
// +kubebuilder:rbac:groups=batch.io,resources=cronjobs/status,verbs=get;update;patch
5+
// +kubebuilder:rbac:groups=art,resources=jobs,verbs=get
6+
// +kubebuilder:rbac:groups=batch;batch;batch,resources=jobs/status,verbs=watch
7+
// +kubebuilder:rbac:groups=batch;cron,resources=jobs/status,verbs=create;get
8+
// +kubebuilder:rbac:groups=cron;batch,resources=jobs/status,verbs=get;create
9+
// +kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=watch;watch

pkg/rbac/testdata/role.yaml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
---
3+
apiVersion: rbac.authorization.k8s.io/v1
4+
kind: ClusterRole
5+
metadata:
6+
creationTimestamp: null
7+
name: manager-role
8+
rules:
9+
- apiGroups:
10+
- batch.io
11+
resources:
12+
- cronjobs
13+
verbs:
14+
- create
15+
- get
16+
- watch
17+
- apiGroups:
18+
- batch.io
19+
resources:
20+
- cronjobs/status
21+
verbs:
22+
- get
23+
- patch
24+
- update
25+
- apiGroups:
26+
- art
27+
resources:
28+
- jobs
29+
verbs:
30+
- get
31+
- apiGroups:
32+
- batch
33+
resources:
34+
- jobs/status
35+
verbs:
36+
- watch
37+
- apiGroups:
38+
- batch
39+
- cron
40+
resources:
41+
- jobs/status
42+
verbs:
43+
- create
44+
- get

0 commit comments

Comments
 (0)