Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validation webhooks for users, vhosts, and policies #78

Merged
merged 3 commits into from
Mar 22, 2021
Merged
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 api/v1alpha1/exchange_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var _ = Describe("exchange webhook", func() {

var exchange = Exchange{
ObjectMeta: metav1.ObjectMeta{
Name: "update-binding",
Name: "test-exchange",
},
Spec: ExchangeSpec{
Name: "test",
Expand Down
8 changes: 8 additions & 0 deletions api/v1alpha1/policy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)

// PolicySpec defines the desired state of Policy
Expand Down Expand Up @@ -66,6 +67,13 @@ type PolicyList struct {
Items []Policy `json:"items"`
}

func (p *Policy) GroupResource() schema.GroupResource {
return schema.GroupResource{
Group: p.GroupVersionKind().Group,
Resource: p.GroupVersionKind().Kind,
}
}

func init() {
SchemeBuilder.Register(&Policy{}, &PolicyList{})
}
55 changes: 55 additions & 0 deletions api/v1alpha1/policy_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package v1alpha1

import (
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

func (p *Policy) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(p).
Complete()
}

// +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1alpha1-policy,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=policies,versions=v1alpha1,name=vpolicy.kb.io,sideEffects=none,admissionReviewVersions=v1

var _ webhook.Validator = &Policy{}

// no validation on create
func (p *Policy) ValidateCreate() error {
return nil
}

// returns error type 'forbidden' for updates on policy name, vhost and rabbitmqClusterReference
func (p *Policy) ValidateUpdate(old runtime.Object) error {
oldPolicy, ok := old.(*Policy)
if !ok {
return apierrors.NewBadRequest(fmt.Sprintf("expected a policy but got a %T", old))
}

detailMsg := "updates on name, vhost and rabbitmqClusterReference are all forbidden"
if p.Spec.Name != oldPolicy.Spec.Name {
return apierrors.NewForbidden(p.GroupResource(), p.Name,
field.Forbidden(field.NewPath("spec", "name"), detailMsg))
}

if p.Spec.Vhost != oldPolicy.Spec.Vhost {
return apierrors.NewForbidden(p.GroupResource(), p.Name,
field.Forbidden(field.NewPath("spec", "vhost"), detailMsg))
}

if p.Spec.RabbitmqClusterReference != oldPolicy.Spec.RabbitmqClusterReference {
return apierrors.NewForbidden(p.GroupResource(), p.Name,
field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg))
}
return nil
}

// no validation on delete
func (p *Policy) ValidateDelete() error {
return nil
}
73 changes: 73 additions & 0 deletions api/v1alpha1/policy_webhook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package v1alpha1

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

var _ = Describe("policy webhook", func() {
var policy = Policy{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: PolicySpec{
Name: "test",
Vhost: "/test",
Pattern: "a-pattern",
ApplyTo: "all",
Priority: 0,
RabbitmqClusterReference: RabbitmqClusterReference{
Name: "a-cluster",
Namespace: "default",
},
},
}

It("does not allow updates on policy name", func() {
newPolicy := policy.DeepCopy()
newPolicy.Spec.Name = "new-name"
Expect(apierrors.IsForbidden(newPolicy.ValidateUpdate(&policy))).To(BeTrue())
})

It("does not allow updates on vhost", func() {
newPolicy := policy.DeepCopy()
newPolicy.Spec.Vhost = "new-vhost"
Expect(apierrors.IsForbidden(newPolicy.ValidateUpdate(&policy))).To(BeTrue())
})

It("does not allow updates on RabbitmqClusterReference", func() {
newPolicy := policy.DeepCopy()
newPolicy.Spec.RabbitmqClusterReference = RabbitmqClusterReference{
Name: "new-cluster",
Namespace: "default",
}
Expect(apierrors.IsForbidden(newPolicy.ValidateUpdate(&policy))).To(BeTrue())
})

It("allows updates on policy.spec.pattern", func() {
newPolicy := policy.DeepCopy()
newPolicy.Spec.Pattern = "new-pattern"
Expect(newPolicy.ValidateUpdate(&policy)).To(Succeed())
})

It("allows updates on policy.spec.applyTo", func() {
newPolicy := policy.DeepCopy()
newPolicy.Spec.ApplyTo = "queues"
Expect(newPolicy.ValidateUpdate(&policy)).To(Succeed())
})

It("allows updates on policy.spec.priority", func() {
newPolicy := policy.DeepCopy()
newPolicy.Spec.Priority = 1000
Expect(newPolicy.ValidateUpdate(&policy)).To(Succeed())
})

It("allows updates on policy.spec.definition", func() {
newPolicy := policy.DeepCopy()
newPolicy.Spec.Definition = &runtime.RawExtension{Raw: []byte(`{"key":"new-definition-value"}`)}
Expect(newPolicy.ValidateUpdate(&policy)).To(Succeed())
})
})
8 changes: 8 additions & 0 deletions api/v1alpha1/user_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)

// UserSpec defines the desired state of User.
Expand Down Expand Up @@ -74,6 +75,13 @@ type UserList struct {
Items []User `json:"items"`
}

func (u *User) GroupResource() schema.GroupResource {
return schema.GroupResource{
Group: u.GroupVersionKind().Group,
Resource: u.GroupVersionKind().Kind,
}
}

func init() {
SchemeBuilder.Register(&User{}, &UserList{})
}
45 changes: 45 additions & 0 deletions api/v1alpha1/user_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package v1alpha1

import (
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

func (u *User) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(u).
Complete()
}

// +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1alpha1-user,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=users,versions=v1alpha1,name=vuser.kb.io,sideEffects=none,admissionReviewVersions=v1

var _ webhook.Validator = &User{}

// no validation on create
func (u *User) ValidateCreate() error {
return nil
}

// returns error type 'forbidden' for updates on rabbitmqClusterReference
// user.spec.tags can be updated
func (u *User) ValidateUpdate(old runtime.Object) error {
oldUser, ok := old.(*User)
if !ok {
return apierrors.NewBadRequest(fmt.Sprintf("expected a user but got a %T", old))
}

if u.Spec.RabbitmqClusterReference != oldUser.Spec.RabbitmqClusterReference {
return apierrors.NewForbidden(u.GroupResource(), u.Name,
field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), "update on rabbitmqClusterReference is forbidden"))
}
return nil
}

