Skip to content

Commit b55f6bc

Browse files
committed
Add finalizer for SA SCCs when they're used and clean up on deletion
Signed-off-by: Angel Misevski <[email protected]>
1 parent 59b5099 commit b55f6bc

File tree

3 files changed

+115
-7
lines changed

3 files changed

+115
-7
lines changed

controllers/workspace/devworkspace_controller.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request
260260

261261
// Set finalizer on DevWorkspace if necessary
262262
// Note: we need to check the flattened workspace to see if a finalizer is needed, as plugins could require storage
263-
if isFinalizerNecessary(workspace, storageProvisioner) {
263+
if storageProvisioner.NeedsStorage(&workspace.Spec.Template) {
264264
coputil.AddFinalizer(clusterWorkspace, storageCleanupFinalizer)
265265
if err := r.Update(ctx, clusterWorkspace); err != nil {
266266
return reconcile.Result{}, err
@@ -367,6 +367,13 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request
367367
}
368368
return reconcile.Result{Requeue: serviceAcctStatus.Requeue}, serviceAcctStatus.Err
369369
}
370+
if wsprovision.NeedsServiceAccountFinalizer(&workspace.Spec.Template) {
371+
coputil.AddFinalizer(clusterWorkspace, serviceAccountCleanupFinalizer)
372+
if err := r.Update(ctx, clusterWorkspace); err != nil {
373+
return reconcile.Result{}, err
374+
}
375+
}
376+
370377
serviceAcctName := serviceAcctStatus.ServiceAccountName
371378
reconcileStatus.setConditionTrue(dw.DevWorkspaceServiceAccountReady, "DevWorkspace serviceaccount ready")
372379

controllers/workspace/finalize.go

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,42 @@ import (
3333
)
3434

3535
const (
36-
storageCleanupFinalizer = "storage.controller.devfile.io"
36+
storageCleanupFinalizer = "storage.controller.devfile.io"
37+
serviceAccountCleanupFinalizer = "serviceaccount.controller.devfile.io"
3738
)
3839

39-
func (r *DevWorkspaceReconciler) finalize(ctx context.Context, log logr.Logger, workspace *dw.DevWorkspace) (reconcile.Result, error) {
40-
if !coputil.HasFinalizer(workspace, storageCleanupFinalizer) {
41-
return reconcile.Result{}, nil
40+
func (r *DevWorkspaceReconciler) workspaceNeedsFinalize(workspace *dw.DevWorkspace) bool {
41+
for _, finalizer := range workspace.Finalizers {
42+
if finalizer == storageCleanupFinalizer {
43+
return true
44+
}
45+
if finalizer == serviceAccountCleanupFinalizer {
46+
return true
47+
}
4248
}
49+
return false
50+
}
51+
52+
func (r *DevWorkspaceReconciler) finalize(ctx context.Context, log logr.Logger, workspace *dw.DevWorkspace) (reconcile.Result, error) {
4353
workspace.Status.Message = "Cleaning up resources for deletion"
4454
workspace.Status.Phase = devworkspacePhaseTerminating
4555
err := r.Client.Status().Update(ctx, workspace)
4656
if err != nil && !k8sErrors.IsConflict(err) {
4757
return reconcile.Result{}, err
4858
}
4959

60+
for _, finalizer := range workspace.Finalizers {
61+
switch finalizer {
62+
case storageCleanupFinalizer:
63+
return r.finalizeStorage(ctx, log, workspace)
64+
case serviceAccountCleanupFinalizer:
65+
return r.finalizeServiceAccount(ctx, log, workspace)
66+
}
67+
}
68+
return reconcile.Result{}, nil
69+
}
70+
71+
func (r *DevWorkspaceReconciler) finalizeStorage(ctx context.Context, log logr.Logger, workspace *dw.DevWorkspace) (reconcile.Result, error) {
5072
// Need to make sure Deployment is cleaned up before starting job to avoid mounting issues for RWO PVCs
5173
wait, err := wsprovision.DeleteWorkspaceDeployment(ctx, workspace, r.Client)
5274
if err != nil {
@@ -98,8 +120,20 @@ func (r *DevWorkspaceReconciler) finalize(ctx context.Context, log logr.Logger,
98120
return reconcile.Result{}, r.Update(ctx, workspace)
99121
}
100122

101-
func isFinalizerNecessary(workspace *dw.DevWorkspace, provisioner storage.Provisioner) bool {
102-
return provisioner.NeedsStorage(&workspace.Spec.Template)
123+
func (r *DevWorkspaceReconciler) finalizeServiceAccount(ctx context.Context, log logr.Logger, workspace *dw.DevWorkspace) (reconcile.Result, error) {
124+
retry, err := wsprovision.FinalizeServiceAccount(workspace, ctx, r.NonCachingClient)
125+
if err != nil {
126+
log.Error(err, "Failed to finalize workspace ServiceAccount")
127+
failedStatus := currentStatus{phase: dw.DevWorkspaceStatusError}
128+
failedStatus.setConditionTrue(dw.DevWorkspaceError, err.Error())
129+
return r.updateWorkspaceStatus(workspace, r.Log, &failedStatus, reconcile.Result{}, nil)
130+
}
131+
if retry {
132+
return reconcile.Result{Requeue: true}, nil
133+
}
134+
log.Info("ServiceAccount clean up successful; clearing finalizer")
135+
coputil.RemoveFinalizer(workspace, serviceAccountCleanupFinalizer)
136+
return reconcile.Result{}, r.Update(ctx, workspace)
103137
}
104138

105139
func (r *DevWorkspaceReconciler) namespaceIsTerminating(ctx context.Context, namespace string) (bool, error) {

pkg/provision/workspace/serviceaccount.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package workspace
1717

1818
import (
19+
"context"
1920
"fmt"
2021

2122
dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
@@ -26,6 +27,7 @@ import (
2627
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
2728
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2829
"k8s.io/apimachinery/pkg/types"
30+
crclient "sigs.k8s.io/controller-runtime/pkg/client"
2931
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3032

3133
"github.com/devfile/devworkspace-operator/pkg/common"
@@ -103,6 +105,27 @@ func SyncServiceAccount(
103105
}
104106
}
105107

108+
func NeedsServiceAccountFinalizer(workspace *dw.DevWorkspaceTemplateSpec) bool {
109+
if !workspace.Attributes.Exists(constants.WorkspaceSCCAttribute) {
110+
return false
111+
}
112+
if workspace.Attributes.GetString(constants.WorkspaceSCCAttribute, nil) == "" {
113+
return false
114+
}
115+
return true
116+
}
117+
118+
func FinalizeServiceAccount(workspace *dw.DevWorkspace, ctx context.Context, nonCachingClient crclient.Client) (retry bool, err error) {
119+
saName := common.ServiceAccountName(workspace.Status.DevWorkspaceId)
120+
namespace := workspace.Namespace
121+
if !workspace.Spec.Template.Attributes.Exists(constants.WorkspaceSCCAttribute) {
122+
return false, nil
123+
}
124+
sccName := workspace.Spec.Template.Attributes.GetString(constants.WorkspaceSCCAttribute, nil)
125+
126+
return removeSCCFromServiceAccount(saName, namespace, sccName, ctx, nonCachingClient)
127+
}
128+
106129
func addSCCToServiceAccount(saName, namespace, sccName string, clusterAPI sync.ClusterAPI) (retry bool, err error) {
107130
serviceaccount := fmt.Sprintf("system:serviceaccount:%s:%s", namespace, saName)
108131

@@ -139,3 +162,47 @@ func addSCCToServiceAccount(saName, namespace, sccName string, clusterAPI sync.C
139162

140163
return false, nil
141164
}
165+
166+
func removeSCCFromServiceAccount(saName, namespace, sccName string, ctx context.Context, nonCachingClient crclient.Client) (retry bool, err error) {
167+
serviceaccount := fmt.Sprintf("system:serviceaccount:%s:%s", namespace, saName)
168+
169+
scc := &securityv1.SecurityContextConstraints{}
170+
if err := nonCachingClient.Get(ctx, types.NamespacedName{Name: sccName}, scc); err != nil {
171+
switch {
172+
case k8sErrors.IsForbidden(err):
173+
return false, fmt.Errorf("operator does not have permissions to get the '%s' SecurityContextConstraints", sccName)
174+
case k8sErrors.IsNotFound(err):
175+
return false, fmt.Errorf("requested SecurityContextConstraints '%s' not found on cluster", sccName)
176+
default:
177+
return false, err
178+
}
179+
}
180+
181+
found := false
182+
var newUsers []string
183+
for _, user := range scc.Users {
184+
if user == serviceaccount {
185+
found = true
186+
continue
187+
}
188+
newUsers = append(newUsers, user)
189+
}
190+
if !found {
191+
return false, err
192+
}
193+
194+
scc.Users = newUsers
195+
196+
if err := nonCachingClient.Update(ctx, scc); err != nil {
197+
switch {
198+
case k8sErrors.IsForbidden(err):
199+
return false, fmt.Errorf("operator does not have permissions to update the '%s' SecurityContextConstraints", sccName)
200+
case k8sErrors.IsConflict(err):
201+
return true, nil
202+
default:
203+
return false, err
204+
}
205+
}
206+
207+
return false, nil
208+
}

0 commit comments

Comments
 (0)