@@ -3,8 +3,10 @@ package rules
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "time"
6
7
7
8
"github.com/octopusdeploy/octopus-permissions-controller/api/v1beta1"
9
+ "go.uber.org/multierr"
8
10
corev1 "k8s.io/api/core/v1"
9
11
rbacv1 "k8s.io/api/rbac/v1"
10
12
"k8s.io/apimachinery/pkg/api/errors"
@@ -46,8 +48,9 @@ type Engine interface {
46
48
}
47
49
48
50
type InMemoryEngine struct {
49
- rules map [AgentName ]map [Scope ]ServiceAccountName
50
- client client.Client
51
+ rules map [AgentName ]map [Scope ]ServiceAccountName
52
+ createdRoles map [string ]string
53
+ client client.Client
51
54
}
52
55
53
56
func (s * Scope ) IsEmpty () bool {
@@ -70,6 +73,59 @@ func (i *InMemoryEngine) GetServiceAccountForScope(scope Scope, agentName AgentN
70
73
return "" , nil
71
74
}
72
75
76
+ func (i * InMemoryEngine ) Reconcile2 (ctx context.Context ) error {
77
+ logger := log .FromContext (ctx ).WithName ("engine" )
78
+ i .createdRoles = make (map [string ]string )
79
+
80
+ wsaList , err := getWorkloadServiceAccounts (ctx , i .client )
81
+ if err != nil {
82
+ return err
83
+ }
84
+
85
+ err = i .ensureRoles (& wsaList )
86
+ if err != nil {
87
+ logger .Error (err , "failed to ensure roles for workload service accounts" )
88
+ }
89
+
90
+ err = i .generateRoleBindings (& wsaList )
91
+ if err != nil {
92
+ logger .Error (err , "failed to ensure role bindings for workload service accounts" )
93
+ }
94
+
95
+ return nil
96
+ }
97
+
98
+ func (i * InMemoryEngine ) ensureRoles (wsaList * []v1beta1.WorkloadServiceAccount ) error {
99
+ var err error
100
+ for _ , wsa := range * wsaList {
101
+ ctx := getContextWithTimeout (time .Second * 30 )
102
+ if role , createErr := i .createRoleIfNeeded (ctx , wsa ); createErr != nil {
103
+ err = multierr .Append (err , createErr )
104
+ } else if role != nil {
105
+ i .createdRoles [wsa .Name ] = role .Name
106
+ }
107
+ }
108
+ return err
109
+ }
110
+
111
+ func (i * InMemoryEngine ) generateRoleBindings (wsaList * []v1beta1.WorkloadServiceAccount ) error {
112
+ var err error
113
+ for _ , wsa := range * wsaList {
114
+ if role , createErr := i .createRoleIfNeeded (ctx , wsa ); createErr != nil {
115
+ err = multierr .Append (err , createErr )
116
+ } else if role != nil {
117
+ i .createdRoles [wsa .Name ] = role .Name
118
+ }
119
+ }
120
+ return err
121
+ }
122
+
123
+ func getContextWithTimeout (timeout time.Duration ) context.Context {
124
+ ctx , cancel := context .WithDeadline (context .Background (), time .Now ().Add (timeout ))
125
+ defer cancel ()
126
+ return ctx
127
+ }
128
+
73
129
func (i * InMemoryEngine ) Reconcile (ctx context.Context , namespace string ) error {
74
130
logger := log .FromContext (ctx ).WithName ("engine" )
75
131
@@ -153,12 +209,14 @@ func createServiceAccount(ctx context.Context, c client.Client, namespace string
153
209
}
154
210
155
211
// createRoleIfNeeded creates a Role for inline permissions if they exist
156
- func createRoleIfNeeded (ctx context.Context , c client. Client , namespace string , permissions []rbacv1. PolicyRule ) (string , error ) {
212
+ func ( i * InMemoryEngine ) createRoleIfNeeded (ctx context.Context , wsa v1beta1. WorkloadServiceAccount ) (* rbacv1. Role , error ) {
157
213
logger := log .FromContext (ctx ).WithName ("createRoleIfNeeded" )
158
214
159
- if len (permissions ) == 0 {
160
- return "" , nil
215
+ if len (wsa . Spec . Permissions . Permissions ) == 0 {
216
+ return nil , nil
161
217
}
218
+ permissions := wsa .Spec .Permissions .Permissions
219
+ namespace := wsa .GetNamespace ()
162
220
163
221
// Generate a role name based on permissions hash to ensure uniqueness
164
222
permissionsHash := shortHash (fmt .Sprintf ("%+v" , permissions ))
@@ -175,22 +233,48 @@ func createRoleIfNeeded(ctx context.Context, c client.Client, namespace string,
175
233
Rules : permissions ,
176
234
}
177
235
178
- err := c .Create (ctx , role )
236
+ existingRole := & rbacv1.Role {}
237
+ err := i .client .Get (ctx , client .ObjectKeyFromObject (role ), existingRole )
238
+ if err == nil {
239
+ //TODO: Compare existing rules with desired rules and update if necessary
240
+ logger .Info ("Role already exists" , "name" , roleName )
241
+ return existingRole , nil
242
+ }
243
+
244
+ err = i .client .Create (ctx , role )
179
245
if err != nil {
180
246
if errors .IsAlreadyExists (err ) {
181
247
logger .Info ("Role already exists" , "name" , roleName )
182
- return roleName , nil
248
+ return role , nil
183
249
}
184
- return "" , fmt .Errorf ("failed to create Role %s: %w" , roleName , err )
250
+ return nil , fmt .Errorf ("failed to create Role %s: %w" , roleName , err )
185
251
}
186
252
187
253
logger .Info ("Created Role" , "name" , roleName , "permissions" , len (permissions ))
188
- return roleName , nil
254
+ return role , nil
189
255
}
190
256
191
- // createRoleBindings creates RoleBindings to bind the ServiceAccount to Roles and ClusterRoles
192
- func createRoleBindings (ctx context.Context , c client.Client , namespace string , serviceAccountName ServiceAccountName , permissions v1beta1.WorkloadServiceAccountPermissions , generatedRoleName string ) error {
193
- logger := log .FromContext (ctx ).WithName ("createRoleBindings" )
257
+ func (i * InMemoryEngine ) generateRoleBindings (ctx context.Context , wsa v1beta1.WorkloadServiceAccount ) []* rbacv1.RoleBinding {
258
+ logger := log .FromContext (ctx ).WithName ("generateRoleBindings" )
259
+ namespace := wsa .GetNamespace ()
260
+
261
+ if len (wsa .Spec .Permissions .Roles ) != 0 {
262
+ roleRefs := wsa .Spec .Permissions .Roles
263
+
264
+ for _ , roleRef := range roleRefs {
265
+ roleBindingName := fmt .Sprintf ("%s-%s-binding" , wsa .Name , roleRef .Name )
266
+ roleBinding := & rbacv1.RoleBinding {
267
+ ObjectMeta : metav1.ObjectMeta {
268
+ Name : roleBindingName ,
269
+ Namespace : namespace ,
270
+ Labels : map [string ]string {
271
+ PermissionsKey : "enabled" ,
272
+ },
273
+ },
274
+ RoleRef : roleRef ,
275
+ }
276
+ }
277
+ }
194
278
195
279
// Bind to existing Roles
196
280
for _ , roleRef := range permissions .Roles {
0 commit comments