Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile.dapper
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM registry.suse.com/bci/golang:1.22
FROM registry.suse.com/bci/golang:1.23

ARG DAPPER_HOST_ARCH
ENV ARCH $DAPPER_HOST_ARCH
Expand Down
13 changes: 12 additions & 1 deletion docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ This admission webhook prevents the disabling or deletion of a NodeDriver if the

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.

#### BackingNamespace validation
The BackingNamespace field cannot be changed once set. Projects without the BackingNamespace field can have it added.

#### Protects system project

The system project cannot be deleted.
Expand All @@ -200,8 +203,17 @@ Project quotas and default limits must be consistent with one another and must b

#### On create

Populates the BackingNamespace field by concatenating `Project.ClusterName` and `Project.Name`.

If the project is using a generated name (ie `GenerateName` is not empty), the generation happens within the mutating webhook.
The reason for this is that the BackingNamespace is made up of the `Project.Name`, and name generation happens after mutating webhooks and before validating webhooks.

Adds the authz.management.cattle.io/creator-role-bindings annotation.

#### On update

If the BackingNamespace field is empty, populate the BackingNamespace field with the project name.

## ProjectRoleTemplateBinding

### Validation Checks
Expand All @@ -220,7 +232,6 @@ Users cannot create ProjectRoleTemplateBindings that violate the following const
- The `ProjectName` field must be:
- Provided as a non-empty value
- Specified using the format of `clusterName:projectName`; `clusterName` is the `metadata.name` of a cluster, and `projectName` is the `metadata.name` of a project
- The `projectName` part of the field must match the namespace of the ProjectRoleTemplateBinding
- Refer to a valid project and cluster (both must exist and project.Spec.ClusterName must equal the cluster)
- Either a user subject (through `UserName` or `UserPrincipalName`), or a group subject (through `GroupName`
or `GroupPrincipalName`), or a service account subject (through `ServiceAccount`) must be specified. Exactly one
Expand Down
14 changes: 7 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/rancher/webhook

go 1.22.0
go 1.23.0

toolchain go1.22.7
toolchain go1.23.7

