Skip to content

Commit 7ee694d

Browse files
committed
Refactor the role and rolebinding code again
Signed-off-by: Lubron Zhan <[email protected]>
1 parent 680c1d7 commit 7ee694d

File tree

6 files changed

+162
-95
lines changed

6 files changed

+162
-95
lines changed

internal/provisioner/objects/rbac/clusterrole/cluster_role.go

Lines changed: 10 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,10 @@ import (
2121
"github.com/projectcontour/contour/internal/provisioner/labels"
2222
"github.com/projectcontour/contour/internal/provisioner/model"
2323
"github.com/projectcontour/contour/internal/provisioner/objects"
24-
corev1 "k8s.io/api/core/v1"
25-
discoveryv1 "k8s.io/api/discovery/v1"
26-
networkingv1 "k8s.io/api/networking/v1"
24+
"github.com/projectcontour/contour/internal/provisioner/objects/rbac/util"
2725
rbacv1 "k8s.io/api/rbac/v1"
2826
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2927
"sigs.k8s.io/controller-runtime/pkg/client"
30-
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
31-
)
32-
33-
const (
34-
contourV1GroupName = "projectcontour.io"
3528
)
3629

3730
// EnsureClusterRole ensures a ClusterRole resource exists with the provided name
@@ -50,40 +43,7 @@ func EnsureClusterRole(ctx context.Context, cli client.Client, name string, cont
5043
// desiredClusterRole constructs an instance of the desired ClusterRole resource with
5144
// the provided name and contour namespace/name for the owning contour labels.
5245
func desiredClusterRole(name string, contour *model.Contour, gatewayClassOnly bool) *rbacv1.ClusterRole {
53-
var (
54-
createGetUpdate = []string{"create", "get", "update"}
55-
getListWatch = []string{"get", "list", "watch"}
56-
update = []string{"update"}
57-
)
58-
59-
policyRuleFor := func(apiGroup string, verbs []string, resources ...string) rbacv1.PolicyRule {
60-
return rbacv1.PolicyRule{
61-
Verbs: verbs,
62-
APIGroups: []string{apiGroup},
63-
Resources: resources,
64-
}
65-
}
66-
67-
if gatewayClassOnly {
68-
return &rbacv1.ClusterRole{
69-
TypeMeta: metav1.TypeMeta{
70-
Kind: "Role",
71-
},
72-
ObjectMeta: metav1.ObjectMeta{
73-
Name: name,
74-
Labels: contour.CommonLabels(),
75-
Annotations: contour.CommonAnnotations(),
76-
},
77-
Rules: []rbacv1.PolicyRule{
78-
// Gateway API resources.
79-
// Note, ReferenceGrant does not currently have a .status field so it's omitted from the status rule.
80-
policyRuleFor(gatewayv1alpha2.GroupName, getListWatch, "gatewayclasses"),
81-
policyRuleFor(gatewayv1alpha2.GroupName, update, "gatewayclasses/status"),
82-
},
83-
}
84-
}
85-
86-
return &rbacv1.ClusterRole{
46+
role := &rbacv1.ClusterRole{
8747
TypeMeta: metav1.TypeMeta{
8848
Kind: "Role",
8949
},
@@ -92,27 +52,15 @@ func desiredClusterRole(name string, contour *model.Contour, gatewayClassOnly bo
9252
Labels: contour.CommonLabels(),
9353
Annotations: contour.CommonAnnotations(),
9454
},
95-
Rules: []rbacv1.PolicyRule{
96-
// Core Contour-watched resources.
97-
policyRuleFor(corev1.GroupName, getListWatch, "secrets", "endpoints", "services", "namespaces"),
98-
99-
// Discovery Contour-watched resources.
100-
policyRuleFor(discoveryv1.GroupName, getListWatch, "endpointslices"),
101-
102-
// Gateway API resources.
103-
// Note, ReferenceGrant does not currently have a .status field so it's omitted from the status rule.
104-
policyRuleFor(gatewayv1alpha2.GroupName, getListWatch, "gatewayclasses", "gateways", "httproutes", "tlsroutes", "grpcroutes", "tcproutes", "referencegrants"),
105-
policyRuleFor(gatewayv1alpha2.GroupName, update, "gatewayclasses/status", "gateways/status", "httproutes/status", "tlsroutes/status", "grpcroutes/status", "tcproutes/status"),
106-
107-
// Ingress resources.
108-
policyRuleFor(networkingv1.GroupName, getListWatch, "ingresses"),
109-
policyRuleFor(networkingv1.GroupName, createGetUpdate, "ingresses/status"),
110-
111-
// Contour CRDs.
112-
policyRuleFor(contourV1GroupName, getListWatch, "httpproxies", "tlscertificatedelegations", "extensionservices", "contourconfigurations"),
113-
policyRuleFor(contourV1GroupName, createGetUpdate, "httpproxies/status", "extensionservices/status", "contourconfigurations/status"),
114-
},
55+
Rules: util.ClusterScopePolicyRulesForContour(),
56+
}
57+
if gatewayClassOnly {
58+
return role
11559
}
60+
61+
// add basic rules to role
62+
role.Rules = append(role.Rules, util.BasicPolicyRulesForContour()...)
63+
return role
11664
}
11765

11866
// updateClusterRoleIfNeeded updates a ClusterRole resource if current does not match desired,

internal/provisioner/objects/rbac/rbac.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,26 @@ func ensureContourRBAC(ctx context.Context, cli client.Client, contour *model.Co
5454
}
5555

5656
// By default, Contour watches all namespaces, use default cluster role and rolebinding
57+
clusterRoleForGatewayclassOnly := true
5758
if contour.WatchAllNamespaces() {
5859
// Ensure cluster role & binding.
59-
if err := clusterrole.EnsureClusterRole(ctx, cli, names.ClusterRole, contour, false); err != nil {
60+
if err := clusterrole.EnsureClusterRole(ctx, cli, names.ClusterRole, contour, !clusterRoleForGatewayclassOnly); err != nil {
6061
return fmt.Errorf("failed to ensure cluster role %s: %w", names.ClusterRole, err)
6162
}
6263
} else {
6364
// Ensure cluster role & cluster binding for gatewayclass first since it's cluster scope variables
64-
if err := clusterrole.EnsureClusterRole(ctx, cli, names.ClusterRole, contour, true); err != nil {
65+
if err := clusterrole.EnsureClusterRole(ctx, cli, names.ClusterRole, contour, clusterRoleForGatewayclassOnly); err != nil {
6566
return fmt.Errorf("failed to ensure cluster role %s: %w", names.ClusterRole, err)
6667
}
6768

6869
// Ensures role and rolebinding for set of namespaces in contour.spec.watchNamespaces variable
6970
// Ensure cluster role & cluster binding for gatewayclass first since it's cluster scope variables
70-
if err := role.EnsureRolesForNamespaces(ctx, cli, names.Role, contour); err != nil {
71+
if err := role.EnsureRolesInNamespaces(ctx, cli, names.Role, contour, contour.Spec.WatchNamespaces); err != nil {
7172
return fmt.Errorf("failed to ensure cluster role %s: %w", names.ClusterRole, err)
7273
}
7374
// Ensures role and rolebinding for set of namespaces in contour.spec.watchNamespaces variable
7475
// Ensure cluster role & cluster binding for gatewayclass first since it's cluster scope variables
75-
if err := rolebinding.EnsureRoleBindingsForNamespaces(ctx, cli, names.RoleBinding, names.ServiceAccount, names.Role, contour); err != nil {
76+
if err := rolebinding.EnsureRoleBindingsInNamespaces(ctx, cli, names.RoleBinding, names.ServiceAccount, names.Role, contour, contour.Spec.WatchNamespaces); err != nil {
7677
return fmt.Errorf("failed to ensure cluster role %s: %w", names.ClusterRole, err)
7778
}
7879
}

internal/provisioner/objects/rbac/role/role.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ import (
2121
"github.com/projectcontour/contour/internal/provisioner/labels"
2222
"github.com/projectcontour/contour/internal/provisioner/model"
2323
"github.com/projectcontour/contour/internal/provisioner/objects"
24+
"github.com/projectcontour/contour/internal/provisioner/objects/rbac/util"
2425
coordinationv1 "k8s.io/api/coordination/v1"
2526
corev1 "k8s.io/api/core/v1"
2627
rbacv1 "k8s.io/api/rbac/v1"
2728
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
kerrors "k8s.io/apimachinery/pkg/util/errors"
2830
"sigs.k8s.io/controller-runtime/pkg/client"
2931
)
3032

@@ -41,21 +43,29 @@ func EnsureControllerRole(ctx context.Context, cli client.Client, name string, c
4143
return objects.EnsureObject(ctx, cli, desired, updater, &rbacv1.Role{})
4244
}
4345

44-
// EnsureRolesForNamespaces ensures a Role resource exists with the for the Contour
45-
// controller.
46-
func EnsureRolesForNamespaces(ctx context.Context, cli client.Client, name string, contour *model.Contour) error {
47-
desired := desiredControllerRole(name, contour)
46+
// EnsureRolesInNamespaces ensures a set of Role resources exist in namespaces
47+
// specified, for contour to manage resources under these namespaces. And
48+
// contour namespace/name for the owning contour labels for the Contour
49+
// controller
50+
func EnsureRolesInNamespaces(ctx context.Context, cli client.Client, name string, contour *model.Contour, namespaces []string) error {
51+
errs := []error{}
52+
for _, ns := range namespaces {
53+
desired := desiredRoleForContourInNamespace(name, ns, contour)
4854

49-
updater := func(ctx context.Context, cli client.Client, current, desired *rbacv1.Role) error {
50-
_, err := updateRoleIfNeeded(ctx, cli, contour, current, desired)
51-
return err
55+
updater := func(ctx context.Context, cli client.Client, current, desired *rbacv1.Role) error {
56+
_, err := updateRoleIfNeeded(ctx, cli, contour, current, desired)
57+
return err
58+
}
59+
if err := objects.EnsureObject(ctx, cli, desired, updater, &rbacv1.Role{}); err != nil {
60+
errs = append(errs, err)
61+
}
5262
}
5363

54-
return objects.EnsureObject(ctx, cli, desired, updater, &rbacv1.Role{})
64+
return kerrors.NewAggregate(errs)
5565
}
5666

5767
// desiredControllerRole constructs an instance of the desired Role resource with the
58-
// provided ns/name and contour namespace/name for the owning contour labels for
68+
// provided ns/name and using contour namespace/name for the owning contour labels for
5969
// the Contour controller.
6070
func desiredControllerRole(name string, contour *model.Contour) *rbacv1.Role {
6171
role := &rbacv1.Role{
@@ -85,6 +95,23 @@ func desiredControllerRole(name string, contour *model.Contour) *rbacv1.Role {
8595
return role
8696
}
8797

98+
// desiredRoleForContour constructs an instance of the desired Role resource with the
99+
// provided ns/name and using contour namespace/name for the corresponding Countour instance
100+
func desiredRoleForContourInNamespace(name, namespace string, contour *model.Contour) *rbacv1.Role {
101+
return &rbacv1.Role{
102+
TypeMeta: metav1.TypeMeta{
103+
Kind: "Role",
104+
},
105+
ObjectMeta: metav1.ObjectMeta{
106+
Name: name,
107+
Namespace: contour.Namespace,
108+
Labels: contour.CommonLabels(),
109+
Annotations: contour.CommonAnnotations(),
110+
},
111+
Rules: util.BasicPolicyRulesForContour(),
112+
}
113+
}
114+
88115
// updateRoleIfNeeded updates a Role resource if current does not match desired,
89116
// using contour to verify the existence of owner labels.
90117
func updateRoleIfNeeded(ctx context.Context, cli client.Client, contour *model.Contour, current, desired *rbacv1.Role) (*rbacv1.Role, error) {

internal/provisioner/objects/rbac/rolebinding/role_binding.go

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,18 @@ import (
2121
"github.com/projectcontour/contour/internal/provisioner/labels"
2222
"github.com/projectcontour/contour/internal/provisioner/model"
2323
"github.com/projectcontour/contour/internal/provisioner/objects"
24-
2524
corev1 "k8s.io/api/core/v1"
2625
rbacv1 "k8s.io/api/rbac/v1"
2726
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
kerrors "k8s.io/apimachinery/pkg/util/errors"
2828
"sigs.k8s.io/controller-runtime/pkg/client"
2929
)
3030

3131
// EnsureRoleBinding ensures a RoleBinding resource exists with the provided
32-
// ns/name and contour namespace/name for the owning contour labels.
32+
// ns/name and using contour namespace/name for the owning contour labels.
3333
// The RoleBinding will use svcAct for the subject and role for the role reference.
3434
func EnsureRoleBinding(ctx context.Context, cli client.Client, name, svcAct, role string, contour *model.Contour) error {
35-
desired := desiredRoleBinding(name, svcAct, role, contour)
35+
desired := desiredRoleBindingInNamespace(name, svcAct, role, contour.Namespace, contour)
3636

3737
// Enclose contour.
3838
updater := func(ctx context.Context, cli client.Client, current, desired *rbacv1.RoleBinding) error {
@@ -42,40 +42,45 @@ func EnsureRoleBinding(ctx context.Context, cli client.Client, name, svcAct, rol
4242
return objects.EnsureObject(ctx, cli, desired, updater, &rbacv1.RoleBinding{})
4343
}
4444

45-
// EnsureRoleBindingsForNamespaces ensures a RoleBinding resource exists with the provided
46-
// ns/name and contour namespace/name for the owning contour labels.
47-
// The RoleBinding will use svcAct for the subject and role for the role reference.
48-
func EnsureRoleBindingsForNamespaces(ctx context.Context, cli client.Client, name, svcAct, role string, contour *model.Contour) error {
49-
desired := desiredRoleBinding(name, svcAct, role, contour)
45+
// EnsureRoleBindingsInNamespaces ensures a set of RoleBindings resource exist with the provided
46+
// namespaces/name using contour namespace/name for the owning contour labels.
47+
// The RoleBindings will use same svcAct for the subject and role for the role reference.
48+
func EnsureRoleBindingsInNamespaces(ctx context.Context, cli client.Client, name, svcAct, role string, contour *model.Contour, namespaces []string) error {
49+
errs := []error{}
50+
for _, ns := range namespaces {
51+
desired := desiredRoleBindingInNamespace(name, svcAct, role, ns, contour)
5052

51-
// Enclose contour.
52-
updater := func(ctx context.Context, cli client.Client, current, desired *rbacv1.RoleBinding) error {
53-
return updateRoleBindingIfNeeded(ctx, cli, contour, current, desired)
53+
// Enclose contour.
54+
updater := func(ctx context.Context, cli client.Client, current, desired *rbacv1.RoleBinding) error {
55+
return updateRoleBindingIfNeeded(ctx, cli, contour, current, desired)
56+
}
57+
objects.EnsureObject(ctx, cli, desired, updater, &rbacv1.RoleBinding{})
5458
}
5559

56-
return objects.EnsureObject(ctx, cli, desired, updater, &rbacv1.RoleBinding{})
60+
return kerrors.NewAggregate(errs)
5761
}
5862

59-
// desiredRoleBinding constructs an instance of the desired RoleBinding resource
60-
// with the provided name in Contour spec Namespace, using contour namespace/name
63+
// desiredRoleBindingInNamespace constructs an instance of the desired RoleBinding resource
64+
// with the provided name in provided namespace, using contour namespace/name
6165
// for the owning contour labels. The RoleBinding will use svcAct for the subject
6266
// and role for the role reference.
63-
func desiredRoleBinding(name, svcAcctRef, roleRef string, contour *model.Contour) *rbacv1.RoleBinding {
67+
func desiredRoleBindingInNamespace(name, svcAcctRef, roleRef, namespace string, contour *model.Contour) *rbacv1.RoleBinding {
6468
rb := &rbacv1.RoleBinding{
6569
TypeMeta: metav1.TypeMeta{
6670
Kind: "RoleBinding",
6771
},
6872
ObjectMeta: metav1.ObjectMeta{
69-
Namespace: contour.Namespace,
73+
Namespace: namespace,
7074
Name: name,
7175
Labels: contour.CommonLabels(),
7276
Annotations: contour.CommonAnnotations(),
7377
},
7478
}
7579
rb.Subjects = []rbacv1.Subject{{
76-
Kind: "ServiceAccount",
77-
APIGroup: corev1.GroupName,
78-
Name: svcAcctRef,
80+
Kind: "ServiceAccount",
81+
APIGroup: corev1.GroupName,
82+
Name: svcAcctRef,
83+
// service account will be the same one
7984
Namespace: contour.Namespace,
8085
}}
8186
rb.RoleRef = rbacv1.RoleRef{

internal/provisioner/objects/rbac/rolebinding/role_binding_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ func checkRoleBindingName(t *testing.T, rb *rbacv1.RoleBinding, expected string)
3333
t.Errorf("role binding %q has unexpected name", rb.Name)
3434
}
3535

36+
func checkRoleBindingNamespace(t *testing.T, rb *rbacv1.RoleBinding, expected string) {
37+
t.Helper()
38+
39+
if rb.Namespace == expected {
40+
return
41+
}
42+
43+
t.Errorf("role binding %q has unexpected namespace", rb.Namespace)
44+
}
45+
3646
func checkRoleBindingLabels(t *testing.T, rb *rbacv1.RoleBinding, expected map[string]string) {
3747
t.Helper()
3848

@@ -66,11 +76,13 @@ func checkRoleBindingRole(t *testing.T, rb *rbacv1.RoleBinding, expected string)
6676
func TestDesiredRoleBinding(t *testing.T) {
6777
name := "job-test"
6878
cntr := model.Default(fmt.Sprintf("%s-ns", name), name)
79+
testns := "test-ns"
6980
rbName := "test-rb"
7081
svcAcct := "test-svc-acct-ref"
7182
roleRef := "test-role-ref"
72-
rb := desiredRoleBinding(rbName, svcAcct, roleRef, cntr)
83+
rb := desiredRoleBindingInNamespace(rbName, svcAcct, roleRef, testns, cntr)
7384
checkRoleBindingName(t, rb, rbName)
85+
checkRoleBindingNamespace(t, rb, testns)
7486
ownerLabels := map[string]string{
7587
model.ContourOwningGatewayNameLabel: cntr.Name,
7688
model.GatewayAPIOwningGatewayNameLabel: cntr.Name,
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright Project Contour Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package util
15+
16+
import (
17+
corev1 "k8s.io/api/core/v1"
18+
discoveryv1 "k8s.io/api/discovery/v1"
19+
networkingv1 "k8s.io/api/networking/v1"
20+
rbacv1 "k8s.io/api/rbac/v1"
21+
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
22+
)
23+
24+
const (
25+
ContourV1GroupName = "projectcontour.io"
26+
)
27+
28+
var (
29+
createGetUpdate = []string{"create", "get", "update"}
30+
getListWatch = []string{"get", "list", "watch"}
31+
update = []string{"update"}
32+
)
33+
34+
// PolicyRuleFor returns PolicyRule object with provided apiGroup, verbs and resources
35+
func PolicyRuleFor(apiGroup string, verbs []string, resources ...string) rbacv1.PolicyRule {
36+
return rbacv1.PolicyRule{
37+
Verbs: verbs,
38+
APIGroups: []string{apiGroup},
39+
Resources: resources,
40+
}
41+
}
42+
43+
// BasicPolicyRulesForContour returns set of basic rules that contour requires
44+
func BasicPolicyRulesForContour() []rbacv1.PolicyRule {
45+
return []rbacv1.PolicyRule{
46+
// Core Contour-watched resources.
47+
PolicyRuleFor(corev1.GroupName, getListWatch, "secrets", "endpoints", "services", "namespaces"),
48+
49+
// Discovery Contour-watched resources.
50+
PolicyRuleFor(discoveryv1.GroupName, getListWatch, "endpointslices"),
51+
52+
// Gateway API resources.
53+
// Note, ReferenceGrant does not currently have a .status field so it's omitted from the status rule.
54+
PolicyRuleFor(gatewayv1alpha2.GroupName, getListWatch, "gateways", "httproutes", "tlsroutes", "grpcroutes", "tcproutes", "referencegrants"),
55+
PolicyRuleFor(gatewayv1alpha2.GroupName, update, "gateways/status", "httproutes/status", "tlsroutes/status", "grpcroutes/status", "tcproutes/status"),
56+
57+
// Ingress resources.
58+
PolicyRuleFor(networkingv1.GroupName, getListWatch, "ingresses"),
59+
PolicyRuleFor(networkingv1.GroupName, createGetUpdate, "ingresses/status"),
60+
61+
// Contour CRDs.
62+
PolicyRuleFor(ContourV1GroupName, getListWatch, "httpproxies", "tlscertificatedelegations", "extensionservices", "contourconfigurations"),
63+
PolicyRuleFor(ContourV1GroupName, createGetUpdate, "httpproxies/status", "extensionservices/status", "contourconfigurations/status"),
64+
}
65+
}
66+
67+
// ClusterScopePolicyRulesForContour returns set of rules only for cluster scope object
68+
func ClusterScopePolicyRulesForContour() []rbacv1.PolicyRule {
69+
return []rbacv1.PolicyRule{
70+
// GatewayClass only.
71+
PolicyRuleFor(gatewayv1alpha2.GroupName, getListWatch, "gatewayclasses"),
72+
PolicyRuleFor(gatewayv1alpha2.GroupName, update, "gatewayclasses/status"),
73+
}
74+
}

0 commit comments

Comments
 (0)