Skip to content
This repository was archived by the owner on Apr 4, 2023. It is now read-only.

Commit e87a3a6

Browse files
Merge pull request #162 from wallrj/161-pilot-privileges
Automatic merge from submit-queue. Pilot service account and RBAC policy * Add a service account for Pilots * and a corresponding RBAC policy which allows pilots to update their pilot resources. Fixes: #161 **Release note**: ```release-note NONE ```
2 parents 6ef8657 + 4c2dcba commit e87a3a6

File tree

12 files changed

+551
-16
lines changed

12 files changed

+551
-16
lines changed

hack/e2e.sh

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,6 @@ function test_cassandracluster() {
207207

208208
kubectl create namespace "${namespace}"
209209

210-
# XXX Temporary work around until cassandra controller manages RBAC
211-
kubectl create --namespace "${namespace}" \
212-
rolebinding "${namespace}-binding" \
213-
--clusterrole=cluster-admin \
214-
--serviceaccount="${namespace}:default"
215-
216210
if ! kubectl get \
217211
--namespace "${namespace}" \
218212
CassandraClusters; then

pkg/controllers/cassandra/cassandra.go

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ import (
2424
"github.com/jetstack/navigator/pkg/controllers"
2525
"github.com/jetstack/navigator/pkg/controllers/cassandra/nodepool"
2626
"github.com/jetstack/navigator/pkg/controllers/cassandra/pilot"
27+
"github.com/jetstack/navigator/pkg/controllers/cassandra/role"
28+
"github.com/jetstack/navigator/pkg/controllers/cassandra/rolebinding"
2729
servicecql "github.com/jetstack/navigator/pkg/controllers/cassandra/service/cql"
2830
serviceseedprovider "github.com/jetstack/navigator/pkg/controllers/cassandra/service/seedprovider"
31+
"github.com/jetstack/navigator/pkg/controllers/cassandra/serviceaccount"
32+
rbacinformers "k8s.io/client-go/informers/rbac/v1beta1"
2933
)
3034

3135
// NewCassandra returns a new CassandraController that can be used
@@ -35,16 +39,19 @@ import (
3539
// It accepts a list of informers that are then used to monitor the state of the
3640
// target cluster.
3741
type CassandraController struct {
38-
control ControlInterface
39-
cassLister listersv1alpha1.CassandraClusterLister
40-
statefulSetLister appslisters.StatefulSetLister
41-
cassListerSynced cache.InformerSynced
42-
serviceListerSynced cache.InformerSynced
43-
statefulSetListerSynced cache.InformerSynced
44-
pilotsListerSynced cache.InformerSynced
45-
podsListerSynced cache.InformerSynced
46-
queue workqueue.RateLimitingInterface
47-
recorder record.EventRecorder
42+
control ControlInterface
43+
cassLister listersv1alpha1.CassandraClusterLister
44+
statefulSetLister appslisters.StatefulSetLister
45+
cassListerSynced cache.InformerSynced
46+
serviceListerSynced cache.InformerSynced
47+
statefulSetListerSynced cache.InformerSynced
48+
pilotsListerSynced cache.InformerSynced
49+
podsListerSynced cache.InformerSynced
50+
serviceAccountsListerSynced cache.InformerSynced
51+
rolesListerSynced cache.InformerSynced
52+
roleBindingsListerSynced cache.InformerSynced
53+
queue workqueue.RateLimitingInterface
54+
recorder record.EventRecorder
4855
}
4956

5057
func NewCassandra(
@@ -55,6 +62,9 @@ func NewCassandra(
5562
statefulSets appsinformers.StatefulSetInformer,
5663
pilots navigatorinformers.PilotInformer,
5764
pods coreinformers.PodInformer,
65+
serviceAccounts coreinformers.ServiceAccountInformer,
66+
roles rbacinformers.RoleInformer,
67+
roleBindings rbacinformers.RoleBindingInformer,
5868
recorder record.EventRecorder,
5969
) *CassandraController {
6070
queue := workqueue.NewNamedRateLimitingQueue(
@@ -82,6 +92,9 @@ func NewCassandra(
8292
cc.statefulSetListerSynced = statefulSets.Informer().HasSynced
8393
cc.pilotsListerSynced = pilots.Informer().HasSynced
8494
cc.podsListerSynced = pods.Informer().HasSynced
95+
cc.serviceAccountsListerSynced = serviceAccounts.Informer().HasSynced
96+
cc.rolesListerSynced = roles.Informer().HasSynced
97+
cc.roleBindingsListerSynced = roleBindings.Informer().HasSynced
8598
cc.control = NewControl(
8699
serviceseedprovider.NewControl(
87100
kubeClient,
@@ -105,6 +118,21 @@ func NewCassandra(
105118
statefulSets.Lister(),
106119
recorder,
107120
),
121+
serviceaccount.NewControl(
122+
kubeClient,
123+
serviceAccounts.Lister(),
124+
recorder,
125+
),
126+
role.NewControl(
127+
kubeClient,
128+
roles.Lister(),
129+
recorder,
130+
),
131+
rolebinding.NewControl(
132+
kubeClient,
133+
roleBindings.Lister(),
134+
recorder,
135+
),
108136
recorder,
109137
)
110138
cc.recorder = recorder
@@ -274,6 +302,9 @@ func CassandraControllerFromContext(ctx *controllers.Context) *CassandraControll
274302
ctx.KubeSharedInformerFactory.Apps().V1beta1().StatefulSets(),
275303
ctx.SharedInformerFactory.Navigator().V1alpha1().Pilots(),
276304
ctx.KubeSharedInformerFactory.Core().V1().Pods(),
305+
ctx.KubeSharedInformerFactory.Core().V1().ServiceAccounts(),
306+
ctx.KubeSharedInformerFactory.Rbac().V1beta1().Roles(),
307+
ctx.KubeSharedInformerFactory.Rbac().V1beta1().RoleBindings(),
277308
ctx.Recorder,
278309
)
279310
}

pkg/controllers/cassandra/cluster_control.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import (
55
v1alpha1 "github.com/jetstack/navigator/pkg/apis/navigator/v1alpha1"
66
"github.com/jetstack/navigator/pkg/controllers/cassandra/nodepool"
77
"github.com/jetstack/navigator/pkg/controllers/cassandra/pilot"
8+
"github.com/jetstack/navigator/pkg/controllers/cassandra/role"
9+
"github.com/jetstack/navigator/pkg/controllers/cassandra/rolebinding"
810
servicecql "github.com/jetstack/navigator/pkg/controllers/cassandra/service/cql"
911
serviceseedprovider "github.com/jetstack/navigator/pkg/controllers/cassandra/service/seedprovider"
12+
"github.com/jetstack/navigator/pkg/controllers/cassandra/serviceaccount"
1013
apiv1 "k8s.io/api/core/v1"
1114
"k8s.io/client-go/tools/record"
1215
)
@@ -17,6 +20,8 @@ const (
1720
SuccessSync = "SuccessSync"
1821

1922
MessageErrorSyncServiceAccount = "Error syncing service account: %s"
23+
MessageErrorSyncRole = "Error syncing role: %s"
24+
MessageErrorSyncRoleBinding = "Error syncing role binding: %s"
2025
MessageErrorSyncConfigMap = "Error syncing config map: %s"
2126
MessageErrorSyncService = "Error syncing service: %s"
2227
MessageErrorSyncNodePools = "Error syncing node pools: %s"
@@ -35,6 +40,9 @@ type defaultCassandraClusterControl struct {
3540
cqlServiceControl servicecql.Interface
3641
nodepoolControl nodepool.Interface
3742
pilotControl pilot.Interface
43+
serviceAccountControl serviceaccount.Interface
44+
roleControl role.Interface
45+
roleBindingControl rolebinding.Interface
3846
recorder record.EventRecorder
3947
}
4048

@@ -43,13 +51,19 @@ func NewControl(
4351
cqlServiceControl servicecql.Interface,
4452
nodepoolControl nodepool.Interface,
4553
pilotControl pilot.Interface,
54+
serviceAccountControl serviceaccount.Interface,
55+
roleControl role.Interface,
56+
roleBindingControl rolebinding.Interface,
4657
recorder record.EventRecorder,
4758
) ControlInterface {
4859
return &defaultCassandraClusterControl{
4960
seedProviderServiceControl: seedProviderServiceControl,
5061
cqlServiceControl: cqlServiceControl,
5162
nodepoolControl: nodepoolControl,
5263
pilotControl: pilotControl,
64+
serviceAccountControl: serviceAccountControl,
65+
roleControl: roleControl,
66+
roleBindingControl: roleBindingControl,
5367
recorder: recorder,
5468
}
5569
}
@@ -100,6 +114,39 @@ func (e *defaultCassandraClusterControl) Sync(c *v1alpha1.CassandraCluster) erro
100114
)
101115
return err
102116
}
117+
err = e.serviceAccountControl.Sync(c)
118+
if err != nil {
119+
e.recorder.Eventf(
120+
c,
121+
apiv1.EventTypeWarning,
122+
ErrorSync,
123+
MessageErrorSyncServiceAccount,
124+
err,
125+
)
126+
return err
127+
}
128+
err = e.roleControl.Sync(c)
129+
if err != nil {
130+
e.recorder.Eventf(
131+
c,
132+
apiv1.EventTypeWarning,
133+
ErrorSync,
134+
MessageErrorSyncRole,
135+
err,
136+
)
137+
return err
138+
}
139+
err = e.roleBindingControl.Sync(c)
140+
if err != nil {
141+
e.recorder.Eventf(
142+
c,
143+
apiv1.EventTypeWarning,
144+
ErrorSync,
145+
MessageErrorSyncRoleBinding,
146+
err,
147+
)
148+
return err
149+
}
103150
e.recorder.Event(
104151
c,
105152
apiv1.EventTypeNormal,

pkg/controllers/cassandra/nodepool/resource.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func StatefulSetForCluster(
4747
Labels: nodePoolLabels,
4848
},
4949
Spec: apiv1.PodSpec{
50+
ServiceAccountName: util.ServiceAccountName(cluster),
5051
Volumes: []apiv1.Volume{
5152
apiv1.Volume{
5253
Name: sharedVolumeName,
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package role
2+
3+
import (
4+
"github.com/jetstack/navigator/pkg/apis/navigator"
5+
"github.com/jetstack/navigator/pkg/apis/navigator/v1alpha1"
6+
"github.com/jetstack/navigator/pkg/controllers/cassandra/util"
7+
rbacv1 "k8s.io/api/rbac/v1beta1"
8+
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/client-go/kubernetes"
11+
rbaclisters "k8s.io/client-go/listers/rbac/v1beta1"
12+
13+
"k8s.io/client-go/tools/record"
14+
)
15+
16+
type Interface interface {
17+
Sync(*v1alpha1.CassandraCluster) error
18+
}
19+
20+
type control struct {
21+
kubeClient kubernetes.Interface
22+
roleLister rbaclisters.RoleLister
23+
recorder record.EventRecorder
24+
}
25+
26+
var _ Interface = &control{}
27+
28+
func NewControl(
29+
kubeClient kubernetes.Interface,
30+
roleLister rbaclisters.RoleLister,
31+
recorder record.EventRecorder,
32+
) *control {
33+
return &control{
34+
kubeClient: kubeClient,
35+
roleLister: roleLister,
36+
recorder: recorder,
37+
}
38+
}
39+
40+
func (c *control) Sync(cluster *v1alpha1.CassandraCluster) error {
41+
newRole := RoleForCluster(cluster)
42+
client := c.kubeClient.RbacV1beta1().Roles(newRole.Namespace)
43+
_, err := c.roleLister.
44+
Roles(newRole.Namespace).
45+
Get(newRole.Name)
46+
if k8sErrors.IsNotFound(err) {
47+
_, err = client.Create(newRole)
48+
}
49+
return err
50+
}
51+
52+
func RoleForCluster(cluster *v1alpha1.CassandraCluster) *rbacv1.Role {
53+
return &rbacv1.Role{
54+
ObjectMeta: metav1.ObjectMeta{
55+
Name: util.PilotRBACRoleName(cluster),
56+
Namespace: cluster.Namespace,
57+
OwnerReferences: []metav1.OwnerReference{
58+
util.NewControllerRef(cluster),
59+
},
60+
Labels: util.ClusterLabels(cluster),
61+
},
62+
Rules: []rbacv1.PolicyRule{
63+
{
64+
APIGroups: []string{""},
65+
Verbs: []string{"create", "update", "patch"},
66+
Resources: []string{"events"},
67+
},
68+
{
69+
APIGroups: []string{""},
70+
Verbs: []string{"create", "update", "patch", "get", "list", "watch"},
71+
Resources: []string{"configmaps"},
72+
},
73+
{
74+
APIGroups: []string{navigator.GroupName},
75+
Verbs: []string{"get", "list", "watch"},
76+
Resources: []string{
77+
"pilots",
78+
"cassandraclusters",
79+
},
80+
},
81+
{
82+
APIGroups: []string{navigator.GroupName},
83+
Verbs: []string{"update", "patch"},
84+
Resources: []string{
85+
"pilots/status",
86+
},
87+
},
88+
},
89+
}
90+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package role_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/jetstack/navigator/pkg/controllers/cassandra/role"
8+
casstesting "github.com/jetstack/navigator/pkg/controllers/cassandra/testing"
9+
)
10+
11+
func TestRoleSync(t *testing.T) {
12+
t.Run(
13+
"role missing",
14+
func(t *testing.T) {
15+
f := casstesting.NewFixture(t)
16+
f.Run()
17+
f.AssertRolesLength(1)
18+
},
19+
)
20+
t.Run(
21+
"role exists",
22+
func(t *testing.T) {
23+
f := casstesting.NewFixture(t)
24+
existingRole := role.RoleForCluster(f.Cluster)
25+
f.AddObjectK(existingRole)
26+
f.Run()
27+
f.AssertRolesLength(1)
28+
},
29+
)
30+
t.Run(
31+
"sync fails",
32+
func(t *testing.T) {
33+
f := casstesting.NewFixture(t)
34+
f.RoleControl = &casstesting.FakeControl{
35+
SyncError: fmt.Errorf("simulated sync error"),
36+
}
37+
f.RunExpectError()
38+
},
39+
)
40+
}

0 commit comments

Comments
 (0)