Skip to content

Commit c6eafa3

Browse files
authored
Merge pull request #149 from appuio/be-write-perms
Add permission to create BE; give admin permission over created BE
2 parents 7688c3d + d749694 commit c6eafa3

File tree

4 files changed

+77
-32
lines changed

4 files changed

+77
-32
lines changed

apiserver/billing/rbac.go

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
)
1919

2020
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=get;list;watch;create;delete;patch;update;edit
21-
// +kubebuilder:rbac:groups=rbac.appuio.io,resources=billingentities,verbs=*
21+
// +kubebuilder:rbac:groups=rbac.appuio.io;billing.appuio.io,resources=billingentities,verbs=*
2222

2323
// createRBACWrapper is a wrapper around the storage that creates a ClusterRole and ClusterRoleBinding for each BillingEntity on creation.
2424
type createRBACWrapper struct {
@@ -44,11 +44,10 @@ func (c *createRBACWrapper) Create(ctx context.Context, obj runtime.Object, crea
4444
return createdObj, fmt.Errorf("could not get name of created object: %w", err)
4545
}
4646

47-
rolename := fmt.Sprintf("billingentities-%s-viewer", objName)
48-
49-
role := &rbacv1.ClusterRole{
47+
viewRoleName := fmt.Sprintf("billingentities-%s-viewer", objName)
48+
viewRole := &rbacv1.ClusterRole{
5049
ObjectMeta: metav1.ObjectMeta{
51-
Name: rolename,
50+
Name: viewRoleName,
5251
},
5352
Rules: []rbacv1.PolicyRule{
5453
{
@@ -59,10 +58,40 @@ func (c *createRBACWrapper) Create(ctx context.Context, obj runtime.Object, crea
5958
},
6059
},
6160
}
62-
63-
rolebinding := &rbacv1.ClusterRoleBinding{
61+
viewRoleBinding := &rbacv1.ClusterRoleBinding{
62+
ObjectMeta: metav1.ObjectMeta{
63+
Name: viewRoleName,
64+
},
65+
Subjects: []rbacv1.Subject{},
66+
RoleRef: rbacv1.RoleRef{
67+
Kind: "ClusterRole",
68+
APIGroup: "rbac.authorization.k8s.io",
69+
Name: viewRoleName,
70+
},
71+
}
72+
adminRoleName := fmt.Sprintf("billingentities-%s-admin", objName)
73+
adminRole := &rbacv1.ClusterRole{
74+
ObjectMeta: metav1.ObjectMeta{
75+
Name: adminRoleName,
76+
},
77+
Rules: []rbacv1.PolicyRule{
78+
{
79+
APIGroups: []string{"rbac.appuio.io", "billing.appuio.io"},
80+
Resources: []string{"billingentities"},
81+
Verbs: []string{"get", "patch", "update", "edit"},
82+
ResourceNames: []string{objName},
83+
},
84+
{
85+
APIGroups: []string{"rbac.authorization.k8s.io"},
86+
Resources: []string{"clusterrolebindings"},
87+
Verbs: []string{"get", "edit", "update", "patch"},
88+
ResourceNames: []string{viewRoleName, adminRoleName},
89+
},
90+
},
91+
}
92+
adminRoleBinding := &rbacv1.ClusterRoleBinding{
6493
ObjectMeta: metav1.ObjectMeta{
65-
Name: rolename,
94+
Name: adminRoleName,
6695
},
6796
Subjects: []rbacv1.Subject{
6897
{
@@ -74,7 +103,7 @@ func (c *createRBACWrapper) Create(ctx context.Context, obj runtime.Object, crea
74103
RoleRef: rbacv1.RoleRef{
75104
Kind: "ClusterRole",
76105
APIGroup: "rbac.authorization.k8s.io",
77-
Name: rolename,
106+
Name: adminRoleName,
78107
},
79108
}
80109

@@ -87,16 +116,21 @@ func (c *createRBACWrapper) Create(ctx context.Context, obj runtime.Object, crea
87116
return nil
88117
}
89118

90-
err = c.client.Create(ctx, role, &client.CreateOptions{DryRun: opts.DryRun})
91-
if err != nil {
92-
rollbackErr := rollback()
93-
return createdObj, multierr.Append(err, rollbackErr)
119+
toCreate := []client.Object{viewRole, viewRoleBinding, adminRole, adminRoleBinding}
120+
created := make([]client.Object, 0, len(toCreate))
121+
var createErr error
122+
for _, obj := range toCreate {
123+
if err := c.client.Create(ctx, obj, &client.CreateOptions{DryRun: opts.DryRun}); err != nil {
124+
createErr = err
125+
break
126+
}
127+
created = append(created, obj)
94128
}
95-
err = c.client.Create(ctx, rolebinding, &client.CreateOptions{DryRun: opts.DryRun})
96-
if err != nil {
97-
rollbackErr := rollback()
98-
roleRollbackErr := c.client.Delete(ctx, role, &client.DeleteOptions{DryRun: opts.DryRun})
99-
return createdObj, multierr.Combine(err, rollbackErr, roleRollbackErr)
129+
if err := createErr; err != nil {
130+
for _, obj := range created {
131+
multierr.AppendInto(&err, c.client.Delete(ctx, obj, &client.DeleteOptions{DryRun: opts.DryRun}))
132+
}
133+
return createdObj, multierr.Combine(err, rollback())
100134
}
101135

102136
return createdObj, nil

apiserver/billing/rbac_test.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,22 @@ func Test_createRBACWrapper(t *testing.T) {
4545
_, err := subject.Create(ctxWithInfo("create", "", user), &testresource.TestResource{}, nil, &metav1.CreateOptions{})
4646
require.NoError(t, err)
4747

48-
rn := "billingentities-" + returnedResourceName + "-viewer"
49-
var role rbacv1.ClusterRole
50-
require.NoError(t, c.Get(context.Background(), types.NamespacedName{Name: rn}, &role))
51-
var clusterrole rbacv1.ClusterRoleBinding
52-
require.NoError(t, c.Get(context.Background(), types.NamespacedName{Name: rn}, &clusterrole))
53-
assert.Equal(t, []rbacv1.Subject{{APIGroup: "rbac.authorization.k8s.io", Kind: "User", Name: user}}, clusterrole.Subjects)
48+
{
49+
viewerName := "billingentities-" + returnedResourceName + "-viewer"
50+
var role rbacv1.ClusterRole
51+
require.NoError(t, c.Get(context.Background(), types.NamespacedName{Name: viewerName}, &role))
52+
var clusterrole rbacv1.ClusterRoleBinding
53+
require.NoError(t, c.Get(context.Background(), types.NamespacedName{Name: viewerName}, &clusterrole))
54+
}
55+
56+
{
57+
adminName := "billingentities-" + returnedResourceName + "-admin"
58+
var role rbacv1.ClusterRole
59+
require.NoError(t, c.Get(context.Background(), types.NamespacedName{Name: adminName}, &role))
60+
var clusterrole rbacv1.ClusterRoleBinding
61+
require.NoError(t, c.Get(context.Background(), types.NamespacedName{Name: adminName}, &clusterrole))
62+
assert.Equal(t, []rbacv1.Subject{{APIGroup: "rbac.authorization.k8s.io", Kind: "User", Name: user}}, clusterrole.Subjects)
63+
}
5464
}
5565

5666
func Test_createRBACWrapper_rollback(t *testing.T) {

config/rbac/apiserver/role.yaml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ rules:
8080
- patch
8181
- update
8282
- watch
83+
- apiGroups:
84+
- billing.appuio.io
85+
- rbac.appuio.io
86+
resources:
87+
- billingentities
88+
verbs:
89+
- '*'
8390
- apiGroups:
8491
- coordination.k8s.io
8592
resources:
@@ -111,12 +118,6 @@ rules:
111118
- patch
112119
- update
113120
- watch
114-
- apiGroups:
115-
- rbac.appuio.io
116-
resources:
117-
- billingentities
118-
verbs:
119-
- '*'
120121
- apiGroups:
121122
- rbac.appuio.io
122123
resources:

config/user-rbac/basic-user-role.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ rules:
1616
# `get` permissions are created manually or when creating a new BillingEntity
1717
- apiGroups: ["rbac.appuio.io"]
1818
resources: ["billingentities"]
19-
verbs: ["watch", "list"]
19+
verbs: ["create", "watch", "list"]
2020
- apiGroups: ["billing.appuio.io"]
2121
resources: ["billingentities"]
22-
verbs: ["get", "watch", "list"]
22+
verbs: ["create", "get", "watch", "list"]
2323
# Invitation
2424
# `get` permissions are created when creating a new BillingEntity
2525
- apiGroups: ["rbac.appuio.io"]

0 commit comments

Comments
 (0)