Skip to content

Commit 43111e1

Browse files
author
Rodrigo Valin
authored
Enables TLS on Prometheus endpoint. (mongodb#897)
1 parent 671f69a commit 43111e1

File tree

9 files changed

+233
-80
lines changed

9 files changed

+233
-80
lines changed

Diff for: .dockerignore

+5
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,10 @@ zz_*
44
vendor/
55
scripts/
66
.git/
7+
bin/
8+
testbin/
9+
.mypy_cache/
10+
main
11+
__debug_bin
712
# allow agent LICENSE
813
!scripts/dev/templates/agent/LICENSE

Diff for: api/v1/mongodbcommunity_types.go

+16
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ type Prometheus struct {
146146
// Indicates path to the metrics endpoint.
147147
// +kubebuilder:validation:Pattern=^\/[a-z0-9]+$
148148
MetricsPath string `json:"metricsPath,omitempty"`
149+
150+
// Name of a Secret (type kubernetes.io/tls) holding the certificates to use in the
151+
// Prometheus endpoint.
152+
// +optional
153+
TLSSecretRef SecretKeyReference `json:"tlsSecretKeyRef,omitempty"`
149154
}
150155

151156
func (p Prometheus) GetPasswordKey() string {
@@ -708,12 +713,23 @@ func (m MongoDBCommunity) TLSSecretNamespacedName() types.NamespacedName {
708713
return types.NamespacedName{Name: m.Spec.Security.TLS.CertificateKeySecret.Name, Namespace: m.Namespace}
709714
}
710715

716+
// PrometheusTLSSecretNamespacedName will get the namespaced name of the Secret containing the server certificate and key
717+
func (m MongoDBCommunity) PrometheusTLSSecretNamespacedName() types.NamespacedName {
718+
return types.NamespacedName{Name: m.Spec.Prometheus.TLSSecretRef.Name, Namespace: m.Namespace}
719+
}
720+
711721
// TLSOperatorSecretNamespacedName will get the namespaced name of the Secret created by the operator
712722
// containing the combined certificate and key.
713723
func (m MongoDBCommunity) TLSOperatorSecretNamespacedName() types.NamespacedName {
714724
return types.NamespacedName{Name: m.Name + "-server-certificate-key", Namespace: m.Namespace}
715725
}
716726

727+
// PrometheusTLSOperatorSecretNamespacedName will get the namespaced name of the Secret created by the operator
728+
// containing the combined certificate and key.
729+
func (m MongoDBCommunity) PrometheusTLSOperatorSecretNamespacedName() types.NamespacedName {
730+
return types.NamespacedName{Name: m.Name + "-prometheus-certificate-key", Namespace: m.Namespace}
731+
}
732+
717733
func (m MongoDBCommunity) NamespacedName() types.NamespacedName {
718734
return types.NamespacedName{Name: m.Name, Namespace: m.Namespace}
719735
}

Diff for: config/crd/bases/mongodbcommunity.mongodb.com_mongodbcommunity.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,21 @@ spec:
113113
description: Port where metrics endpoint will bind to. Defaults
114114
to 9216.
115115
type: integer
116+
tlsSecretKeyRef:
117+
description: Name of a Secret (type kubernetes.io/tls) holding
118+
the certificates to use in the Prometheus endpoint.
119+
properties:
120+
key:
121+
description: Key is the key in the secret storing this password.
122+
Defaults to "password"
123+
type: string
124+
name:
125+
description: Name is the name of the secret storing this user's
126+
password
127+
type: string
128+
required:
129+
- name
130+
type: object
116131
username:
117132
description: HTTP Basic Auth Username for metrics endpoint.
118133
type: string

Diff for: controllers/mongodb_tls.go

+55-11
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (r *ReplicaSetReconciler) validateTLSConfig(mdb mdbv1.MongoDBCommunity) (bo
8282

8383
// validate whether the secret contains "tls.crt" and "tls.key", or it contains "tls.pem"
8484
// if it contains all three, then the pem entry should be equal to the concatenation of crt and key
85-
_, err = getPemOrConcatenatedCrtAndKey(r.client, mdb)
85+
_, err = getPemOrConcatenatedCrtAndKey(r.client, mdb, mdb.TLSSecretNamespacedName())
8686
if err != nil {
8787
r.log.Warnf(err.Error())
8888
return false, nil
@@ -102,7 +102,7 @@ func getTLSConfigModification(getUpdateCreator secret.GetUpdateCreator, mdb mdbv
102102
return automationconfig.NOOP(), nil
103103
}
104104

105-
certKey, err := getPemOrConcatenatedCrtAndKey(getUpdateCreator, mdb)
105+
certKey, err := getPemOrConcatenatedCrtAndKey(getUpdateCreator, mdb, mdb.TLSSecretNamespacedName())
106106
if err != nil {
107107
return automationconfig.NOOP(), err
108108
}
@@ -111,13 +111,13 @@ func getTLSConfigModification(getUpdateCreator secret.GetUpdateCreator, mdb mdbv
111111
}
112112

113113
// getCertAndKey will fetch the certificate and key from the user-provided Secret.
114-
func getCertAndKey(getter secret.Getter, mdb mdbv1.MongoDBCommunity) string {
115-
cert, err := secret.ReadKey(getter, tlsSecretCertName, mdb.TLSSecretNamespacedName())
114+
func getCertAndKey(getter secret.Getter, mdb mdbv1.MongoDBCommunity, secretName types.NamespacedName) string {
115+
cert, err := secret.ReadKey(getter, tlsSecretCertName, secretName)
116116
if err != nil {
117117
return ""
118118
}
119119

120-
key, err := secret.ReadKey(getter, tlsSecretKeyName, mdb.TLSSecretNamespacedName())
120+
key, err := secret.ReadKey(getter, tlsSecretKeyName, secretName)
121121
if err != nil {
122122
return ""
123123
}
@@ -126,8 +126,8 @@ func getCertAndKey(getter secret.Getter, mdb mdbv1.MongoDBCommunity) string {
126126
}
127127

128128
// getPem will fetch the pem from the user-provided secret
129-
func getPem(getter secret.Getter, mdb mdbv1.MongoDBCommunity) string {
130-
pem, err := secret.ReadKey(getter, tlsSecretPemName, mdb.TLSSecretNamespacedName())
129+
func getPem(getter secret.Getter, mdb mdbv1.MongoDBCommunity, secretName types.NamespacedName) string {
130+
pem, err := secret.ReadKey(getter, tlsSecretPemName, secretName)
131131
if err != nil {
132132
return ""
133133
}
@@ -144,9 +144,9 @@ func combineCertificateAndKey(cert, key string) string {
144144
// This is either the tls.pem entry in the given secret, or the concatenation
145145
// of tls.crt and tls.key
146146
// It performs a basic validation on the entries.
147-
func getPemOrConcatenatedCrtAndKey(getter secret.Getter, mdb mdbv1.MongoDBCommunity) (string, error) {
148-
certKey := getCertAndKey(getter, mdb)
149-
pem := getPem(getter, mdb)
147+
func getPemOrConcatenatedCrtAndKey(getter secret.Getter, mdb mdbv1.MongoDBCommunity, secretName types.NamespacedName) (string, error) {
148+
certKey := getCertAndKey(getter, mdb, secretName)
149+
pem := getPem(getter, mdb, secretName)
150150
if certKey == "" && pem == "" {
151151
return "", fmt.Errorf(`Neither "%s" nor the pair "%s"/"%s" were present in the TLS secret`, tlsSecretPemName, tlsSecretCertName, tlsSecretKeyName)
152152
}
@@ -165,7 +165,7 @@ func getPemOrConcatenatedCrtAndKey(getter secret.Getter, mdb mdbv1.MongoDBCommun
165165
// ensureTLSSecret will create or update the operator-managed Secret containing
166166
// the concatenated certificate and key from the user-provided Secret.
167167
func ensureTLSSecret(getUpdateCreator secret.GetUpdateCreator, mdb mdbv1.MongoDBCommunity) error {
168-
certKey, err := getPemOrConcatenatedCrtAndKey(getUpdateCreator, mdb)
168+
certKey, err := getPemOrConcatenatedCrtAndKey(getUpdateCreator, mdb, mdb.TLSSecretNamespacedName())
169169
if err != nil {
170170
return err
171171
}
@@ -182,6 +182,26 @@ func ensureTLSSecret(getUpdateCreator secret.GetUpdateCreator, mdb mdbv1.MongoDB
182182
return secret.CreateOrUpdate(getUpdateCreator, operatorSecret)
183183
}
184184

185+
// ensurePrometheusTLSSecret will create or update the operator-managed Secret containing
186+
// the concatenated certificate and key from the user-provided Secret.
187+
func ensurePrometheusTLSSecret(getUpdateCreator secret.GetUpdateCreator, mdb mdbv1.MongoDBCommunity) error {
188+
certKey, err := getPemOrConcatenatedCrtAndKey(getUpdateCreator, mdb, mdb.DeepCopy().PrometheusTLSSecretNamespacedName())
189+
if err != nil {
190+
return err
191+
}
192+
// Calculate file name from certificate and key
193+
fileName := tlsOperatorSecretFileName(certKey)
194+
195+
operatorSecret := secret.Builder().
196+
SetName(mdb.PrometheusTLSOperatorSecretNamespacedName().Name).
197+
SetNamespace(mdb.PrometheusTLSOperatorSecretNamespacedName().Namespace).
198+
SetField(fileName, certKey).
199+
SetOwnerReferences(mdb.GetOwnerReferences()).
200+
Build()
201+
202+
return secret.CreateOrUpdate(getUpdateCreator, operatorSecret)
203+
}
204+
185205
// tlsOperatorSecretFileName calculates the file name to use for the mounted
186206
// certificate-key file. The name is based on the hash of the combined cert and key.
187207
// If the certificate or key changes, the file path changes as well which will trigger
@@ -250,3 +270,27 @@ func buildTLSPodSpecModification(mdb mdbv1.MongoDBCommunity) podtemplatespec.Mod
250270
podtemplatespec.WithVolumeMounts(construct.MongodbName, tlsSecretVolumeMount, caVolumeMount),
251271
)
252272
}
273+
274+
// buildTLSPrometheus adds the TLS mounts for Prometheus.
275+
func buildTLSPrometheus(mdb mdbv1.MongoDBCommunity) podtemplatespec.Modification {
276+
if mdb.Spec.Prometheus == nil || mdb.Spec.Prometheus.TLSSecretRef.Name == "" {
277+
return podtemplatespec.NOOP()
278+
}
279+
280+
// Configure a volume which mounts the secret holding the server key and certificate
281+
// The same key-certificate pair is used for all servers
282+
tlsSecretVolume := statefulset.CreateVolumeFromSecret("prom-tls-secret", mdb.PrometheusTLSOperatorSecretNamespacedName().Name)
283+
284+
// TODO: Is it ok to use the same `tlsOperatorSecretMountPath`
285+
tlsSecretVolumeMount := statefulset.CreateVolumeMount(tlsSecretVolume.Name, tlsOperatorSecretMountPath, statefulset.WithReadOnly(true))
286+
287+
// MongoDB expects both key and certificate to be provided in a single PEM file
288+
// We are using a secret format where they are stored in separate fields, tls.crt and tls.key
289+
// Because of this we need to use an init container which reads the two files mounted from the secret and combines them into one
290+
return podtemplatespec.Apply(
291+
// podtemplatespec.WithVolume(caVolume),
292+
podtemplatespec.WithVolume(tlsSecretVolume),
293+
podtemplatespec.WithVolumeMounts(construct.AgentName, tlsSecretVolumeMount),
294+
podtemplatespec.WithVolumeMounts(construct.MongodbName, tlsSecretVolumeMount),
295+
)
296+
}

Diff for: controllers/prometheus.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package controllers
2+
3+
import (
4+
"fmt"
5+
6+
mdbv1 "github.com/mongodb/mongodb-kubernetes-operator/api/v1"
7+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/automationconfig"
8+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/secret"
9+
"github.com/pkg/errors"
10+
11+
"k8s.io/apimachinery/pkg/types"
12+
)
13+
14+
const (
15+
listenAddress = "0.0.0.0"
16+
)
17+
18+
// PrometheusModification adds Prometheus configuration to AutomationConfig.
19+
func getPrometheusModification(getUpdateCreator secret.GetUpdateCreator, mdb mdbv1.MongoDBCommunity) (automationconfig.Modification, error) {
20+
if mdb.Spec.Prometheus == nil {
21+
return automationconfig.NOOP(), nil
22+
}
23+
24+
secretNamespacedName := types.NamespacedName{Name: mdb.Spec.Prometheus.PasswordSecretRef.Name, Namespace: mdb.Namespace}
25+
password, err := secret.ReadKey(getUpdateCreator, mdb.Spec.Prometheus.GetPasswordKey(), secretNamespacedName)
26+
if err != nil {
27+
return automationconfig.NOOP(), errors.Errorf("could not configure Prometheus modification: %s", err)
28+
}
29+
30+
var certKey string
31+
var tlsPEMPath string
32+
var scheme string
33+
34+
if mdb.Spec.Prometheus.TLSSecretRef.Name != "" {
35+
certKey, err = getPemOrConcatenatedCrtAndKey(getUpdateCreator, mdb, mdb.PrometheusTLSSecretNamespacedName())
36+
if err != nil {
37+
return automationconfig.NOOP(), err
38+
}
39+
tlsPEMPath = tlsOperatorSecretMountPath + tlsOperatorSecretFileName(certKey)
40+
scheme = "https"
41+
} else {
42+
scheme = "http"
43+
}
44+
45+
return func(config *automationconfig.AutomationConfig) {
46+
promConfig := automationconfig.NewDefaultPrometheus(mdb.Spec.Prometheus.Username)
47+
48+
promConfig.TLSPemPath = tlsPEMPath
49+
promConfig.Scheme = scheme
50+
promConfig.Password = password
51+
52+
if mdb.Spec.Prometheus.Port > 0 {
53+
promConfig.ListenAddress = fmt.Sprintf("%s:%d", listenAddress, mdb.Spec.Prometheus.Port)
54+
}
55+
56+
if mdb.Spec.Prometheus.MetricsPath != "" {
57+
promConfig.MetricsPath = mdb.Spec.Prometheus.MetricsPath
58+
}
59+
60+
config.Prometheus = &promConfig
61+
}, nil
62+
}

Diff for: controllers/replica_set_controller.go

+28-10
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import (
1515
"sigs.k8s.io/controller-runtime/pkg/source"
1616

1717
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/container"
18-
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/secret"
19-
"github.com/mongodb/mongodb-kubernetes-operator/pkg/monitoring"
2018

2119
"github.com/mongodb/mongodb-kubernetes-operator/pkg/util/functions"
2220
"github.com/mongodb/mongodb-kubernetes-operator/pkg/util/merge"
@@ -187,6 +185,14 @@ func (r ReplicaSetReconciler) Reconcile(ctx context.Context, request reconcile.R
187185
)
188186
}
189187

188+
if err := r.ensurePrometheusTLSResources(mdb); err != nil {
189+
return status.Update(r.client.Status(), &mdb,
190+
statusOptions().
191+
withMessage(Error, fmt.Sprintf("Error ensuring TLS resources: %s", err)).
192+
withFailedPhase(),
193+
)
194+
}
195+
190196
if err := r.ensureUserResources(mdb); err != nil {
191197
return status.Update(r.client.Status(), &mdb,
192198
statusOptions().
@@ -299,6 +305,23 @@ func (r *ReplicaSetReconciler) ensureTLSResources(mdb mdbv1.MongoDBCommunity) er
299305
return nil
300306
}
301307

308+
// ensurePrometheusTLSResources creates any required TLS resources that the MongoDBCommunity
309+
// requires for TLS configuration.
310+
func (r *ReplicaSetReconciler) ensurePrometheusTLSResources(mdb mdbv1.MongoDBCommunity) error {
311+
if mdb.Spec.Prometheus == nil || mdb.Spec.Prometheus.TLSSecretRef.Name == "" {
312+
return nil
313+
}
314+
315+
// the TLS secret needs to be created beforehand, as both the StatefulSet and AutomationConfig
316+
// require the contents.
317+
r.log.Infof("Prometheus TLS is enabled, creating/updating TLS secret")
318+
if err := ensurePrometheusTLSSecret(r.client, mdb); err != nil {
319+
return errors.Errorf("could not ensure TLS secret: %s", err)
320+
}
321+
322+
return nil
323+
}
324+
302325
// deployStatefulSet deploys the backing StatefulSet of the MongoDBCommunity resource.
303326
//
304327
// When `Spec.Arbiters` > 0, a second StatefulSet will be created, with the amount
@@ -585,15 +608,9 @@ func (r ReplicaSetReconciler) buildAutomationConfig(mdb mdbv1.MongoDBCommunity)
585608
secretNamespacedName := types.NamespacedName{Name: mdb.Spec.Prometheus.PasswordSecretRef.Name, Namespace: mdb.Namespace}
586609
r.secretWatcher.Watch(secretNamespacedName, mdb.NamespacedName())
587610

588-
password, err := secret.ReadKey(r.client, mdb.Spec.Prometheus.GetPasswordKey(), secretNamespacedName)
611+
prometheusModification, err = getPrometheusModification(r.client, mdb)
589612
if err != nil {
590-
if apiErrors.IsNotFound(err) {
591-
r.log.Infof("Could not read Secret %s. Prometheus will not be configured during this reconciliation, %s", mdb.Spec.Prometheus.PasswordSecretRef.Name, err)
592-
} else {
593-
return automationconfig.AutomationConfig{}, errors.Errorf("could not configure Prometheus modification: %s", err)
594-
}
595-
} else {
596-
prometheusModification = monitoring.PrometheusModification(mdb, password)
613+
return automationconfig.AutomationConfig{}, errors.Errorf("could not enable TLS on Prometheus endpoint: %s", err)
597614
}
598615
}
599616

@@ -661,6 +678,7 @@ func buildStatefulSetModificationFunction(mdb mdbv1.MongoDBCommunity) statefulse
661678
statefulset.WithPodSpecTemplate(
662679
podtemplatespec.Apply(
663680
buildTLSPodSpecModification(mdb),
681+
buildTLSPrometheus(mdb),
664682
),
665683
),
666684

Diff for: pkg/monitoring/monitoring.go

-35
This file was deleted.

0 commit comments

Comments
 (0)