Skip to content

Commit

Permalink
WIP webhook
Browse files Browse the repository at this point in the history
  • Loading branch information
britaniar committed Mar 14, 2024
1 parent aa099a6 commit aa6f635
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"go.goms.io/fleet/pkg/utils"
"net/http"
"strings"

Expand All @@ -19,12 +18,12 @@ import (
)

const (
ClusterResourceOverride = "ClusterResourceOverride"
// ValidationPath is the webhook service path which admission requests are routed to for validating custom resource definition resources.
ValidationPath = "/validate-fleetoverridehandler"
groupMatch = `^[^.]*\.(.*)`

ResourceAllowedFormat = "user: '%s' in '%s' is allowed to %s resource %+v"
ResourceDeniedFormat = "user: '%s' in '%s' is not allowed to %s resource %+v"
ResourceAllowedFormat = "user: '%s' in group '%s' is allowed to %s resource %+v"
ResourceDeniedFormat = "user: '%s' in group '%s' is not allowed to %s resource %+v because %s"
)

// Add registers the webhook for K8s built-in object types.
Expand All @@ -49,31 +48,22 @@ type fleetOverrideValidator struct {

// Handle receives the request then allows/denies the request to modify fleet resources.
func (v *fleetOverrideValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
var response admission.Response
if req.Operation == admissionv1.Create || req.Operation == admissionv1.Update {
// Check if the resource is being selected by resource name
response = v.isResourceSelectedByName(req)
response := v.isResourceSelectedByName(req)

// Check if each resource is selected by one override
response = v.isOverrideSelectionCountValid(ctx, req, v.client)

//// Check if each resource is selected by one override
//if !isOverrideSelectionCountValid(req, ctx, v.client) {
// return admission.Denied("Resource is already selected by one override.")
//}
//
// Check if total override count limit has been reached for both CRO & RO
response = v.isOverrideCountValid(req, ctx, v.client)
response = v.isOverrideCountValid(ctx, req, v.client)

//// Check if each override field is valid
//for _, override := range getOverrides(obj) {
// for _, field := range override.Fields {
// if !isValidOverrideField(field.Path) {
// return admission.Denied(fmt.Sprintf("Override field %s is not valid", field.Path))
// }
// }
//}
// Check if each override field is valid
response = v.isValidOverrideField(req)

return admission.Allowed("")
return response
}
return response
return admission.Allowed(fmt.Sprintf(ResourceAllowedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind()))
}

// isResourceSelectedByName checks if override is selecting resource by name
Expand All @@ -95,68 +85,82 @@ func (v *fleetOverrideValidator) isResourceSelectedByName(req admission.Request)

// Check if the resource is being selected by resource name
if _, ok := clusterResourceSelectors[0].(map[string]interface{})["name"]; !ok {
return admission.Denied(fmt.Sprintf(ResourceAllowedFormat, "test-user", utils.GenerateGroupString([]string{"test-group"}), admissionv1.Create, req.Object.Object.GetObjectKind()))
return admission.Denied(fmt.Sprintf(ResourceDeniedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind(), "resource is not selected by name"))
}

return admission.Allowed(fmt.Sprintf(ResourceAllowedFormat, "test-user", utils.GenerateGroupString([]string{"test-group"}), admissionv1.Create, req.Object.Object.GetObjectKind()))
return admission.Allowed(fmt.Sprintf(ResourceAllowedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind()))
}

