Skip to content

Commit 4a51055

Browse files
authored
Merge pull request #296 from rabbitmq/connection-secret-tls
Support TLS for connectionSecret
2 parents 0993597 + 135741d commit 4a51055

File tree

7 files changed

+323
-54
lines changed

7 files changed

+323
-54
lines changed

docs/examples/non-operator-managed-rabbitmq/queue.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ type: Opaque
77
stringData:
88
username: a-user # an existing user
99
password: a-secure-password
10-
uri: my.rabbit:15672 # uri for the management api
10+
uri: https://my.rabbit:15672 # uri for the management api; when scheme is not provided in uri, operator defalts to 'http'
1111
---
1212
apiVersion: rabbitmq.com/v1beta1
1313
kind: Queue
@@ -20,4 +20,4 @@ spec:
2020
durable: true
2121
rabbitmqClusterReference:
2222
connectionSecret:
23-
name: my-rabbitmq-creds # provided instead of a rabbitmqcluster name; secret must contain keys username, password and uri, and be in the same namespace as the object
23+
name: my-rabbitmq-creds # provided instead of a rabbitmqcluster name; secret must contain keys username, password and uri, and be in the same namespace as the object

internal/cluster_reference.go

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"net/url"
78
"strings"
89

910
rabbitmqv1beta1 "github.com/rabbitmq/cluster-operator/api/v1beta1"
@@ -40,11 +41,7 @@ func ParseRabbitmqClusterReference(ctx context.Context, c client.Client, rmq top
4041
if err := c.Get(ctx, types.NamespacedName{Namespace: requestNamespace, Name: rmq.ConnectionSecret.Name}, secret); err != nil {
4142
return nil, false, err
4243
}
43-
creds, err := readCredentialsFromKubernetesSecret(secret)
44-
if err != nil {
45-
return nil, false, fmt.Errorf("unable to retrieve information from Kubernetes secret %s: %w", secret.Name, err)
46-
}
47-
return creds, false, nil
44+
return readCredentialsFromKubernetesSecret(secret)
4845
}
4946

5047
var namespace string
@@ -101,7 +98,7 @@ func ParseRabbitmqClusterReference(ctx context.Context, c client.Client, rmq top
10198
return nil, false, err
10299
}
103100

104-
endpoint, err := managementURI(svc)
101+
endpoint, err := managementURI(svc, cluster.TLSEnabled())
105102
if err != nil {
106103
return nil, false, fmt.Errorf("failed to get endpoint from specified rabbitmqcluster: %w", err)
107104
}
@@ -133,18 +130,34 @@ func AllowedNamespace(rmq topology.RabbitmqClusterReference, requestNamespace st
133130
return true
134131
}
135132

136-
func readCredentialsFromKubernetesSecret(secret *corev1.Secret) (ConnectionCredentials, error) {
133+
func readCredentialsFromKubernetesSecret(secret *corev1.Secret) (ConnectionCredentials, bool, error) {
137134
if secret == nil {
138-
return nil, errors.New("unable to extract data from nil secret")
135+
return nil, false, fmt.Errorf("unable to retrieve information from Kubernetes secret %s: %w", secret.Name, errors.New("nil secret"))
136+
}
137+
138+
uBytes, found := secret.Data["uri"]
139+
if !found {
140+
return nil, false, keyMissingErr("uri")
141+
}
142+
143+
uri := string(uBytes)
144+
if !strings.HasPrefix(uri, "http") {
145+
uri = "http://" + uri // set scheme to http if not provided
146+
}
147+
var tlsEnabled bool
148+
if parsed, err := url.Parse(uri); err != nil {
149+
return nil, false, err
150+
} else if parsed.Scheme == "https" {
151+
tlsEnabled = true
139152
}
140153

141154
return ClusterCredentials{
142155
data: map[string][]byte{
143156
"username": secret.Data["username"],
144157
"password": secret.Data["password"],
145-
"uri": secret.Data["uri"],
158+
"uri": []byte(uri),
146159
},
147-
}, nil
160+
}, tlsEnabled, nil
148161
}
149162

