Skip to content

Commit f140a4a

Browse files
authored
Topic permission 191 (#456)
* Add topicpermissions.rabbitmq.com - issue #191 * Reconcile logic and sys tests for topic permission - issue: #191 * Set github workflow to latest go to resolve vul scan errors * Increase timeout when waiting for permissions * Make exchange in topic permission CR required * Update topic permission property description * Remove unused generated sample file
1 parent 8cce72b commit f140a4a

29 files changed

+1972
-16
lines changed

.github/workflows/pr.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
branches: [ main ]
66

77
env:
8-
GO_VERSION: '^1.19.0' # Require Go 1.19 and above, but lower than Go 2.0.0
8+
GO_VERSION: '^1.19.1' # Require Go 1.19 and above, but lower than Go 2.0.0
99

1010
jobs:
1111

PROJECT

+12
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,16 @@ resources:
105105
webhooks:
106106
validation: true
107107
webhookVersion: v1
108+
- api:
109+
crdVersion: v1
110+
namespaced: true
111+
controller: true
112+
domain: rabbitmq.com
113+
group: rabbitmq.com
114+
kind: TopicPermission
115+
path: github.com/rabbitmq/messaging-topology-operator/api/v1beta1
116+
version: v1beta1
117+
webhooks:
118+
validation: true
119+
webhookVersion: v1
108120
version: "3"

api/v1beta1/topicpermission_types.go

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package v1beta1
2+
3+
import (
4+
corev1 "k8s.io/api/core/v1"
5+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
6+
"k8s.io/apimachinery/pkg/runtime/schema"
7+
)
8+
9+
// TopicPermissionSpec defines the desired state of TopicPermission
10+
type TopicPermissionSpec struct {
11+
// Name of an existing user; must provide user or userReference, else create/update will fail; cannot be updated.
12+
User string `json:"user,omitempty"`
13+
// Reference to an existing user.rabbitmq.com object; must provide user or userReference, else create/update will fail; cannot be updated.
14+
UserReference *corev1.LocalObjectReference `json:"userReference,omitempty"`
15+
// Name of an existing vhost; required property; cannot be updated.
16+
// +kubebuilder:validation:Required
17+
Vhost string `json:"vhost"`
18+
// Permissions to grant to the user to a topic exchange; required property.
19+
// +kubebuilder:validation:Required
20+
Permissions TopicPermissionConfig `json:"permissions"`
21+
// Reference to the RabbitmqCluster that both the provided user and vhost are.
22+
// Required property.
23+
// +kubebuilder:validation:Required
24+
RabbitmqClusterReference RabbitmqClusterReference `json:"rabbitmqClusterReference"`
25+
}
26+
27+
type TopicPermissionConfig struct {
28+
// Name of a topic exchange; required property; cannot be updated.
29+
// +kubebuilder:validation:Required
30+
Exchange string `json:"exchange,omitempty"`
31+
// +kubebuilder:validation:Optional
32+
Read string `json:"read,omitempty"`
33+
// +kubebuilder:validation:Optional
34+
Write string `json:"write,omitempty"`
35+
}
36+
37+
// TopicPermissionStatus defines the observed state of TopicPermission
38+
type TopicPermissionStatus struct {
39+
// observedGeneration is the most recent successful generation observed for this TopicPermission. It corresponds to the
40+
// TopicPermission's generation, which is updated on mutation by the API Server.
41+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
42+
Conditions []Condition `json:"conditions,omitempty"`
43+
}
44+
45+
//+kubebuilder:object:root=true
46+
//+kubebuilder:subresource:status
47+
48+
// TopicPermission is the Schema for the topicpermissions API
49+
type TopicPermission struct {
50+
metav1.TypeMeta `json:",inline"`
51+
metav1.ObjectMeta `json:"metadata,omitempty"`
52+
53+
Spec TopicPermissionSpec `json:"spec,omitempty"`
54+
Status TopicPermissionStatus `json:"status,omitempty"`
55+
}
56+
57+
//+kubebuilder:object:root=true
58+
59+
// TopicPermissionList contains a list of TopicPermission
60+
type TopicPermissionList struct {
61+
metav1.TypeMeta `json:",inline"`
62+
metav1.ListMeta `json:"metadata,omitempty"`
63+
Items []TopicPermission `json:"items"`
64+
}
65+
66+
func (t *TopicPermission) GroupResource() schema.GroupResource {
67+
return schema.GroupResource{
68+
Group: t.GroupVersionKind().Group,
69+
Resource: t.GroupVersionKind().Kind,
70+
}
71+
}
72+
73+
func (t *TopicPermission) RabbitReference() RabbitmqClusterReference {
74+
return t.Spec.RabbitmqClusterReference
75+
}
76+
77+
func (t *TopicPermission) SetStatusConditions(c []Condition) {
78+
t.Status.Conditions = c
79+
}
80+
81+
func init() {
82+
SchemeBuilder.Register(&TopicPermission{}, &TopicPermissionList{})
83+
}
+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package v1beta1
2+
3+
import (
4+
"context"
5+
. "github.com/onsi/ginkgo/v2"
6+
. "github.com/onsi/gomega"
7+
corev1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/types"
10+
)
11+
12+
var _ = Describe("TopicPermission", func() {
13+
var (
14+
namespace = "default"
15+
ctx = context.Background()
16+
)
17+
18+
It("creates a topic permission when username is provided", func() {
19+
permission := TopicPermission{
20+
ObjectMeta: metav1.ObjectMeta{
21+
Name: "test-permission-1",
22+
Namespace: namespace,
23+
},
24+
Spec: TopicPermissionSpec{
25+
User: "test",
26+
Vhost: "/test",
27+
Permissions: TopicPermissionConfig{
28+
Exchange: "some",
29+
Read: "^?",
30+
Write: ".*",
31+
},
32+
RabbitmqClusterReference: RabbitmqClusterReference{
33+
Name: "some-cluster",
34+
},
35+
},
36+
}
37+
Expect(k8sClient.Create(ctx, &permission)).To(Succeed())
38+
fetchedTopicPermission := &TopicPermission{}
39+
Expect(k8sClient.Get(ctx, types.NamespacedName{
40+
Name: permission.Name,
41+
Namespace: permission.Namespace,
42+
}, fetchedTopicPermission)).To(Succeed())
43+
Expect(fetchedTopicPermission.Spec.User).To(Equal("test"))
44+
Expect(fetchedTopicPermission.Spec.Vhost).To(Equal("/test"))
45+
Expect(fetchedTopicPermission.Spec.RabbitmqClusterReference.Name).To(Equal("some-cluster"))
46+
47+
Expect(fetchedTopicPermission.Spec.Permissions.Exchange).To(Equal("some"))
48+
Expect(fetchedTopicPermission.Spec.Permissions.Write).To(Equal(".*"))
49+
Expect(fetchedTopicPermission.Spec.Permissions.Read).To(Equal("^?"))
50+
})
51+
52+
It("creates a permission object with user reference is provided", func() {
53+
permission := TopicPermission{
54+
ObjectMeta: metav1.ObjectMeta{
55+
Name: "user-ref-permission",
56+
Namespace: namespace,
57+
},
58+
Spec: TopicPermissionSpec{
59+
UserReference: &corev1.LocalObjectReference{
60+
Name: "a-created-user",
61+
},
62+
Vhost: "/test",
63+
Permissions: TopicPermissionConfig{},
64+
RabbitmqClusterReference: RabbitmqClusterReference{
65+
Name: "some-cluster",
66+
},
67+
},
68+
}
69+
Expect(k8sClient.Create(ctx, &permission)).To(Succeed())
70+
fetchedTopicPermission := &TopicPermission{}
71+
Expect(k8sClient.Get(ctx, types.NamespacedName{
72+
Name: permission.Name,
73+
Namespace: permission.Namespace,
74+
}, fetchedTopicPermission)).To(Succeed())
75+
Expect(fetchedTopicPermission.Spec.UserReference.Name).To(Equal("a-created-user"))
76+
Expect(fetchedTopicPermission.Spec.User).To(Equal(""))
77+
Expect(fetchedTopicPermission.Spec.Vhost).To(Equal("/test"))
78+
Expect(fetchedTopicPermission.Spec.RabbitmqClusterReference.Name).To(Equal("some-cluster"))
79+
})
80+
})
+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package v1beta1
2+
3+
import (
4+
"fmt"
5+
6+
apierrors "k8s.io/apimachinery/pkg/api/errors"
7+
"k8s.io/apimachinery/pkg/runtime"
8+
"k8s.io/apimachinery/pkg/util/validation/field"
9+
ctrl "sigs.k8s.io/controller-runtime"
10+
logf "sigs.k8s.io/controller-runtime/pkg/log"
11+
"sigs.k8s.io/controller-runtime/pkg/webhook"
12+
)
13+
14+
// log is for logging in this package.
15+
var topicpermissionlog = logf.Log.WithName("topicpermission-resource")
16+
17+
func (r *TopicPermission) SetupWebhookWithManager(mgr ctrl.Manager) error {
18+
return ctrl.NewWebhookManagedBy(mgr).
19+
For(r).
20+
Complete()
21+
}
22+
23+
//+kubebuilder:webhook:path=/validate-rabbitmq-com-v1beta1-topicpermission,mutating=false,failurePolicy=fail,sideEffects=None,groups=rabbitmq.com,resources=topicpermissions,verbs=create;update,versions=v1beta1,name=vtopicpermission.kb.io,admissionReviewVersions={v1,v1beta1}
24+
25+
var _ webhook.Validator = &TopicPermission{}
26+
27+
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
28+
func (p *TopicPermission) ValidateCreate() error {
29+
var errorList field.ErrorList
30+
if p.Spec.User == "" && p.Spec.UserReference == nil {
31+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
32+
"must specify either spec.user or spec.userReference"))
33+
return apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList)
34+
}
35+
36+
if p.Spec.User != "" && p.Spec.UserReference != nil {
37+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
38+
"cannot specify spec.user and spec.userReference at the same time"))
39+
return apierrors.NewInvalid(GroupVersion.WithKind("Permission").GroupKind(), p.Name, errorList)
40+
}
41+
return p.Spec.RabbitmqClusterReference.ValidateOnCreate(p.GroupResource(), p.Name)
42+
}
43+
44+
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
45+
func (p *TopicPermission) ValidateUpdate(old runtime.Object) error {
46+
oldPermission, ok := old.(*TopicPermission)
47+
if !ok {
48+
return apierrors.NewBadRequest(fmt.Sprintf("expected a permission but got a %T", old))
49+
}
50+
51+
var errorList field.ErrorList
52+
if p.Spec.User == "" && p.Spec.UserReference == nil {
53+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
54+
"must specify either spec.user or spec.userReference"))
55+
return apierrors.NewInvalid(GroupVersion.WithKind("TopicPermission").GroupKind(), p.Name, errorList)
56+
}
57+
58+
if p.Spec.User != "" && p.Spec.UserReference != nil {
59+
errorList = append(errorList, field.Required(field.NewPath("spec", "user and userReference"),
60+
"cannot specify spec.user and spec.userReference at the same time"))
61+
return apierrors.NewInvalid(GroupVersion.WithKind("TopicPermission").GroupKind(), p.Name, errorList)
62+
}
63+
64+
detailMsg := "updates on exchange, user, userReference, vhost and rabbitmqClusterReference are all forbidden"
65+
if p.Spec.Permissions.Exchange != oldPermission.Spec.Permissions.Exchange {
66+
return apierrors.NewForbidden(p.GroupResource(), p.Name,
67+
field.Forbidden(field.NewPath("spec", "permissions", "exchange"), detailMsg))
68+
}
69+
70+
if p.Spec.User != oldPermission.Spec.User {
71+
return apierrors.NewForbidden(p.GroupResource(), p.Name,
72+
field.Forbidden(field.NewPath("spec", "user"), detailMsg))
73+
}
74+
75+
if userReferenceUpdated(p.Spec.UserReference, oldPermission.Spec.UserReference) {
76+
return apierrors.NewForbidden(p.GroupResource(), p.Name,
77+
field.Forbidden(field.NewPath("spec", "userReference"), detailMsg))
78+
}
79+
80+
if p.Spec.Vhost != oldPermission.Spec.Vhost {
81+
return apierrors.NewForbidden(p.GroupResource(), p.Name,
82+
field.Forbidden(field.NewPath("spec", "vhost"), detailMsg))
83+
}
84+
85+
if !oldPermission.Spec.RabbitmqClusterReference.Matches(&p.Spec.RabbitmqClusterReference) {
86+
return apierrors.NewForbidden(p.GroupResource(), p.Name,
87+
field.Forbidden(field.NewPath("spec", "rabbitmqClusterReference"), detailMsg))
88+
}
89+
return nil
90+
}
91+
92+
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
93+
func (r *TopicPermission) ValidateDelete() error {
94+
return nil
95+
}

0 commit comments

Comments
 (0)