// isValidOverrideField check is the override is being applied to a valid field
func isValidOverrideField(fieldPath string) bool {
func (v *fleetOverrideValidator) isValidOverrideField(req admission.Request) admission.Response {
fieldPath := req.AdmissionRequest.SubResource
// Fields in the TypeMeta should not be overridden
if strings.HasPrefix(fieldPath, ".metadata") && (strings.HasSuffix(fieldPath, ".kind") || strings.HasSuffix(fieldPath, ".apiVersion")) {
return false
return admission.Denied(fmt.Sprintf(ResourceDeniedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind(), "cannot override field in TypeMeta"))
}

// Fields in the ObjectMeta excluding annotations and labels should not be overridden
if strings.HasPrefix(fieldPath, ".metadata") && !strings.HasPrefix(fieldPath, ".metadata.annotations") && !strings.HasPrefix(fieldPath, ".metadata.labels") {
return false
return admission.Denied(fmt.Sprintf(ResourceDeniedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind(), "cannot override field in ObjectMeta"))
}

// The Status field should not be overridden
if strings.HasPrefix(fieldPath, ".status") {
return false
return admission.Denied(fmt.Sprintf(ResourceDeniedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind(), "cannot override status field"))
}

return true
return admission.Allowed(fmt.Sprintf(ResourceAllowedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind()))
}

// isOverrideSelectionCountValid checks if the number of overrides selected for that resources limit has been reached.
// The namespace scoped resource can be selected by at most 2 overrides (CRO and RO). The cluster scoped resource can be only selected by one CRO
func isOverrideSelectionCountValid(obj *unstructured.Unstructured, ctx context.Context, client client.Client) bool {
func (v *fleetOverrideValidator) isOverrideSelectionCountValid(ctx context.Context, req admission.Request, client client.Client) admission.Response {
var countRO int64
var countCRO int64
var err error

// Decode the object
obj := &unstructured.Unstructured{}
if err := v.decoder.Decode(req, obj); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

if obj.GetNamespace() == "" {
// Cluster-scoped resource
if obj.GetKind() == "ClusterResourceOverride" {
countCRO, err = getClusterOverrideCount(obj.GetName(), ctx, client)
if obj.GetKind() == ClusterResourceOverride {
countCRO, err = getClusterOverrideCount(ctx, obj.GetName(), client)
if err != nil {
return false
return admission.Errored(http.StatusBadRequest, err)
}
if !(countCRO < 1) {
return admission.Denied(fmt.Sprintf(ResourceDeniedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind(), "can only create 1 ClusterResourceOverride for each resource"))
}
return countCRO < 1
}
} else {
// Namespace-scoped resource
if countCRO, err = getClusterOverrideCount(obj.GetName(), ctx, client); err != nil {
return false
if countCRO, err = getClusterOverrideCount(ctx, obj.GetName(), client); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if countRO, err = getResourceOverrideCount(obj.GetName(), ctx, client); err != nil {
return false
if countRO, err = getResourceOverrideCount(ctx, obj.GetName(), client); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if obj.GetKind() == "ClusterResourceOverride" {
if obj.GetKind() == ClusterResourceOverride {
// Cluster-scoped resource selected by a CRO
return countCRO < 1
if countCRO == 1 {
return admission.Denied(fmt.Sprintf(ResourceDeniedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind(), "can only create 1 ClusterResourceOverride for each resource"))
}
} else if obj.GetKind() == "ResourceOverride" {
// Namespace-scoped resource selected by a RO
return countRO < 1
if !(countRO < 1) {
return admission.Denied(fmt.Sprintf(ResourceDeniedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind(), "can only create 1 ResourceOverride for each resource"))
}
}
}
return true
return admission.Allowed(fmt.Sprintf(ResourceAllowedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind()))
}

// getClusterOverrideCount gets the number of cluster resource overrides for the given resource type
func getClusterOverrideCount(resourceName string, ctx context.Context, client client.Client) (int64, error) {
func getClusterOverrideCount(ctx context.Context, resourceName string, client client.Client) (int64, error) {
// Create a list of cluster resource overrides with the given resource type
overrideList := &fleetv1alpha1.ClusterResourceOverrideList{}
if err := client.List(ctx, overrideList); err != nil {
Expand All @@ -174,7 +178,7 @@ func getClusterOverrideCount(resourceName string, ctx context.Context, client cl
}

// getResourceOverrideCount gets the number of resource overrides for the given namespace and resource type
func getResourceOverrideCount(resourceName string, ctx context.Context, client client.Client) (int64, error) {
func getResourceOverrideCount(ctx context.Context, resourceName string, client client.Client) (int64, error) {
overrideList := &fleetv1alpha1.ResourceOverrideList{}
if err := client.List(ctx, overrideList); err != nil {
return 0, err
Expand All @@ -193,7 +197,7 @@ func getResourceOverrideCount(resourceName string, ctx context.Context, client c
// isOverrideCountValid checks if the override count limit has been reached
// At most 100 clusterResourceOverride can be created.
// At most 100 resourceOverride can be created.
func (v *fleetOverrideValidator) isOverrideCountValid(req admission.Request, ctx context.Context, client client.Client) admission.Response {
func (v *fleetOverrideValidator) isOverrideCountValid(ctx context.Context, req admission.Request, client client.Client) admission.Response {
var count int64
var err error

Expand All @@ -211,16 +215,16 @@ func (v *fleetOverrideValidator) isOverrideCountValid(req admission.Request, ctx
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if obj.GetKind() == "ClusterResourceOverride" {
if obj.GetKind() == ClusterResourceOverride {
if !(count <= 100) {
return admission.Denied("Override count limit has been reached.")
return admission.Denied(fmt.Sprintf(ResourceDeniedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind(), "can only create at most 100 CluserResourceOverrides"))
}
} else if obj.GetKind() == "ResourceOverride" {
if !(count <= 100) {
return admission.Denied("Override count limit has been reached.")
return admission.Denied(fmt.Sprintf(ResourceDeniedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind(), "can only create at most 100 ResourceOverrides"))
}
}
return admission.Allowed(fmt.Sprintf(ResourceAllowedFormat, "test-user", utils.GenerateGroupString([]string{"test-group"}), admissionv1.Create, req.Object.Object.GetObjectKind()))
return admission.Allowed(fmt.Sprintf(ResourceAllowedFormat, req.UserInfo.Username, req.UserInfo.Groups, req.Operation, req.Object.Object.GetObjectKind()))
}

// getTotalClusterOverrideCount gets the total number of cluster resource overrides
Expand Down
Loading

0 comments on commit aa6f635

Please sign in to comment.