Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update SCC and RBAC handling for DevWorkspaces #954

Merged
merged 10 commits into from
Nov 7, 2022
33 changes: 22 additions & 11 deletions controllers/workspace/devworkspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/devfile/devworkspace-operator/pkg/provision/storage"
"github.com/devfile/devworkspace-operator/pkg/provision/sync"
wsprovision "github.com/devfile/devworkspace-operator/pkg/provision/workspace"
"github.com/devfile/devworkspace-operator/pkg/provision/workspace/rbac"
"github.com/devfile/devworkspace-operator/pkg/timing"
"github.com/go-logr/logr"
"github.com/google/uuid"
Expand Down Expand Up @@ -85,7 +86,8 @@ type DevWorkspaceReconciler struct {
// +kubebuilder:rbac:groups="batch",resources=jobs,verbs=get;create;list;watch;update;patch;delete
// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=mutatingwebhookconfigurations;validatingwebhookconfigurations,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=authorization.k8s.io,resources=subjectaccessreviews;localsubjectaccessreviews,verbs=create
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings;clusterroles;clusterrolebindings,verbs=get;list;watch;create;update
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=get;list;watch;create;update
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=get;list;watch;create;update;delete
// +kubebuilder:rbac:groups=oauth.openshift.io,resources=oauthclients,verbs=get;list;watch;create;update;patch;delete;deletecollection
// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;create
// +kubebuilder:rbac:groups=config.openshift.io,resources=proxies,verbs=get,resourceNames=cluster
Expand Down Expand Up @@ -285,7 +287,7 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request

// Set finalizer on DevWorkspace if necessary
// Note: we need to check the flattened workspace to see if a finalizer is needed, as plugins could require storage
if storageProvisioner.NeedsStorage(&workspace.Spec.Template) {
if storageProvisioner.NeedsStorage(&workspace.Spec.Template) && !coputil.HasFinalizer(clusterWorkspace, constants.StorageCleanupFinalizer) {
coputil.AddFinalizer(clusterWorkspace, constants.StorageCleanupFinalizer)
if err := r.Update(ctx, clusterWorkspace.DevWorkspace); err != nil {
return reconcile.Result{}, err
Expand Down Expand Up @@ -340,9 +342,24 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request

timing.SetTime(timingInfo, timing.ComponentsReady)

rbacStatus := wsprovision.SyncRBAC(workspace, clusterAPI)
if rbacStatus.Err != nil || !rbacStatus.Continue {
return reconcile.Result{Requeue: true}, rbacStatus.Err
// Add finalizer to ensure workspace rolebinding gets cleaned up when workspace
// is deleted.
if !coputil.HasFinalizer(clusterWorkspace, constants.RBACCleanupFinalizer) {
coputil.AddFinalizer(clusterWorkspace, constants.RBACCleanupFinalizer)
if err := r.Update(ctx, clusterWorkspace.DevWorkspace); err != nil {
return reconcile.Result{}, err
}
}
if err := rbac.SyncRBAC(workspace, clusterAPI); err != nil {
switch rbacErr := err.(type) {
case *rbac.RetryError:
reqLogger.Info(rbacErr.Error())
return reconcile.Result{Requeue: true}, nil
case *rbac.FailError:
return r.failWorkspace(workspace, fmt.Sprintf("Error provisioning rbac: %s", rbacErr), metrics.ReasonInfrastructureFailure, reqLogger, &reconcileStatus)
default:
return reconcile.Result{}, err
}
}

// Step two: Create routing, and wait for routing to be ready
Expand Down Expand Up @@ -431,12 +448,6 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request
}
return reconcile.Result{Requeue: serviceAcctStatus.Requeue}, serviceAcctStatus.Err
}
if wsprovision.NeedsServiceAccountFinalizer(&workspace.Spec.Template) {
coputil.AddFinalizer(clusterWorkspace, constants.ServiceAccountCleanupFinalizer)
if err := r.Update(ctx, clusterWorkspace.DevWorkspace); err != nil {
return reconcile.Result{}, err
}
}
serviceAcctName = serviceAcctStatus.ServiceAccountName
reconcileStatus.setConditionTrue(dw.DevWorkspaceServiceAccountReady, "DevWorkspace serviceaccount ready")
}
Expand Down
43 changes: 43 additions & 0 deletions controllers/workspace/finalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/devworkspace-operator/pkg/provision/sync"
"github.com/devfile/devworkspace-operator/pkg/provision/workspace/rbac"

"github.com/go-logr/logr"
coputil "github.com/redhat-cop/operator-utils/pkg/util"
Expand Down Expand Up @@ -70,6 +71,8 @@ func (r *DevWorkspaceReconciler) finalize(ctx context.Context, log logr.Logger,
return r.finalizeStorage(ctx, log, workspace, finalizeStatus)
case constants.ServiceAccountCleanupFinalizer:
return r.finalizeServiceAccount(ctx, log, workspace, finalizeStatus)
case constants.RBACCleanupFinalizer:
return r.finalizeRBAC(ctx, log, workspace, finalizeStatus)
}
}
return reconcile.Result{}, nil
Expand Down Expand Up @@ -130,6 +133,46 @@ func (r *DevWorkspaceReconciler) finalizeStorage(ctx context.Context, log logr.L
return reconcile.Result{}, r.Update(ctx, workspace.DevWorkspace)
}

func (r *DevWorkspaceReconciler) finalizeRBAC(ctx context.Context, log logr.Logger, workspace *common.DevWorkspaceWithConfig, finalizeStatus *currentStatus) (reconcile.Result, error) {
terminating, err := r.namespaceIsTerminating(ctx, workspace.Namespace)
if err != nil {
return reconcile.Result{}, err
} else if terminating {
// Namespace is terminating, it's redundant to update roles/rolebindings since they will be removed with the workspace
log.Info("Namespace is terminating; clearing storage finalizer")
coputil.RemoveFinalizer(workspace, constants.RBACCleanupFinalizer)
return reconcile.Result{}, r.Update(ctx, workspace.DevWorkspace)
}

if err := rbac.FinalizeRBAC(workspace, sync.ClusterAPI{
Ctx: ctx,
Client: r.Client,
Scheme: r.Scheme,
Logger: log,
}); err != nil {
switch rbacErr := err.(type) {
case *rbac.RetryError:
log.Info(rbacErr.Error())
return reconcile.Result{Requeue: true}, nil
case *rbac.FailError:
if workspace.Status.Phase != dw.DevWorkspaceStatusError {
// Avoid repeatedly logging error unless it's relevant
log.Error(rbacErr, "Failed to finalize workspace RBAC")
}
finalizeStatus.phase = dw.DevWorkspaceStatusError
finalizeStatus.setConditionTrue(dw.DevWorkspaceError, err.Error())
return reconcile.Result{}, nil
default:
return reconcile.Result{}, err
}
}
log.Info("RBAC cleanup successful; clearing finalizer")
coputil.RemoveFinalizer(workspace, constants.RBACCleanupFinalizer)
return reconcile.Result{}, r.Update(ctx, workspace.DevWorkspace)
}

// Deprecated: Only required to support old workspaces that use the service account finalizer. The service account finalizer should
// not be added to new workspaces.
func (r *DevWorkspaceReconciler) finalizeServiceAccount(ctx context.Context, log logr.Logger, workspace *common.DevWorkspaceWithConfig, finalizeStatus *currentStatus) (reconcile.Result, error) {
retry, err := wsprovision.FinalizeServiceAccount(workspace, ctx, r.NonCachingClient)
if err != nil {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions deploy/deployment/kubernetes/combined.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions deploy/deployment/openshift/combined.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions deploy/templates/components/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,20 @@ rules:
resources:
- clusterrolebindings
- clusterroles
verbs:
- create
- get
- list
- update
- watch
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
- roles
verbs:
- create
- delete
- get
- list
- update
Expand Down
10 changes: 6 additions & 4 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package cache
import (
"fmt"

"github.com/devfile/devworkspace-operator/pkg/common"
"github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
routev1 "github.com/openshift/api/route/v1"
Expand All @@ -25,7 +24,6 @@ import (
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/cache"
)
Expand All @@ -48,6 +46,10 @@ func GetCacheFunc() (cache.NewCacheFunc, error) {
if err != nil {
return nil, err
}
rbacObjectSelector, err := labels.Parse("controller.devfile.io/workspace-rbac=true")
if err != nil {
return nil, err
}

selectors := cache.SelectorsByObject{
&appsv1.Deployment{}: {
Expand Down Expand Up @@ -75,10 +77,10 @@ func GetCacheFunc() (cache.NewCacheFunc, error) {
Label: secretObjectSelector,
},
&rbacv1.Role{}: {
Field: fields.SelectorFromSet(fields.Set{"metadata.name": common.WorkspaceRoleName()}),
Label: rbacObjectSelector,
},
&rbacv1.RoleBinding{}: {
Field: fields.SelectorFromSet(fields.Set{"metadata.name": common.WorkspaceRolebindingName()}),
Label: rbacObjectSelector,
},
}

Expand Down
32 changes: 28 additions & 4 deletions pkg/common/naming.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,21 +119,45 @@ func MetadataConfigMapName(workspaceId string) string {
// can potentially push the name over the 63 character limit (if the original
// object has a long name)
func AutoMountConfigMapVolumeName(volumeName string) string {
return fmt.Sprintf("%s", volumeName)
return volumeName
}

func AutoMountSecretVolumeName(volumeName string) string {
return fmt.Sprintf("%s", volumeName)
return volumeName
}

func AutoMountPVCVolumeName(pvcName string) string {
return fmt.Sprintf("%s", pvcName)
return pvcName
}

func WorkspaceRoleName() string {
return "workspace"
return "devworkspace-default-role"
}

func WorkspaceRolebindingName() string {
return "devworkspace-default-rolebinding"
}

func WorkspaceSCCRoleName(sccName string) string {
return fmt.Sprintf("devworkspace-use-%s", sccName)
}

func WorkspaceSCCRolebindingName(sccName string) string {
return fmt.Sprintf("devworkspace-use-%s", sccName)
}

// OldWorkspaceRoleName returns the name used for the workspace serviceaccount role
//
// Deprecated: use WorkspaceRoleName() instead.
// TODO: remove for DevWorkspace Operator v0.19
func OldWorkspaceRoleName() string {
return "workspace"
}

// OldWorkspaceRolebindingName returns the name used for the workspace serviceaccount rolebinding
//
// Deprecated: use WorkspaceRoleBindingName() instead.
// TODO: remove for DevWorkspace Operator v0.19
func OldWorkspaceRolebindingName() string {
return constants.ServiceAccount + "dw"
}
8 changes: 8 additions & 0 deletions pkg/constants/finalizers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,13 @@ const (
// ServiceAccountCleanupFinalizer is used to block DevWorkspace deletion when it is
// necessary to clean up additional non-workspace roles added to the workspace
// serviceaccount
//
// Deprecated: Will not be added to new workspaces but needs to be tracked for
// removal to ensure workspaces that used it previously will be cleaned up.
ServiceAccountCleanupFinalizer = "serviceaccount.controller.devfile.io"
// RBACCleanupFinalizer is used to block DevWorkspace deletion in order to ensure
// the workspace role and rolebinding are cleaned up correctly. Since each workspace
// serviceaccount is added to the workspace rolebinding, it is necessary to remove it
// when a workspace is deleted
RBACCleanupFinalizer = "rbac.controller.devfile.io"
)
Loading