Skip to content

Commit 6b74a9a

Browse files
Validate creatorId and creator-principal-name annotations for cluster/project (#501)
Ref: rancher/rancher#46828
1 parent f98e238 commit 6b74a9a

File tree

14 files changed

+1012
-57
lines changed

14 files changed

+1012
-57
lines changed

Diff for: docs.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ If yes, the webhook redacts the role, so that it only grants a deletion permissi
6363

6464
# management.cattle.io/v3
6565

66+
## Cluster
67+
68+
### Validation Checks
69+
70+
#### Annotations validation
71+
72+
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.
73+
74+
When a cluster is updated `field.cattle.io/creator-principal-name` and `field.cattle.io/creatorId` annotations must stay the same or removed.
75+
6676
## ClusterProxyConfig
6777

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

217227
#### ClusterName validation
218228

219-
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.
229+
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.
220230

221231
#### Protects system project
222232

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

234244
Limits for any resource must not be less than requests.
235245

246+
#### Annotations validation
247+
248+
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.
249+
250+
When a project is updated `field.cattle.io/creator-principal-name` and `field.cattle.io/creatorId` annotations must stay the same or removed.
251+
236252
### Mutations
237253

238254
#### On create

Diff for: pkg/auth/escalation.go

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
const (
2323
// CreatorIDAnn is an annotation key for the id of the creator.
2424
CreatorIDAnn = "field.cattle.io/creatorId"
25+
// CreatorPrincipalNameAnn is an annotation key for the principal name of the creator.
26+
CreatorPrincipalNameAnn = "field.cattle.io/creator-principal-name"
2527
)
2628

2729
// RequestUserHasVerb checks if the user associated with the context has a given verb on a given gvr for a specified name/namespace

Diff for: pkg/codegen/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func main() {
4848
v3.ClusterProxyConfig{},
4949
v3.Feature{},
5050
v3.Setting{},
51+
v3.User{},
5152
},
5253
},
5354
"provisioning.cattle.io": {

Diff for: pkg/generated/controllers/management.cattle.io/v3/interface.go

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: pkg/generated/controllers/management.cattle.io/v3/user.go

+208
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: pkg/resources/common/validation.go

+55
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ package common
22

33
import (
44
"errors"
5+
"fmt"
56
"net/http"
67

78
"github.com/rancher/webhook/pkg/admission"
89
"github.com/rancher/webhook/pkg/auth"
10+
controllerv3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3"
911
admissionv1 "k8s.io/api/admission/v1"
1012
rbacv1 "k8s.io/api/rbac/v1"
13+
apierrors "k8s.io/apimachinery/pkg/api/errors"
1114
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1215
"k8s.io/apimachinery/pkg/util/validation/field"
1316
"k8s.io/kubernetes/pkg/apis/rbac"
@@ -58,3 +61,55 @@ func ValidateRules(rules []rbacv1.PolicyRule, isNamespaced bool, fldPath *field.
5861
}
5962
return returnErr
6063
}
64+
65+
var annotationsFieldPath = field.NewPath("metadata").Child("annotations")
66+
67+
// CheckCreatorPrincipalName checks that if creator-principal-name annotation is set then creatorId annotation must be set as well.
68+
// The value of creator-principal-name annotation should match the creator's user principal id.
69+
func CheckCreatorPrincipalName(userCache controllerv3.UserCache, obj metav1.Object) (*field.Error, error) {
70+
annotations := obj.GetAnnotations()
71+
principalName := annotations[auth.CreatorPrincipalNameAnn]
72+
if principalName == "" { // Nothing to check.
73+
return nil, nil
74+
}
75+
76+
creatorID := annotations[auth.CreatorIDAnn]
77+
if creatorID == "" {
78+
return field.Invalid(annotationsFieldPath, auth.CreatorPrincipalNameAnn, fmt.Sprintf("annotation %s is required", auth.CreatorIDAnn)), nil
79+
}
80+
81+
user, err := userCache.Get(creatorID)
82+
if err != nil {
83+
if apierrors.IsNotFound(err) {
84+
return field.Invalid(annotationsFieldPath, auth.CreatorPrincipalNameAnn, fmt.Sprintf("creator user %s doesn't exist", creatorID)), nil
85+
}
86+
return nil, fmt.Errorf("error getting creator user %s: %w", creatorID, err)
87+
}
88+
89+
for _, principal := range user.PrincipalIDs {
90+
if principal == principalName {
91+
return nil, nil
92+
}
93+
}
94+
95+
return field.Invalid(annotationsFieldPath, auth.CreatorPrincipalNameAnn, fmt.Sprintf("creator user %s doesn't have principal %s", creatorID, principalName)), nil
96+
}
97+
98+
// CheckCreatorAnnotationsOnUpdate checks that the creatorId and creator-principal-name annotations are immutable.
99+
// The only allowed update is removing the annotations.
100+
// This function should only be called for the update operation.
101+
func CheckCreatorAnnotationsOnUpdate(oldObj, newObj metav1.Object) *field.Error {
102+
oldAnnotations := oldObj.GetAnnotations()
103+
newAnnotations := newObj.GetAnnotations()
104+
105+
for _, annotation := range []string{auth.CreatorIDAnn, auth.CreatorPrincipalNameAnn} {
106+
if _, ok := newAnnotations[annotation]; ok {
107+
// If the annotation exists on the new object it must be the same as on the old object.
108+
if oldAnnotations[annotation] != newAnnotations[annotation] {
109+
return field.Invalid(annotationsFieldPath, annotation, "annotation is immutable")
110+
}
111+
}
112+
}
113+
114+
return nil
115+
}

0 commit comments

Comments
 (0)