Skip to content

Commit

Permalink
Validate creatorId and creator-principal-name annotations for cluster…
Browse files Browse the repository at this point in the history
…/project (#501)

Ref: rancher/rancher#46828
  • Loading branch information
pmatseykanets authored Sep 19, 2024
1 parent f98e238 commit 6b74a9a
Show file tree
Hide file tree
Showing 14 changed files with 1,012 additions and 57 deletions.
18 changes: 17 additions & 1 deletion docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ If yes, the webhook redacts the role, so that it only grants a deletion permissi

# management.cattle.io/v3

## Cluster

### Validation Checks

#### Annotations validation

When a cluster is created and `field.cattle.io/creator-principal-name` annotation is set then `field.cattle.io/creatorId` annotation must be set as well. The value of `field.cattle.io/creator-principal-name` should match the creator's user principal id.

When a cluster is updated `field.cattle.io/creator-principal-name` and `field.cattle.io/creatorId` annotations must stay the same or removed.

## ClusterProxyConfig

### Validation Checks
Expand Down Expand Up @@ -216,7 +226,7 @@ This admission webhook prevents the disabling or deletion of a NodeDriver if the

#### ClusterName validation

ClusterName must be equal to the namespace, and must refer to an existing management.cattle.io/v3.Cluster object. In addition, users cannot update the field after creation.
ClusterName must be equal to the namespace, and must refer to an existing `management.cattle.io/v3.Cluster` object. In addition, users cannot update the field after creation.

#### Protects system project

Expand All @@ -233,6 +243,12 @@ The container default resource configuration must have properly formatted quanti

Limits for any resource must not be less than requests.

#### Annotations validation

When a project is created and `field.cattle.io/creator-principal-name` annotation is set then `field.cattle.io/creatorId` annotation must be set as well. The value of `field.cattle.io/creator-principal-name` should match the creator's user principal id.

When a project is updated `field.cattle.io/creator-principal-name` and `field.cattle.io/creatorId` annotations must stay the same or removed.

### Mutations

#### On create
Expand Down
2 changes: 2 additions & 0 deletions pkg/auth/escalation.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
const (
// CreatorIDAnn is an annotation key for the id of the creator.
CreatorIDAnn = "field.cattle.io/creatorId"
// CreatorPrincipalNameAnn is an annotation key for the principal name of the creator.
CreatorPrincipalNameAnn = "field.cattle.io/creator-principal-name"
)

// RequestUserHasVerb checks if the user associated with the context has a given verb on a given gvr for a specified name/namespace
Expand Down
1 change: 1 addition & 0 deletions pkg/codegen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func main() {
v3.ClusterProxyConfig{},
v3.Feature{},
v3.Setting{},
v3.User{},
},
},
"provisioning.cattle.io": {
Expand Down

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

208 changes: 208 additions & 0 deletions pkg/generated/controllers/management.cattle.io/v3/user.go

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

55 changes: 55 additions & 0 deletions pkg/resources/common/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package common

import (
"errors"
"fmt"
"net/http"

"github.com/rancher/webhook/pkg/admission"
"github.com/rancher/webhook/pkg/auth"
controllerv3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3"
admissionv1 "k8s.io/api/admission/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/apis/rbac"
Expand Down Expand Up @@ -58,3 +61,55 @@ func ValidateRules(rules []rbacv1.PolicyRule, isNamespaced bool, fldPath *field.
}
return returnErr
}

var annotationsFieldPath = field.NewPath("metadata").Child("annotations")

// CheckCreatorPrincipalName checks that if creator-principal-name annotation is set then creatorId annotation must be set as well.
// The value of creator-principal-name annotation should match the creator's user principal id.
func CheckCreatorPrincipalName(userCache controllerv3.UserCache, obj metav1.Object) (*field.Error, error) {
annotations := obj.GetAnnotations()
principalName := annotations[auth.CreatorPrincipalNameAnn]
if principalName == "" { // Nothing to check.
return nil, nil
}

creatorID := annotations[auth.CreatorIDAnn]
if creatorID == "" {
return field.Invalid(annotationsFieldPath, auth.CreatorPrincipalNameAnn, fmt.Sprintf("annotation %s is required", auth.CreatorIDAnn)), nil
}

user, err := userCache.Get(creatorID)
if err != nil {
if apierrors.IsNotFound(err) {
return field.Invalid(annotationsFieldPath, auth.CreatorPrincipalNameAnn, fmt.Sprintf("creator user %s doesn't exist", creatorID)), nil
}
return nil, fmt.Errorf("error getting creator user %s: %w", creatorID, err)
}

for _, principal := range user.PrincipalIDs {
if principal == principalName {
return nil, nil
}
}

return field.Invalid(annotationsFieldPath, auth.CreatorPrincipalNameAnn, fmt.Sprintf("creator user %s doesn't have principal %s", creatorID, principalName)), nil
}

// CheckCreatorAnnotationsOnUpdate checks that the creatorId and creator-principal-name annotations are immutable.
// The only allowed update is removing the annotations.
// This function should only be called for the update operation.
func CheckCreatorAnnotationsOnUpdate(oldObj, newObj metav1.Object) *field.Error {
oldAnnotations := oldObj.GetAnnotations()
newAnnotations := newObj.GetAnnotations()

for _, annotation := range []string{auth.CreatorIDAnn, auth.CreatorPrincipalNameAnn} {
if _, ok := newAnnotations[annotation]; ok {
// If the annotation exists on the new object it must be the same as on the old object.
if oldAnnotations[annotation] != newAnnotations[annotation] {
return field.Invalid(annotationsFieldPath, annotation, "annotation is immutable")
}
}
}

return nil
}
Loading

0 comments on commit 6b74a9a

Please sign in to comment.