Skip to content

Commit e1269d6

Browse files
committed
Extend kube component webhooks to DevWorkspaceTemplates as well
Add webhook checks to prevent creation/mutation of DevWorkspaceTemplates that define Kubernetes components if the requesting user does not have those permissions. This prevents working around RBAC checks in DevWorkspaces by just creating a DevWorkspaceTemplate instead. Note however that currently DWO does not check that a user creating a workspace has permissions to reference this DevWorkspaceTemplate -- if a user can reference a DWT, they will be able to create the objects defined in that DWT via a DevWorkspace without further RBAC verification. Signed-off-by: Angel Misevski <[email protected]>
1 parent 91c7c50 commit e1269d6

File tree

3 files changed

+113
-12
lines changed

3 files changed

+113
-12
lines changed

webhook/workspace/handler/kubernetes.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
"sigs.k8s.io/yaml"
2727
)
2828

29-
func (h *WebhookHandler) validateKubernetesObjectPermissionsOnCreate(ctx context.Context, req admission.Request, wksp *dwv2.DevWorkspace) error {
29+
func (h *WebhookHandler) validateKubernetesObjectPermissionsOnCreate(ctx context.Context, req admission.Request, wksp *dwv2.DevWorkspaceTemplateSpec) error {
3030
kubeComponents := getKubeComponentsFromWorkspace(wksp)
3131
for componentName, component := range kubeComponents {
3232
if component.Uri != "" {
@@ -42,7 +42,7 @@ func (h *WebhookHandler) validateKubernetesObjectPermissionsOnCreate(ctx context
4242
return nil
4343
}
4444

45-
func (h *WebhookHandler) validateKubernetesObjectPermissionsOnUpdate(ctx context.Context, req admission.Request, newWksp, oldWksp *dwv2.DevWorkspace) error {
45+
func (h *WebhookHandler) validateKubernetesObjectPermissionsOnUpdate(ctx context.Context, req admission.Request, newWksp, oldWksp *dwv2.DevWorkspaceTemplateSpec) error {
4646
newKubeComponents := getKubeComponentsFromWorkspace(newWksp)
4747
oldKubeComponents := getKubeComponentsFromWorkspace(oldWksp)
4848

@@ -145,9 +145,9 @@ func (h *WebhookHandler) validatePermissionsOnObject(ctx context.Context, req ad
145145

146146
// getKubeComponentsFromWorkspace returns the Kubernetes (and OpenShift) components in a workspace
147147
// in a map with component names as keys.
148-
func getKubeComponentsFromWorkspace(wksp *dwv2.DevWorkspace) map[string]dwv2.K8sLikeComponent {
148+
func getKubeComponentsFromWorkspace(wksp *dwv2.DevWorkspaceTemplateSpec) map[string]dwv2.K8sLikeComponent {
149149
kubeComponents := map[string]dwv2.K8sLikeComponent{}
150-
for _, component := range wksp.Spec.Template.Components {
150+
for _, component := range wksp.Components {
151151
kubeComponent, err := getKubeLikeComponent(&component)
152152
if err != nil {
153153
continue
@@ -170,7 +170,7 @@ func getKubeLikeComponent(component *dwv2.Component) (*dwv2.K8sLikeComponent, er
170170
return nil, fmt.Errorf("component does not specify kubernetes or openshift fields")
171171
}
172172

173-
func (h *WebhookHandler) validateKubernetesObjectPermissionsOnCreate_v1alpha1(ctx context.Context, req admission.Request, wksp *dwv1.DevWorkspace) error {
173+
func (h *WebhookHandler) validateKubernetesObjectPermissionsOnCreate_v1alpha1(ctx context.Context, req admission.Request, wksp *dwv1.DevWorkspaceTemplateSpec) error {
174174
kubeComponents := getKubeComponentsFromWorkspace_v1alpha1(wksp)
175175
for componentName, component := range kubeComponents {
176176
if component.Uri != "" {
@@ -186,7 +186,7 @@ func (h *WebhookHandler) validateKubernetesObjectPermissionsOnCreate_v1alpha1(ct
186186
return nil
187187
}
188188

189-
func (h *WebhookHandler) validateKubernetesObjectPermissionsOnUpdate_v1alpha1(ctx context.Context, req admission.Request, newWksp, oldWksp *dwv1.DevWorkspace) error {
189+
func (h *WebhookHandler) validateKubernetesObjectPermissionsOnUpdate_v1alpha1(ctx context.Context, req admission.Request, newWksp, oldWksp *dwv1.DevWorkspaceTemplateSpec) error {
190190
newKubeComponents := getKubeComponentsFromWorkspace_v1alpha1(newWksp)
191191
oldKubeComponents := getKubeComponentsFromWorkspace_v1alpha1(oldWksp)
192192

@@ -209,9 +209,9 @@ func (h *WebhookHandler) validateKubernetesObjectPermissionsOnUpdate_v1alpha1(ct
209209
return nil
210210
}
211211

212-
func getKubeComponentsFromWorkspace_v1alpha1(wksp *dwv1.DevWorkspace) map[string]dwv1.K8sLikeComponent {
212+
func getKubeComponentsFromWorkspace_v1alpha1(wksp *dwv1.DevWorkspaceTemplateSpec) map[string]dwv1.K8sLikeComponent {
213213
kubeComponents := map[string]dwv1.K8sLikeComponent{}
214-
for _, component := range wksp.Spec.Template.Components {
214+
for _, component := range wksp.Components {
215215
kubeComponent, err := getKubeLikeComponent_v1alpha1(&component)
216216
if err != nil {
217217
continue
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright (c) 2019-2022 Red Hat, Inc.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package handler
15+
16+
import (
17+
"context"
18+
"fmt"
19+
"net/http"
20+
21+
dwv1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha1"
22+
dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
23+
"github.com/devfile/devworkspace-operator/pkg/constants"
24+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
25+
)
26+
27+
func (h *WebhookHandler) MutateWorkspaceTemplateV1alpha1OnCreate(ctx context.Context, req admission.Request) admission.Response {
28+
wksp := &dwv1.DevWorkspaceTemplate{}
29+
err := h.Decoder.Decode(req, wksp)
30+
if err != nil {
31+
return admission.Errored(http.StatusBadRequest, err)
32+
}
33+
34+
if err := h.validateKubernetesObjectPermissionsOnCreate_v1alpha1(ctx, req, &wksp.Spec); err != nil {
35+
return admission.Denied(err.Error())
36+
}
37+
38+
return h.returnPatched(req, wksp)
39+
}
40+
41+
func (h *WebhookHandler) MutateWorkspaceTemplateV1alpha2OnCreate(ctx context.Context, req admission.Request) admission.Response {
42+
wksp := &dwv2.DevWorkspaceTemplate{}
43+
err := h.Decoder.Decode(req, wksp)
44+
if err != nil {
45+
return admission.Errored(http.StatusBadRequest, err)
46+
}
47+
48+
if err := h.validateKubernetesObjectPermissionsOnCreate(ctx, req, &wksp.Spec); err != nil {
49+
return admission.Denied(err.Error())
50+
}
51+
52+
return h.returnPatched(req, wksp)
53+
}
54+
55+
func (h *WebhookHandler) MutateWorkspaceTemplateV1alpha1OnUpdate(ctx context.Context, req admission.Request) admission.Response {
56+
newWksp := &dwv1.DevWorkspaceTemplate{}
57+
oldWksp := &dwv1.DevWorkspaceTemplate{}
58+
err := h.parse(req, oldWksp, newWksp)
59+
if err != nil {
60+
return admission.Errored(http.StatusBadRequest, err)
61+
}
62+
63+
if err := h.validateKubernetesObjectPermissionsOnUpdate_v1alpha1(ctx, req, &newWksp.Spec, &oldWksp.Spec); err != nil {
64+
return admission.Denied(err.Error())
65+
}
66+
67+
oldCreator, found := oldWksp.Labels[constants.DevWorkspaceCreatorLabel]
68+
if !found {
69+
return admission.Denied(fmt.Sprintf("label '%s' is missing. Please recreate devworkspace to get it initialized", constants.DevWorkspaceCreatorLabel))
70+
}
71+
72+
newCreator, found := newWksp.Labels[constants.DevWorkspaceCreatorLabel]
73+
if !found {
74+
if newWksp.Labels == nil {
75+
newWksp.Labels = map[string]string{}
76+
}
77+
newWksp.Labels[constants.DevWorkspaceCreatorLabel] = oldCreator
78+
return h.returnPatched(req, newWksp)
79+
}
80+
81+
if newCreator != oldCreator {
82+
return admission.Denied(fmt.Sprintf("label '%s' is assigned once devworkspace is created and is immutable", constants.DevWorkspaceCreatorLabel))
83+
}
84+
85+
return admission.Allowed("new devworkspace has the same devworkspace creator as old one")
86+
}
87+
88+
func (h *WebhookHandler) MutateWorkspaceTemplateV1alpha2OnUpdate(ctx context.Context, req admission.Request) admission.Response {
89+
newWksp := &dwv2.DevWorkspaceTemplate{}
90+
oldWksp := &dwv2.DevWorkspaceTemplate{}
91+
err := h.parse(req, oldWksp, newWksp)
92+
if err != nil {
93+
return admission.Errored(http.StatusBadRequest, err)
94+
}
95+
96+
if err := h.validateKubernetesObjectPermissionsOnUpdate(ctx, req, &newWksp.Spec, &oldWksp.Spec); err != nil {
97+
return admission.Denied(err.Error())
98+
}
99+
100+
return admission.Allowed("new workspace has the same devworkspace as old one")
101+
}

webhook/workspace/handler/workspace.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (h *WebhookHandler) MutateWorkspaceV1alpha1OnCreate(ctx context.Context, re
3838

3939
wksp.Labels = maputils.Append(wksp.Labels, constants.DevWorkspaceCreatorLabel, req.UserInfo.UID)
4040

41-
if err := h.validateKubernetesObjectPermissionsOnCreate_v1alpha1(ctx, req, wksp); err != nil {
41+
if err := h.validateKubernetesObjectPermissionsOnCreate_v1alpha1(ctx, req, &wksp.Spec.Template); err != nil {
4242
return admission.Denied(err.Error())
4343
}
4444

@@ -58,7 +58,7 @@ func (h *WebhookHandler) MutateWorkspaceV1alpha2OnCreate(ctx context.Context, re
5858
return admission.Denied(err.Error())
5959
}
6060

61-
if err := h.validateKubernetesObjectPermissionsOnCreate(ctx, req, wksp); err != nil {
61+
if err := h.validateKubernetesObjectPermissionsOnCreate(ctx, req, &wksp.Spec.Template); err != nil {
6262
return admission.Denied(err.Error())
6363
}
6464

@@ -86,7 +86,7 @@ func (h *WebhookHandler) MutateWorkspaceV1alpha1OnUpdate(ctx context.Context, re
8686
return admission.Denied(msg)
8787
}
8888

89-
if err := h.validateKubernetesObjectPermissionsOnUpdate_v1alpha1(ctx, req, newWksp, oldWksp); err != nil {
89+
if err := h.validateKubernetesObjectPermissionsOnUpdate_v1alpha1(ctx, req, &newWksp.Spec.Template, &oldWksp.Spec.Template); err != nil {
9090
return admission.Denied(err.Error())
9191
}
9292

@@ -166,7 +166,7 @@ func (h *WebhookHandler) MutateWorkspaceV1alpha2OnUpdate(ctx context.Context, re
166166
return admission.Denied(err.Error())
167167
}
168168

169-
if err := h.validateKubernetesObjectPermissionsOnUpdate(ctx, req, newWksp, oldWksp); err != nil {
169+
if err := h.validateKubernetesObjectPermissionsOnUpdate(ctx, req, &newWksp.Spec.Template, &oldWksp.Spec.Template); err != nil {
170170
return admission.Denied(err.Error())
171171
}
172172

0 commit comments

Comments
 (0)