150163
func readUsernamePassword(secret *corev1.Secret) (string, string, error) {
@@ -155,13 +168,17 @@ func readUsernamePassword(secret *corev1.Secret) (string, string, error) {
155168
return string(secret.Data["username"]), string(secret.Data["password"]), nil
156169
}
157170

158-
func managementURI(svc *corev1.Service) (string, error) {
171+
func managementURI(svc *corev1.Service, tlsEnabled bool) (string, error) {
159172
port := managementPort(svc)
160173
if port == 0 {
161174
return "", fmt.Errorf("failed to find 'management' or 'management-tls' from service %s", svc.Name)
162175
}
163176

164-
return fmt.Sprintf("%s:%d", serviceDNSAddress(svc), port), nil
177+
scheme := "http"
178+
if tlsEnabled {
179+
scheme = "https"
180+
}
181+
return fmt.Sprintf("%s://%s:%d", scheme, serviceDNSAddress(svc), port), nil
165182
}
166183

167184
// serviceDNSAddress returns the cluster-local DNS entry associated

internal/cluster_reference_test.go

Lines changed: 169 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ var _ = Describe("ParseRabbitmqClusterReference", func() {
2929
ctx = context.Background()
3030
namespace = "rabbitmq-system"
3131
)
32+
3233
JustBeforeEach(func() {
3334
s := scheme.Scheme
3435
s.AddKnownTypes(rabbitmqv1beta1.SchemeBuilder.GroupVersion, &rabbitmqv1beta1.RabbitmqCluster{})
@@ -89,8 +90,10 @@ var _ = Describe("ParseRabbitmqClusterReference", func() {
8990
Expect(tlsEnabled).To(BeFalse())
9091
usernameBytes, _ := credsProvider.Data("username")
9192
passwordBytes, _ := credsProvider.Data("password")
93+
uriBytes, _ := credsProvider.Data("uri")
9294
Expect(usernameBytes).To(Equal([]byte(existingRabbitMQUsername)))
9395
Expect(passwordBytes).To(Equal([]byte(existingRabbitMQPassword)))
96+
Expect(uriBytes).To(Equal([]byte("http://rmq.rabbitmq-system.svc:15672")))
9497
})
9598

9699
When("RabbitmqCluster does not have status.defaultUser set", func() {
@@ -172,44 +175,188 @@ var _ = Describe("ParseRabbitmqClusterReference", func() {
172175
It("should return the expected credentials", func() {
173176
usernameBytes, _ := credsProv.Data("username")
174177
passwordBytes, _ := credsProv.Data("password")
178+
uriBytes, _ := credsProv.Data("uri")
175179
Expect(usernameBytes).To(Equal([]byte(existingRabbitMQUsername)))
176180
Expect(passwordBytes).To(Equal([]byte(existingRabbitMQPassword)))
181+
Expect(uriBytes).To(Equal([]byte("http://rmq.rabbitmq-system.svc:15672")))
177182
})
178183
})
179184
})
180-
When("spec.rabbitmqClusterReference.connectionSecret is set instead of cluster name", func() {
185+
186+
When("the RabbitmqCluster is configured with TLS", func() {
181187
BeforeEach(func() {
182-
connectionSecret := &corev1.Secret{
188+
existingRabbitMQCluster = &rabbitmqv1beta1.RabbitmqCluster{
189+
ObjectMeta: metav1.ObjectMeta{
190+
Name: "rmq",
191+
Namespace: namespace,
192+
},
193+
Spec: rabbitmqv1beta1.RabbitmqClusterSpec{
194+
TLS: rabbitmqv1beta1.TLSSpec{
195+
SecretName: "a-tls-secret",
196+
DisableNonTLSListeners: true,
197+
},
198+
},
199+
Status: rabbitmqv1beta1.RabbitmqClusterStatus{
200+
Binding: &corev1.LocalObjectReference{
201+
Name: "rmq-default-user-credentials",
202+
},
203+
DefaultUser: &rabbitmqv1beta1.RabbitmqClusterDefaultUser{
204+
ServiceReference: &rabbitmqv1beta1.RabbitmqClusterServiceReference{
205+
Name: "rmq",
206+
Namespace: namespace,
207+
},
208+
},
209+
},
210+
}
211+
existingCredentialSecret = &corev1.Secret{
183212
ObjectMeta: metav1.ObjectMeta{
184-
Name: "rmq-connection-info",
213+
Name: "rmq-default-user-credentials",
185214
Namespace: namespace,
186215
},
187216
Data: map[string][]byte{
188-
"uri": []byte("10.0.0.0:15671"),
189-
"username": []byte("test-user"),
190-
"password": []byte("test-password"),
217+
"username": []byte(existingRabbitMQUsername),
218+
"password": []byte(existingRabbitMQPassword),
191219
},
192220
}
193-
objs = []runtime.Object{connectionSecret}
194-
})
195-
196-
It("returns the expected connection information", func() {
197-
credsProvider, tlsEnabled, err := internal.ParseRabbitmqClusterReference(ctx, fakeClient,
198-
topology.RabbitmqClusterReference{
199-
ConnectionSecret: &corev1.LocalObjectReference{
200-
Name: "rmq-connection-info",
221+
existingService = &corev1.Service{
222+
ObjectMeta: metav1.ObjectMeta{
223+
Name: "rmq",
224+
Namespace: namespace,
225+
},
226+
Spec: corev1.ServiceSpec{
227+
ClusterIP: "1.2.3.4",
228+
Ports: []corev1.ServicePort{
229+
{
230+
Name: "management-tls",
231+
Port: int32(15671),
232+
},
201233
},
202234
},
203-
namespace)
235+
}
236+
objs = []runtime.Object{existingRabbitMQCluster, existingCredentialSecret, existingService}
237+
})
238+
239+
It("returns correct creds in connectionCredentials", func() {
240+
credsProvider, tlsEnabled, err := internal.ParseRabbitmqClusterReference(ctx, fakeClient, topology.RabbitmqClusterReference{Name: existingRabbitMQCluster.Name}, existingRabbitMQCluster.Namespace)
204241
Expect(err).NotTo(HaveOccurred())
205242

206-
Expect(tlsEnabled).To(BeFalse())
207-
returnedUser, _ := credsProvider.Data("username")
208-
returnedPass, _ := credsProvider.Data("password")
209-
returnedURI, _ := credsProvider.Data("uri")
210-
Expect(string(returnedUser)).To(Equal("test-user"))
211-
Expect(string(returnedPass)).To(Equal("test-password"))
212-
Expect(string(returnedURI)).To(Equal("10.0.0.0:15671"))
243+
Expect(tlsEnabled).To(BeTrue())
244+
usernameBytes, _ := credsProvider.Data("username")
245+
passwordBytes, _ := credsProvider.Data("password")
246+
uriBytes, _ := credsProvider.Data("uri")
247+
Expect(usernameBytes).To(Equal([]byte(existingRabbitMQUsername)))
248+
Expect(passwordBytes).To(Equal([]byte(existingRabbitMQPassword)))
249+
Expect(uriBytes).To(Equal([]byte("https://rmq.rabbitmq-system.svc:15671")))
250+
})
251+
})
252+
253+
Context("spec.rabbitmqClusterReference.connectionSecret is set", func() {
254+
When("uri has no scheme defined", func() {
255+
BeforeEach(func() {
256+
noSchemeSecret := &corev1.Secret{
257+
ObjectMeta: metav1.ObjectMeta{
258+
Name: "rmq-connection-info",
259+
Namespace: namespace,
260+
},
261+
Data: map[string][]byte{
262+
"uri": []byte("10.0.0.0:15672"),
263+
"username": []byte("test-user"),
264+
"password": []byte("test-password"),
265+
},
266+
}
267+
objs = []runtime.Object{noSchemeSecret}
268+
})
269+
270+
It("returns the expected connection information", func() {
271+
credsProvider, tlsEnabled, err := internal.ParseRabbitmqClusterReference(ctx, fakeClient,
272+
topology.RabbitmqClusterReference{
273+
ConnectionSecret: &corev1.LocalObjectReference{
274+
Name: "rmq-connection-info",
275+
},
276+
},
277+
namespace)
278+
Expect(err).NotTo(HaveOccurred())
279+
280+
Expect(tlsEnabled).To(BeFalse())
281+
returnedUser, _ := credsProvider.Data("username")
282+
returnedPass, _ := credsProvider.Data("password")
283+
returnedURI, _ := credsProvider.Data("uri")
284+
Expect(string(returnedUser)).To(Equal("test-user"))
285+
Expect(string(returnedPass)).To(Equal("test-password"))
286+
Expect(string(returnedURI)).To(Equal("http://10.0.0.0:15672"))
287+
})
288+
})
289+
290+
When("uri sets http as the scheme", func() {
291+
BeforeEach(func() {
292+
httpSchemeSecret := &corev1.Secret{
293+
ObjectMeta: metav1.ObjectMeta{
294+
Name: "rmq-connection-info",
295+
Namespace: namespace,
296+
},
297+
Data: map[string][]byte{
298+
"uri": []byte("http://10.0.0.0:15672"),
299+
"username": []byte("test-user"),
300+
"password": []byte("test-password"),
301+
},
302+
}
303+
objs = []runtime.Object{httpSchemeSecret}
304+
})
305+
306+
It("returns the expected connection information", func() {
307+
credsProvider, tlsEnabled, err := internal.ParseRabbitmqClusterReference(ctx, fakeClient,
308+
topology.RabbitmqClusterReference{
309+
ConnectionSecret: &corev1.LocalObjectReference{
310+
Name: "rmq-connection-info",
311+
},
312+
},
313+
namespace)
314+
Expect(err).NotTo(HaveOccurred())
315+
316+
Expect(tlsEnabled).To(BeFalse())
317+
returnedUser, _ := credsProvider.Data("username")
318+
returnedPass, _ := credsProvider.Data("password")
319+
returnedURI, _ := credsProvider.Data("uri")
320+
Expect(string(returnedUser)).To(Equal("test-user"))
321+
Expect(string(returnedPass)).To(Equal("test-password"))
322+
Expect(string(returnedURI)).To(Equal("http://10.0.0.0:15672"))
323+
})
324+
})
325+
326+
When("uri sets https as the scheme", func() {
327+
BeforeEach(func() {
328+
httpsSchemeSecret := &corev1.Secret{
329+
ObjectMeta: metav1.ObjectMeta{
330+
Name: "rmq-connection-info",
331+
Namespace: namespace,
332+
},
333+
Data: map[string][]byte{
334+
"uri": []byte("https://10.0.0.0:15671"),
335+
"username": []byte("test-user"),
336+
"password": []byte("test-password"),
337+
},
338+
}
339+
objs = []runtime.Object{httpsSchemeSecret}
340+
})
341+
342+
It("returns the expected connection information", func() {
343+
credsProvider, tlsEnabled, err := internal.ParseRabbitmqClusterReference(ctx, fakeClient,
344+
topology.RabbitmqClusterReference{
345+
ConnectionSecret: &corev1.LocalObjectReference{
346+
Name: "rmq-connection-info",
347+
},
348+
},
349+
namespace)
350+
Expect(err).NotTo(HaveOccurred())
351+
352+
Expect(tlsEnabled).To(BeTrue())
353+
returnedUser, _ := credsProvider.Data("username")
354+
returnedPass, _ := credsProvider.Data("password")
355+
returnedURI, _ := credsProvider.Data("uri")
356+
Expect(string(returnedUser)).To(Equal("test-user"))
357+
Expect(string(returnedPass)).To(Equal("test-password"))
358+
Expect(string(returnedURI)).To(Equal("https://10.0.0.0:15671"))
359+
})
213360
})
214361
})
215362
})
@@ -271,5 +418,4 @@ var _ = Describe("AllowedNamespace", func() {
271418
Expect(internal.AllowedNamespace(ref, "whatever", cluster)).To(BeTrue())
272419
})
273420
})
274-
275421
})

internal/rabbitmq_client_factory.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func generateRabbitholeClient(connectionCreds ConnectionCredentials, tlsEnabled
6565
return nil, keyMissingErr("password")
6666
}
6767

68-
endpoint, found := connectionCreds.Data("uri")
68+
uri, found := connectionCreds.Data("uri")
6969
if !found {
7070
return nil, keyMissingErr("uri")
7171
}
@@ -76,12 +76,12 @@ func generateRabbitholeClient(connectionCreds ConnectionCredentials, tlsEnabled
7676
cfg.RootCAs = certPool
7777

7878
transport := &http.Transport{TLSClientConfig: cfg}
79-
rabbitmqClient, err = rabbithole.NewTLSClient(fmt.Sprintf("https://%s", string(endpoint)), string(defaultUser), string(defaultUserPass), transport)
79+
rabbitmqClient, err = rabbithole.NewTLSClient(fmt.Sprintf("%s", string(uri)), string(defaultUser), string(defaultUserPass), transport)
8080
if err != nil {
8181
return nil, fmt.Errorf("failed to instantiate rabbit rabbitmqClient: %v", err)
8282
}
8383
} else {
84-
rabbitmqClient, err = rabbithole.NewClient(fmt.Sprintf("http://%s", string(endpoint)), string(defaultUser), string(defaultUserPass))
84+
rabbitmqClient, err = rabbithole.NewClient(fmt.Sprintf("%s", string(uri)), string(defaultUser), string(defaultUserPass))
8585
if err != nil {
8686
return nil, fmt.Errorf("failed to instantiate rabbit rabbitmqClient: %v", err)
8787
}

internal/rabbitmq_client_factory_test.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package internal_test
33
import (
44
"crypto/x509"
55
"errors"
6-
"fmt"
76
"github.com/rabbitmq/messaging-topology-operator/internal/internalfakes"
87
"io/ioutil"
98
"net/http"
@@ -47,9 +46,7 @@ var _ = Describe("ParseRabbitmqClusterReference", func() {
4746
FakeConnectionCredentials = &internalfakes.FakeConnectionCredentials{}
4847
FakeConnectionCredentials.DataReturnsOnCall(0, []byte(existingRabbitMQUsername), true)
4948
FakeConnectionCredentials.DataReturnsOnCall(1, []byte(existingRabbitMQPassword), true)
50-
FakeConnectionCredentials.DataReturnsOnCall(2, []byte(fmt.Sprintf("%s:%s",
51-
fakeRabbitMQURL.Hostname(),
52-
fakeRabbitMQURL.Port())), true)
49+
FakeConnectionCredentials.DataReturnsOnCall(2, []byte(fakeRabbitMQURL.String()), true)
5350

5451
fakeRabbitMQServer.RouteToHandler("PUT", "/api/users/example-user", func(w http.ResponseWriter, req *http.Request) {
5552
user, password, ok := req.BasicAuth()
@@ -219,7 +216,7 @@ var _ = Describe("ParseRabbitmqClusterReference", func() {
219216
FakeConnectionCredentials = &internalfakes.FakeConnectionCredentials{}
220217
FakeConnectionCredentials.DataReturnsOnCall(0, []byte(existingRabbitMQUsername), true)
221218
FakeConnectionCredentials.DataReturnsOnCall(1, []byte(existingRabbitMQPassword), true)
222-
FakeConnectionCredentials.DataReturnsOnCall(2, []byte(fmt.Sprintf("%s:%d", fakeRabbitMQURL.Hostname(), fakeRabbitMQPort)), true)
219+
FakeConnectionCredentials.DataReturnsOnCall(2, []byte(fakeRabbitMQURL.String()), true)
223220

224221
fakeRabbitMQServer.RouteToHandler("PUT", "/api/users/example-user", func(w http.ResponseWriter, req *http.Request) {
225222
user, password, ok := req.BasicAuth()

0 commit comments

Comments
 (0)