// no validation on delete
func (u *User) ValidateDelete() error {
return nil
}
38 changes: 38 additions & 0 deletions api/v1alpha1/user_webhook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package v1alpha1

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var _ = Describe("user webhook", func() {
var user = User{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: UserSpec{
Tags: []UserTag{"policymaker"},
RabbitmqClusterReference: RabbitmqClusterReference{
Name: "a-cluster",
Namespace: "default",
},
},
}

It("does not allow updates on RabbitmqClusterReference", func() {
new := user.DeepCopy()
new.Spec.RabbitmqClusterReference = RabbitmqClusterReference{
Name: "new-cluster",
Namespace: "default",
}
Expect(apierrors.IsForbidden(new.ValidateUpdate(&user))).To(BeTrue())
})

It("allows update on tags", func() {
new := user.DeepCopy()
new.Spec.Tags = []UserTag{"monitoring"}
Expect(new.ValidateUpdate(&user)).To(Succeed())
})
})
8 changes: 8 additions & 0 deletions api/v1alpha1/vhost_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)

// VhostSpec defines the desired state of Vhost
Expand Down Expand Up @@ -54,6 +55,13 @@ type VhostList struct {
Items []Vhost `json:"items"`
}

func (v *Vhost) GroupResource() schema.GroupResource {
return schema.GroupResource{
Group: v.GroupVersionKind().Group,
Resource: v.GroupVersionKind().Kind,
}
}

func init() {
SchemeBuilder.Register(&Vhost{}, &VhostList{})
}
52 changes: 52 additions & 0 deletions api/v1alpha1/vhost_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package v1alpha1

import (
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

func (r *Vhost) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}

// +kubebuilder:webhook:verbs=create;update,path=/validate-rabbitmq-com-v1alpha1-vhost,mutating=false,failurePolicy=fail,groups=rabbitmq.com,resources=vhosts,versions=v1alpha1,name=vvhost.kb.io,sideEffects=none,admissionReviewVersions=v1

var _ webhook.Validator = &Vhost{}

// no validation on create
func (v *Vhost) ValidateCreate() error {
return nil
}

// returns error type 'forbidden' for updates on vhost name and rabbitmqClusterReference
// vhost.spec.tracing can be updated
func (v *Vhost) ValidateUpdate(old runtime.Object) error {
oldVhost, ok := old.(*Vhost)
if !ok {
return apierrors.NewBadRequest(fmt.Sprintf("expected a vhost but got a %T", old))
}

detailMsg := "updates on name and rabbitmqClusterReference are all forbidden"
if v.Spec.Name != oldVhost.Spec.Name {
return apierrors.NewForbidden(v.GroupResource(), v.Name,
field.Forbidden(field.NewPath("spec", "name"), detailMsg))
}

if v.Spec.RabbitmqClusterReference != oldVhost.Spec.RabbitmqClusterReference {
return apierrors.NewForbidden(v.GroupResource(), v.Name,
field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg))
}

return nil
}

// no validation on delete
func (v *Vhost) ValidateDelete() error {
return nil
}
46 changes: 46 additions & 0 deletions api/v1alpha1/vhost_webhook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package v1alpha1

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var _ = Describe("vhost webhook", func() {

var vhost = Vhost{
ObjectMeta: metav1.ObjectMeta{
Name: "test-vhost",
},
Spec: VhostSpec{
Name: "test",
Tracing: false,
RabbitmqClusterReference: RabbitmqClusterReference{
Name: "a-cluster",
Namespace: "default",
},
},
}

It("does not allow updates on vhost name", func() {
newVhost := vhost.DeepCopy()
newVhost.Spec.Name = "new-name"
Expect(apierrors.IsForbidden(newVhost.ValidateUpdate(&vhost))).To(BeTrue())
})

It("does not allow updates on RabbitmqClusterReference", func() {
newVhost := vhost.DeepCopy()
newVhost.Spec.RabbitmqClusterReference = RabbitmqClusterReference{
Name: "new-cluster",
Namespace: "default",
}
Expect(apierrors.IsForbidden(newVhost.ValidateUpdate(&vhost))).To(BeTrue())
})

It("allows updates on vhost.spec.tracing", func() {
newVhost := vhost.DeepCopy()
newVhost.Spec.Tracing = true
Expect(newVhost.ValidateUpdate(&vhost)).To(Succeed())
})
})
Loading