replace (
github.com/rancher/rke => github.com/rancher/rke v1.5.13
Expand Down Expand Up @@ -45,7 +45,7 @@ require (
github.com/gorilla/mux v1.8.1
github.com/rancher/dynamiclistener v0.4.0
github.com/rancher/lasso v0.1.0
github.com/rancher/rancher/pkg/apis v0.0.0-20250317215908-5cc17ed521b4
github.com/rancher/rancher/pkg/apis v0.0.0-20250411213817-1116d03f0676
github.com/rancher/rke v1.5.15
github.com/rancher/wrangler/v2 v2.1.4
github.com/robfig/cron v1.2.0
Expand All @@ -56,7 +56,7 @@ require (
golang.org/x/tools v0.28.0
k8s.io/api v0.30.0
k8s.io/apimachinery v0.30.0
k8s.io/apiserver v0.28.9
k8s.io/apiserver v0.28.12
k8s.io/client-go v12.0.0+incompatible
k8s.io/kubernetes v1.28.9
k8s.io/pod-security-admission v0.28.6
Expand Down Expand Up @@ -141,8 +141,8 @@ require (
golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.25.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/time v0.5.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
Expand All @@ -157,7 +157,7 @@ require (
k8s.io/apiextensions-apiserver v0.28.9 // indirect
k8s.io/cloud-provider v0.0.0 // indirect
k8s.io/code-generator v0.28.9 // indirect
k8s.io/component-base v0.28.9 // indirect
k8s.io/component-base v0.28.12 // indirect
k8s.io/component-helpers v0.28.6 // indirect
k8s.io/controller-manager v0.28.6 // indirect
k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect
Expand Down
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,8 @@ github.com/rancher/lasso v0.1.0 h1:LOCNtL7GvyTp3KTyLiFE2JKmxyX2xF6HeFmtd0pvMHA=
github.com/rancher/lasso v0.1.0/go.mod h1:pYKOe2r/5O0w3ypoc7xHQF8LvWCp5PsNRea1Jpq3vBU=
github.com/rancher/norman v0.2.0 h1:PFIiHel0i0OFNZoygIS4oxJV6gRi1tffudzCqDnKLlA=
github.com/rancher/norman v0.2.0/go.mod h1:WbNpu/HwChwKk54W0rWBdioxYVVZwVVz//UX84m/NvY=
github.com/rancher/rancher/pkg/apis v0.0.0-20250317215908-5cc17ed521b4 h1:9ioXT/yLX4RwwryQCD8IEgezERVZBUxf6JWrHK+Fj2M=
github.com/rancher/rancher/pkg/apis v0.0.0-20250317215908-5cc17ed521b4/go.mod h1:KsweP8b8wqois/Pke9C2aw/ExIXBdqFg1/UUE0Gl5g8=
github.com/rancher/rancher/pkg/apis v0.0.0-20250411213817-1116d03f0676 h1:ykXjqtx8TwCeZcGmb/YZ3+pLSPWPbxGvsEnmtlYEMqs=
github.com/rancher/rancher/pkg/apis v0.0.0-20250411213817-1116d03f0676/go.mod h1:8AfqZ7rm9zNt3F73883QnPoT9tGSM5g3QK62pNd1Vts=
github.com/rancher/rke v1.5.13 h1:Y7e3qI0G2HbU3Vm5k3Sqeq+UR2w114K5yY6wT9VFKcY=
github.com/rancher/rke v1.5.13/go.mod h1:/z9oyKqYpFwgRBV9rfLxqUdjydz/VMCTcjld4uUt7uM=
github.com/rancher/wrangler/v2 v2.1.4 h1:ohov0i6A9dJHHO6sjfsH4Dqv93ZTdm5lIJVJdPzVdQc=
Expand Down Expand Up @@ -514,11 +514,11 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
12 changes: 12 additions & 0 deletions pkg/resources/management.cattle.io/v3/project/Project.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

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.

### BackingNamespace validation
The BackingNamespace field cannot be changed once set. Projects without the BackingNamespace field can have it added.

### Protects system project

The system project cannot be deleted.
Expand All @@ -16,4 +19,13 @@ Project quotas and default limits must be consistent with one another and must b

### On create

Populates the BackingNamespace field by concatenating `Project.ClusterName` and `Project.Name`.

If the project is using a generated name (ie `GenerateName` is not empty), the generation happens within the mutating webhook.
The reason for this is that the BackingNamespace is made up of the `Project.Name`, and name generation happens after mutating webhooks and before validating webhooks.

Adds the authz.management.cattle.io/creator-role-bindings annotation.

### On update

If the BackingNamespace field is empty, populate the BackingNamespace field with the project name.
90 changes: 82 additions & 8 deletions pkg/resources/management.cattle.io/v3/project/mutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ package project
import (
"encoding/json"
"fmt"
"strings"

v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
"github.com/rancher/webhook/pkg/admission"
ctrlv3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3"
objectsv3 "github.com/rancher/webhook/pkg/generated/objects/management.cattle.io/v3"
"github.com/rancher/webhook/pkg/patch"
corev1controller "github.com/rancher/wrangler/v2/pkg/generated/controllers/core/v1"
"github.com/rancher/wrangler/v2/pkg/name"
"github.com/sirupsen/logrus"
admissionv1 "k8s.io/api/admission/v1"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/utils/trace"
)

Expand All @@ -31,13 +36,17 @@ var gvr = schema.GroupVersionResource{
// Mutator implements admission.MutatingAdmissionWebhook.
type Mutator struct {
roleTemplateCache ctrlv3.RoleTemplateCache
namespaceCache corev1controller.NamespaceCache
projectCache ctrlv3.ProjectCache
}

// NewMutator returns a new mutator which mutates projects
func NewMutator(roleTemplateCache ctrlv3.RoleTemplateCache) *Mutator {
func NewMutator(nsCache corev1controller.NamespaceCache, roleTemplateCache ctrlv3.RoleTemplateCache, projectCache ctrlv3.ProjectCache) *Mutator {
roleTemplateCache.AddIndexer(mutatorCreatorRoleTemplateIndex, creatorRoleTemplateIndexer)
return &Mutator{
roleTemplateCache: roleTemplateCache,
namespaceCache: nsCache,
projectCache: projectCache,
}
}

Expand All @@ -58,6 +67,7 @@ func (m *Mutator) GVR() schema.GroupVersionResource {
func (m *Mutator) Operations() []admissionregistrationv1.OperationType {
return []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
admissionregistrationv1.Update,
}
}

Expand Down Expand Up @@ -85,13 +95,24 @@ func (m *Mutator) Admit(request *admission.Request) (*admissionv1.AdmissionRespo
}
switch request.Operation {
case admissionv1.Create:
return m.admitCreate(project, request)
project, err = m.admitCreate(project)
if err != nil {
return nil, err
}
case admissionv1.Update:
project = m.updateProjectNamespace(project)
default:
return nil, fmt.Errorf("operation type %q not handled", request.Operation)
}
response := &admissionv1.AdmissionResponse{}
if err := patch.CreatePatch(request.Object.Raw, project, response); err != nil {
return nil, fmt.Errorf("failed to create patch: %w", err)
}
response.Allowed = true
return response, nil
}

func (m *Mutator) admitCreate(project *v3.Project, request *admission.Request) (*admissionv1.AdmissionResponse, error) {
func (m *Mutator) admitCreate(project *v3.Project) (*v3.Project, error) {
logrus.Debugf("[project-mutation] adding creator-role-bindings to project: %v", project.Name)
newProject := project.DeepCopy()

Expand All @@ -103,12 +124,11 @@ func (m *Mutator) admitCreate(project *v3.Project, request *admission.Request) (
return nil, fmt.Errorf("failed to add annotation to project %s: %w", project.Name, err)
}
newProject.Annotations[roleTemplatesRequired] = annotations
response := &admissionv1.AdmissionResponse{}
if err := patch.CreatePatch(request.Object.Raw, newProject, response); err != nil {
return nil, fmt.Errorf("failed to create patch: %w", err)
newProject, err = m.createProjectNamespace(newProject)
if err != nil {
return nil, fmt.Errorf("failed to create project namespace %s: %w", project.Status.BackingNamespace, err)
}
response.Allowed = true
return response, nil
return newProject, nil
}

func (m *Mutator) getCreatorRoleTemplateAnnotations() (string, error) {
Expand All @@ -126,3 +146,57 @@ func (m *Mutator) getCreatorRoleTemplateAnnotations() (string, error) {
}
return string(annotations), nil
}
func (m *Mutator) createProjectNamespace(project *v3.Project) (*v3.Project, error) {
newProject := project.DeepCopy()
backingNamespace := ""
var err error
// When the project name is empty, that means we want to generate a name for it
// Name generation happens after mutating webhooks, so in order to have access to the name early
// for the backing namespace, we need to generate it ourselves
if project.Name == "" {
// If err is nil, (meaning "project exists", see below) we need to repeat the generation process to find a project name and backing namespace that isn't taken
newName := ""
for err == nil {
newName = names.SimpleNameGenerator.GenerateName(project.GenerateName)
_, err = m.projectCache.Get(newProject.Spec.ClusterName, newName)
if err == nil {
// A project with this name already exists. Generate a new name.
continue
} else if !apierrors.IsNotFound(err) {
return nil, err
}

backingNamespace = name.SafeConcatName(newProject.Spec.ClusterName, strings.ToLower(newName))
_, err = m.namespaceCache.Get(backingNamespace)

// If the backing namespace already exists, generate a new project name
if err != nil && !apierrors.IsNotFound(err) {
return nil, err
}
}
newProject.Name = newName
} else {
backingNamespace = name.SafeConcatName(newProject.Spec.ClusterName, strings.ToLower(newProject.Name))
_, err = m.namespaceCache.Get(backingNamespace)
if err == nil {
return nil, fmt.Errorf("namespace %v already exists", backingNamespace)
} else if !apierrors.IsNotFound(err) {
return nil, err
}
}

newProject.Status.BackingNamespace = backingNamespace
return newProject, nil
}

// updateProjectNamespace fills in BackingNamespace with the project name if it wasn't already set
// this was the naming convention of project namespaces prior to using the BackingNamespace field. Filling
// it here is just to maintain backwards compatibility
func (m *Mutator) updateProjectNamespace(project *v3.Project) *v3.Project {
if project.Status.BackingNamespace != "" {
return project
}
newProject := project.DeepCopy()
newProject.Status.BackingNamespace = newProject.Name
return newProject
}
Loading
Loading