Skip to content

Commit c880c8d

Browse files
committed
exclude pwo identity from validation rules
1 parent 00b5db0 commit c880c8d

File tree

9 files changed

+102
-96
lines changed

9 files changed

+102
-96
lines changed

api/core/v1alpha1/common_webhook.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package v1alpha1
33
import (
44
"context"
55
"fmt"
6-
"os"
7-
"strings"
86

97
admissionv1 "k8s.io/api/admission/v1"
108
authv1 "k8s.io/api/authentication/v1"
@@ -59,11 +57,6 @@ func setMetaDataAnnotation(meta metav1.Object, key, value string) { // TODO move
5957
meta.SetAnnotations(labels)
6058
}
6159

62-
func isOwnServiceAccount(userinfo authv1.UserInfo) bool {
63-
svcAccUsername := fmt.Sprintf("system:serviceaccount:%s:%s", os.Getenv("POD_NAMESPACE"), os.Getenv("POD_SERVICE_ACCOUNT"))
64-
return strings.HasSuffix(userinfo.Username, svcAccUsername)
65-
}
66-
6760
// userInfoFromContext extracts the authv1.UserInfo from the admission.Request available in the context. Returns an error if the request can't be found.
6861
func userInfoFromContext(ctx context.Context) (authv1.UserInfo, error) {
6962
req, err := admission.RequestFromContext(ctx)

api/core/v1alpha1/project_webhook.go

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,39 @@ import (
1313
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
1414
)
1515

16+
// +kubebuilder:object:generate=false
17+
type ProjectWebhook struct {
18+
client.Client
19+
20+
// Identity is the name of the entity (usually a service account) the project-workspace-operator uses to access the onboarding cluster.
21+
// It is required to exclude the operator's own identity from validation checks.
22+
Identity string
23+
OverrideName string
24+
}
25+
1626
// log is for logging in this package.
1727
var projectlog = logf.Log.WithName("project-resource")
1828

19-
func (p *Project) SetupWebhookWithManager(mgr ctrl.Manager, memberOverridesName string) error {
29+
func (p *Project) SetupWebhookWithManager(ctx context.Context, mgr ctrl.Manager, memberOverridesName, identity string) error {
30+
pwh := &ProjectWebhook{
31+
Client: mgr.GetClient(),
32+
OverrideName: memberOverridesName,
33+
Identity: identity,
34+
}
2035

2136
return ctrl.NewWebhookManagedBy(mgr).
22-
For(&Project{}).
23-
WithDefaulter(&Project{}).
24-
WithValidator(&projectValidator{
25-
Client: mgr.GetClient(),
26-
overrideName: memberOverridesName,
27-
}).
37+
For(p).
38+
WithDefaulter(pwh).
39+
WithValidator(pwh).
2840
Complete()
2941
}
3042

31-
// +kubebuilder:webhook:path=/mutate-core-openmcp-cloud-v1alpha1-project,mutating=true,failurePolicy=fail,sideEffects=None,groups=core.openmcp.cloud,resources=projects,verbs=create;update,versions=v1alpha1,name=mproject.kb.io,admissionReviewVersions=v1
43+
// +kubebuilder:webhook:path=/mutate-core-openmcp-cloud-v1alpha1-project,mutating=true,failurePolicy=fail,sideEffects=None,groups=core.openmcp.cloud,resources=projects,verbs=create;update,versions=v1alpha1,name=mproject.openmcp.cloud,admissionReviewVersions=v1
3244

33-
var _ webhook.CustomDefaulter = &Project{}
45+
var _ webhook.CustomDefaulter = &ProjectWebhook{}
3446

3547
// Default implements webhook.CustomDefaulter so a webhook will be registered for the type
36-
func (p *Project) Default(ctx context.Context, obj runtime.Object) error {
48+
func (p *ProjectWebhook) Default(ctx context.Context, obj runtime.Object) error {
3749
project, err := expectProject(obj)
3850
if err != nil {
3951
return err
@@ -50,30 +62,12 @@ func (p *Project) Default(ctx context.Context, obj runtime.Object) error {
5062
return nil
5163
}
5264

53-
// Project must implement webhook.CustomValidator in order for it's Mutating/Validating Webhook configuration to be generated by "github.com/openmcp-project/controller-utils/pkg/init/webhooks"
54-
var _ webhook.CustomValidator = &Project{}
55-
56-
func (p *Project) ValidateCreate(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
57-
return
58-
}
59-
func (p *Project) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) {
60-
return
61-
}
62-
func (p *Project) ValidateDelete(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
63-
return
64-
}
65-
66-
// +kubebuilder:webhook:path=/validate-core-openmcp-cloud-v1alpha1-project,mutating=false,failurePolicy=fail,sideEffects=None,groups=core.openmcp.cloud,resources=projects,verbs=create;update;delete,versions=v1alpha1,name=vproject.kb.io,admissionReviewVersions=v1
67-
68-
var _ webhook.CustomValidator = &projectValidator{}
65+
// +kubebuilder:webhook:path=/validate-core-openmcp-cloud-v1alpha1-project,mutating=false,failurePolicy=fail,sideEffects=None,groups=core.openmcp.cloud,resources=projects,verbs=create;update;delete,versions=v1alpha1,name=vproject.openmcp.cloud,admissionReviewVersions=v1
6966

70-
type projectValidator struct {
71-
client.Client
72-
overrideName string
73-
}
67+
var _ webhook.CustomValidator = &ProjectWebhook{}
7468

7569
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type
76-
func (v *projectValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
70+
func (v *ProjectWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
7771
project, err := expectProject(obj)
7872
if err != nil {
7973
return
@@ -97,7 +91,7 @@ func (v *projectValidator) ValidateCreate(ctx context.Context, obj runtime.Objec
9791
}
9892

9993
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type
100-
func (v *projectValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) {
94+
func (v *ProjectWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) {
10195
oldProject, err := expectProject(oldObj)
10296
if err != nil {
10397
return
@@ -138,7 +132,7 @@ func (v *projectValidator) ValidateUpdate(ctx context.Context, oldObj, newObj ru
138132
}
139133

140134
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type
141-
func (v *projectValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
135+
func (v *ProjectWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
142136
project, err := expectProject(obj)
143137
if err != nil {
144138
return
@@ -160,21 +154,21 @@ func expectProject(obj runtime.Object) (*Project, error) {
160154
return project, nil
161155
}
162156

163-
func (v *projectValidator) ensureValidRole(ctx context.Context, project *Project) (bool, error) {
157+
func (v *ProjectWebhook) ensureValidRole(ctx context.Context, project *Project) (bool, error) {
164158
userInfo, err := userInfoFromContext(ctx)
165159
if err != nil {
166160
return false, fmt.Errorf("failed to get userInfo")
167161
}
168-
if project.UserInfoHasRole(userInfo, ProjectRoleAdmin) || isOwnServiceAccount(userInfo) {
162+
if project.UserInfoHasRole(userInfo, ProjectRoleAdmin) || userInfo.Username == v.Identity {
169163
return true, nil
170164
}
171165

172-
if v.overrideName == "" {
166+
if v.OverrideName == "" {
173167
return false, nil
174168
}
175169

176170
overrides := &MemberOverrides{}
177-
if err := v.Get(ctx, types.NamespacedName{Name: v.overrideName}, overrides); err != nil {
171+
if err := v.Get(ctx, types.NamespacedName{Name: v.OverrideName}, overrides); err != nil {
178172
return false, err
179173
}
180174
if overrides.HasAdminOverrideForResource(&userInfo, project.Name, project.Kind) {

api/core/v1alpha1/webhook_suite_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ var _ = BeforeSuite(func() {
9292
Expect(err).NotTo(HaveOccurred())
9393
Expect(k8sClient).NotTo(BeNil())
9494

95+
identity := "nobody"
96+
9597
// start webhook server using Manager
9698
webhookInstallOptions := &testEnv.WebhookInstallOptions
9799
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
@@ -106,10 +108,10 @@ var _ = BeforeSuite(func() {
106108
})
107109
Expect(err).NotTo(HaveOccurred())
108110

109-
err = (&Project{}).SetupWebhookWithManager(mgr, "test-override")
111+
err = (&Project{}).SetupWebhookWithManager(ctx, mgr, "test-override", identity)
110112
Expect(err).NotTo(HaveOccurred())
111113

112-
err = (&Workspace{}).SetupWebhookWithManager(mgr, "test-override")
114+
err = (&Workspace{}).SetupWebhookWithManager(ctx, mgr, "test-override", identity)
113115
Expect(err).NotTo(HaveOccurred())
114116

115117
// +kubebuilder:scaffold:webhook

api/core/v1alpha1/workspace_webhook.go

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,36 @@ import (
1717
// log is for logging in this package.
1818
var workspacelog = logf.Log.WithName("workspace-resource")
1919

20-
func (r *Workspace) SetupWebhookWithManager(mgr ctrl.Manager, memberOverridesName string) error {
20+
// +kubebuilder:object:generate=false
21+
type WorkspaceWebhook struct {
22+
client.Client
23+
24+
// Identity is the name of the entity (usually a service account) the project-workspace-operator uses to access the onboarding cluster.
25+
// It is required to exclude the operator's own identity from validation checks.
26+
Identity string
27+
OverrideName string
28+
}
29+
30+
func (r *Workspace) SetupWebhookWithManager(ctx context.Context, mgr ctrl.Manager, memberOverridesName, identity string) error {
31+
wswh := &WorkspaceWebhook{
32+
Client: mgr.GetClient(),
33+
OverrideName: memberOverridesName,
34+
Identity: identity,
35+
}
2136

2237
return ctrl.NewWebhookManagedBy(mgr).
2338
For(r).
24-
WithDefaulter(&Workspace{}).
25-
WithValidator(&workspaceValidator{
26-
Client: mgr.GetClient(),
27-
overrideName: memberOverridesName,
28-
}).
39+
WithDefaulter(wswh).
40+
WithValidator(wswh).
2941
Complete()
3042
}
3143

32-
// +kubebuilder:webhook:path=/mutate-core-openmcp-cloud-v1alpha1-workspace,mutating=true,failurePolicy=fail,sideEffects=None,groups=core.openmcp.cloud,resources=workspaces,verbs=create;update,versions=v1alpha1,name=mworkspace.kb.io,admissionReviewVersions=v1
44+
// +kubebuilder:webhook:path=/mutate-core-openmcp-cloud-v1alpha1-workspace,mutating=true,failurePolicy=fail,sideEffects=None,groups=core.openmcp.cloud,resources=workspaces,verbs=create;update,versions=v1alpha1,name=mworkspace.openmcp.cloud,admissionReviewVersions=v1
3345

34-
var _ webhook.CustomDefaulter = &Workspace{}
46+
var _ webhook.CustomDefaulter = &WorkspaceWebhook{}
3547

3648
// Default implements webhook.CustomDefaulter so a webhook will be registered for the type
37-
func (w *Workspace) Default(ctx context.Context, obj runtime.Object) error {
49+
func (w *WorkspaceWebhook) Default(ctx context.Context, obj runtime.Object) error {
3850
workspace, err := expectWorkspace(obj)
3951
if err != nil {
4052
return err
@@ -50,30 +62,12 @@ func (w *Workspace) Default(ctx context.Context, obj runtime.Object) error {
5062
return nil
5163
}
5264

53-
// Workspace must implement webhook.CustomValidator in order for it's Mutating/Validating Webhook configuration to be generated by "github.com/openmcp-project/controller-utils/pkg/init/webhooks"
54-
var _ webhook.CustomValidator = &Workspace{}
55-
56-
func (w *Workspace) ValidateCreate(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
57-
return
58-
}
59-
func (w *Workspace) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) {
60-
return
61-
}
62-
func (w *Workspace) ValidateDelete(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
63-
return
64-
}
65-
66-
// +kubebuilder:webhook:path=/validate-core-openmcp-cloud-v1alpha1-workspace,mutating=false,failurePolicy=fail,sideEffects=None,groups=core.openmcp.cloud,resources=workspaces,verbs=create;update;delete,versions=v1alpha1,name=vworkspace.kb.io,admissionReviewVersions=v1
67-
68-
var _ webhook.CustomValidator = &workspaceValidator{}
65+
// +kubebuilder:webhook:path=/validate-core-openmcp-cloud-v1alpha1-workspace,mutating=false,failurePolicy=fail,sideEffects=None,groups=core.openmcp.cloud,resources=workspaces,verbs=create;update;delete,versions=v1alpha1,name=vworkspace.openmcp.cloud,admissionReviewVersions=v1
6966

70-
type workspaceValidator struct {
71-
client.Client
72-
overrideName string
73-
}
67+
var _ webhook.CustomValidator = &WorkspaceWebhook{}
7468

7569
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type
76-
func (v *workspaceValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
70+
func (v *WorkspaceWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
7771
workspace, err := expectWorkspace(obj)
7872
if err != nil {
7973
return
@@ -96,7 +90,7 @@ func (v *workspaceValidator) ValidateCreate(ctx context.Context, obj runtime.Obj
9690
}
9791

9892
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type
99-
func (v *workspaceValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) {
93+
func (v *WorkspaceWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (warnings admission.Warnings, err error) {
10094
oldWorkspace, err := expectWorkspace(oldObj)
10195
if err != nil {
10296
return
@@ -137,7 +131,7 @@ func (v *workspaceValidator) ValidateUpdate(ctx context.Context, oldObj, newObj
137131
}
138132

139133
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type
140-
func (v *workspaceValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
134+
func (v *WorkspaceWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
141135
workspace, err := expectWorkspace(obj)
142136
if err != nil {
143137
return
@@ -160,21 +154,21 @@ func expectWorkspace(obj runtime.Object) (*Workspace, error) {
160154
return workspace, nil
161155
}
162156

163-
func (v *workspaceValidator) ensureValidRole(ctx context.Context, workspace *Workspace) (bool, error) {
157+
func (v *WorkspaceWebhook) ensureValidRole(ctx context.Context, workspace *Workspace) (bool, error) {
164158
userInfo, err := userInfoFromContext(ctx)
165159
if err != nil {
166160
return false, fmt.Errorf("failed to get userInfo")
167161
}
168-
if workspace.UserInfoHasRole(userInfo, WorkspaceRoleAdmin) || isOwnServiceAccount(userInfo) {
162+
if workspace.UserInfoHasRole(userInfo, WorkspaceRoleAdmin) || userInfo.Username == v.Identity {
169163
return true, nil
170164
}
171165

172-
if v.overrideName == "" {
166+
if v.OverrideName == "" {
173167
return false, nil
174168
}
175169

176170
overrides := &MemberOverrides{}
177-
if err := v.Get(ctx, types.NamespacedName{Name: v.overrideName}, overrides); err != nil {
171+
if err := v.Get(ctx, types.NamespacedName{Name: v.OverrideName}, overrides); err != nil {
178172
return false, err
179173
}
180174

api/install/install.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package install
22

33
import (
4+
authenticationv1 "k8s.io/api/authentication/v1"
45
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
56
"k8s.io/apimachinery/pkg/runtime"
67
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -20,8 +21,8 @@ func InstallOperatorAPIsPlatform(scheme *runtime.Scheme) *runtime.Scheme {
2021
utilruntime.Must(deployv1alpha1.AddToScheme(scheme))
2122
utilruntime.Must(pwv1alpha1.AddToScheme(scheme))
2223
utilruntime.Must(apiextv1.AddToScheme(scheme))
23-
utilruntime.Must(gatewayv1.AddToScheme(scheme))
24-
utilruntime.Must(gatewayv1alpha2.AddToScheme(scheme))
24+
utilruntime.Must(gatewayv1.Install(scheme))
25+
utilruntime.Must(gatewayv1alpha2.Install(scheme))
2526

2627
return scheme
2728
}
@@ -30,6 +31,7 @@ func InstallOperatorAPIsOnboarding(scheme *runtime.Scheme) *runtime.Scheme {
3031
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
3132
utilruntime.Must(pwv1alpha1.AddToScheme(scheme))
3233
utilruntime.Must(apiextv1.AddToScheme(scheme))
34+
utilruntime.Must(authenticationv1.AddToScheme(scheme))
3335

3436
return scheme
3537
}

cmd/project-workspace-operator/app/run.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/spf13/cobra"
1212

13+
authenticationv1 "k8s.io/api/authentication/v1"
1314
rbacv1 "k8s.io/api/rbac/v1"
1415
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1516
"k8s.io/apimachinery/pkg/runtime"
@@ -253,6 +254,11 @@ func (o *RunOptions) Run(ctx context.Context) error {
253254
Resources: []string{"clusterroles", "clusterrolebindings", "rolebindings"},
254255
Verbs: []string{"*"},
255256
},
257+
{
258+
APIGroups: []string{"authentication.k8s.io/v1"},
259+
Resources: []string{"selfsubjectreviews"},
260+
Verbs: []string{"*"},
261+
},
256262
},
257263
},
258264
})
@@ -309,6 +315,14 @@ func (o *RunOptions) Run(ctx context.Context) error {
309315
}
310316
}
311317

318+
// figure out own identity
319+
review := &authenticationv1.SelfSubjectReview{}
320+
if err := onboardingCluster.Client().Create(ctx, review); err != nil {
321+
return fmt.Errorf("failed to get own identity: %w", err)
322+
}
323+
identity := review.Status.UserInfo.Username
324+
setupLog.Info("determined own identity to exclude from webhook validation", "identity", identity)
325+
312326
webhookServer := webhook.NewServer(webhook.Options{
313327
TLSOpts: o.WebhookTLSOpts,
314328
Port: WebhookPortPod,
@@ -339,10 +353,10 @@ func (o *RunOptions) Run(ctx context.Context) error {
339353
}
340354

341355
if !pwc.Spec.Webhook.Disabled {
342-
if err = (&pwv1alpha1.Project{}).SetupWebhookWithManager(mgr, pwc.Spec.MemberOverridesName); err != nil {
356+
if err = (&pwv1alpha1.Project{}).SetupWebhookWithManager(ctx, mgr, pwc.Spec.MemberOverridesName, identity); err != nil {
343357
return fmt.Errorf("unable to setup Project webhook: %w", err)
344358
}
345-
if err = (&pwv1alpha1.Workspace{}).SetupWebhookWithManager(mgr, pwc.Spec.MemberOverridesName); err != nil {
359+
if err = (&pwv1alpha1.Workspace{}).SetupWebhookWithManager(ctx, mgr, pwc.Spec.MemberOverridesName, identity); err != nil {
346360
return fmt.Errorf("unable to setup Workspace webhook: %w", err)
347361
}
348362
}

0 commit comments

Comments
 (0)