Skip to content

Commit 5086aac

Browse files
CLOUDP-111968: Watch for changes in CA certificate (#910)
* Watch for changes in CA certificate * Watch for CA secret changes * Add missing autogenerated Prometheus methods * Force re-adding service binding annotations * Ensure CA cert secret is extended by modifying dnsnames * Remove usage of caResourceType
1 parent 3d356ce commit 5086aac

File tree

10 files changed

+202
-71
lines changed

10 files changed

+202
-71
lines changed

api/v1/mongodbcommunity_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,10 @@ func (m MongoDBCommunity) PrometheusTLSSecretNamespacedName() types.NamespacedNa
726726
return types.NamespacedName{Name: m.Spec.Prometheus.TLSSecretRef.Name, Namespace: m.Namespace}
727727
}
728728

729+
func (m MongoDBCommunity) TLSOperatorCASecretNamespacedName() types.NamespacedName {
730+
return types.NamespacedName{Name: m.Name + "-ca-certificate", Namespace: m.Namespace}
731+
}
732+
729733
// TLSOperatorSecretNamespacedName will get the namespaced name of the Secret created by the operator
730734
// containing the combined certificate and key.
731735
func (m MongoDBCommunity) TLSOperatorSecretNamespacedName() types.NamespacedName {

api/v1/zz_generated.deepcopy.go

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controllers/mongodb_tls.go

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/mongodb/mongodb-kubernetes-operator/controllers/construct"
99
"github.com/mongodb/mongodb-kubernetes-operator/pkg/automationconfig"
10+
"github.com/pkg/errors"
1011

1112
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/secret"
1213

@@ -15,7 +16,6 @@ import (
1516
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/podtemplatespec"
1617
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/statefulset"
1718

18-
v1 "k8s.io/api/core/v1"
1919
apiErrors "k8s.io/apimachinery/pkg/api/errors"
2020
"k8s.io/apimachinery/pkg/types"
2121

@@ -40,35 +40,17 @@ func (r *ReplicaSetReconciler) validateTLSConfig(mdb mdbv1.MongoDBCommunity) (bo
4040
r.log.Info("Ensuring TLS is correctly configured")
4141

4242
// Ensure CA cert is configured
43-
var caResourceName types.NamespacedName
44-
var caResourceType string
45-
var caData map[string]string
46-
var err error
47-
if mdb.Spec.Security.TLS.CaCertificateSecret != nil {
48-
caResourceName = mdb.TLSCaCertificateSecretNamespacedName()
49-
caResourceType = "Secret"
50-
caData, err = secret.ReadStringData(r.client, caResourceName)
51-
} else {
52-
caResourceName = mdb.TLSConfigMapNamespacedName()
53-
caResourceType = "ConfigMap"
54-
caData, err = configmap.ReadData(r.client, caResourceName)
55-
}
43+
_, err := getCaCrt(r.client, r.client, mdb)
5644

5745
if err != nil {
5846
if apiErrors.IsNotFound(err) {
59-
r.log.Warnf(`CA %s "%s" not found`, caResourceType, caResourceName)
47+
r.log.Warnf("CA resource not found: %s", err)
6048
return false, nil
6149
}
6250

6351
return false, err
6452
}
6553

66-
// Ensure Secret or ConfigMap has a "ca.crt" field
67-
if cert, ok := caData[tlsCACertName]; !ok || cert == "" {
68-
r.log.Warnf(`%s "%s" should have a CA certificate in field "%s"`, caResourceType, caResourceName, tlsCACertName)
69-
return false, nil
70-
}
71-
7254
// Ensure Secret exists
7355
_, err = secret.ReadStringData(r.client, mdb.TLSSecretNamespacedName())
7456
if err != nil {
@@ -91,23 +73,35 @@ func (r *ReplicaSetReconciler) validateTLSConfig(mdb mdbv1.MongoDBCommunity) (bo
9173
// Watch certificate-key secret to handle rotations
9274
r.secretWatcher.Watch(mdb.TLSSecretNamespacedName(), mdb.NamespacedName())
9375

76+
// Watch CA certificate changes
77+
if mdb.Spec.Security.TLS.CaCertificateSecret != nil {
78+
r.secretWatcher.Watch(mdb.TLSCaCertificateSecretNamespacedName(), mdb.NamespacedName())
79+
} else {
80+
r.configMapWatcher.Watch(mdb.TLSConfigMapNamespacedName(), mdb.NamespacedName())
81+
}
82+
9483
r.log.Infof("Successfully validated TLS config")
9584
return true, nil
9685
}
9786

9887
// getTLSConfigModification creates a modification function which enables TLS in the automation config.
9988
// It will also ensure that the combined cert-key secret is created.
100-
func getTLSConfigModification(getUpdateCreator secret.GetUpdateCreator, mdb mdbv1.MongoDBCommunity) (automationconfig.Modification, error) {
89+
func getTLSConfigModification(cmGetter configmap.Getter, secretGetter secret.Getter, mdb mdbv1.MongoDBCommunity) (automationconfig.Modification, error) {
10190
if !mdb.Spec.Security.TLS.Enabled {
10291
return automationconfig.NOOP(), nil
10392
}
10493

105-
certKey, err := getPemOrConcatenatedCrtAndKey(getUpdateCreator, mdb, mdb.TLSSecretNamespacedName())
94+
caCert, err := getCaCrt(cmGetter, secretGetter, mdb)
10695
if err != nil {
10796
return automationconfig.NOOP(), err
10897
}
10998

110-
return tlsConfigModification(mdb, certKey), nil
99+
certKey, err := getPemOrConcatenatedCrtAndKey(secretGetter, mdb, mdb.TLSSecretNamespacedName())
100+
if err != nil {
101+
return automationconfig.NOOP(), err
102+
}
103+
104+
return tlsConfigModification(mdb, certKey, caCert), nil
111105
}
112106

113107
// getCertAndKey will fetch the certificate and key from the user-provided Secret.
@@ -162,6 +156,48 @@ func getPemOrConcatenatedCrtAndKey(getter secret.Getter, mdb mdbv1.MongoDBCommun
162156
return certKey, nil
163157
}
164158

159+
func getCaCrt(cmGetter configmap.Getter, secretGetter secret.Getter, mdb mdbv1.MongoDBCommunity) (string, error) {
160+
var caResourceName types.NamespacedName
161+
var caData map[string]string
162+
var err error
163+
if mdb.Spec.Security.TLS.CaCertificateSecret != nil {
164+
caResourceName = mdb.TLSCaCertificateSecretNamespacedName()
165+
caData, err = secret.ReadStringData(secretGetter, caResourceName)
166+
} else {
167+
caResourceName = mdb.TLSConfigMapNamespacedName()
168+
caData, err = configmap.ReadData(cmGetter, caResourceName)
169+
}
170+
if err != nil {
171+
return "", err
172+
}
173+
174+
if cert, ok := caData[tlsCACertName]; !ok || cert == "" {
175+
return "", errors.Errorf(`CA certificate resource "%s" should have a CA certificate in field "%s"`, caResourceName, tlsCACertName)
176+
} else {
177+
return cert, nil
178+
}
179+
}
180+
181+
// ensureCASecret will create or update the operator managed Secret containing
182+
// the CA certficate from the user provided Secret or ConfigMap.
183+
func ensureCASecret(cmGetter configmap.Getter, secretGetter secret.Getter, getUpdateCreator secret.GetUpdateCreator, mdb mdbv1.MongoDBCommunity) error {
184+
cert, err := getCaCrt(cmGetter, secretGetter, mdb)
185+
if err != nil {
186+
return err
187+
}
188+
189+
caFileName := tlsOperatorSecretFileName(cert)
190+
191+
operatorSecret := secret.Builder().
192+
SetName(mdb.TLSOperatorCASecretNamespacedName().Name).
193+
SetNamespace(mdb.TLSOperatorCASecretNamespacedName().Namespace).
194+
SetField(caFileName, cert).
195+
SetOwnerReferences(mdb.GetOwnerReferences()).
196+
Build()
197+
198+
return secret.CreateOrUpdate(getUpdateCreator, operatorSecret)
199+
}
200+
165201
// ensureTLSSecret will create or update the operator-managed Secret containing
166202
// the concatenated certificate and key from the user-provided Secret.
167203
func ensureTLSSecret(getUpdateCreator secret.GetUpdateCreator, mdb mdbv1.MongoDBCommunity) error {
@@ -214,8 +250,8 @@ func tlsOperatorSecretFileName(certKey string) string {
214250
}
215251

216252
// tlsConfigModification will enable TLS in the automation config.
217-
func tlsConfigModification(mdb mdbv1.MongoDBCommunity, certKey string) automationconfig.Modification {
218-
caCertificatePath := tlsCAMountPath + tlsCACertName
253+
func tlsConfigModification(mdb mdbv1.MongoDBCommunity, certKey, caCert string) automationconfig.Modification {
254+
caCertificatePath := tlsCAMountPath + tlsOperatorSecretFileName(caCert)
219255
certificateKeyPath := tlsOperatorSecretMountPath + tlsOperatorSecretFileName(certKey)
220256

221257
mode := automationconfig.TLSModeRequired
@@ -247,12 +283,7 @@ func buildTLSPodSpecModification(mdb mdbv1.MongoDBCommunity) podtemplatespec.Mod
247283

248284
// Configure a volume which mounts the CA certificate from either a Secret or a ConfigMap
249285
// The certificate is used by both mongod and the agent
250-
var caVolume v1.Volume
251-
if mdb.Spec.Security.TLS.CaCertificateSecret != nil {
252-
caVolume = statefulset.CreateVolumeFromSecret("tls-ca", mdb.Spec.Security.TLS.CaCertificateSecret.Name)
253-
} else {
254-
caVolume = statefulset.CreateVolumeFromConfigMap("tls-ca", mdb.Spec.Security.TLS.CaConfigMap.Name)
255-
}
286+
caVolume := statefulset.CreateVolumeFromSecret("tls-ca", mdb.TLSOperatorCASecretNamespacedName().Name)
256287
caVolumeMount := statefulset.CreateVolumeMount(caVolume.Name, tlsCAMountPath, statefulset.WithReadOnly(true))
257288

258289
// Configure a volume which mounts the secret holding the server key and certificate

controllers/mongodb_tls_test.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,16 @@ func TestStatefulSet_IsCorrectlyConfiguredWithTLS(t *testing.T) {
3939

4040
// Assert that all TLS volumes have been added.
4141
assert.Len(t, sts.Spec.Template.Spec.Volumes, 7)
42+
permission := int32(416)
4243
assert.Contains(t, sts.Spec.Template.Spec.Volumes, corev1.Volume{
4344
Name: "tls-ca",
4445
VolumeSource: corev1.VolumeSource{
45-
ConfigMap: &corev1.ConfigMapVolumeSource{
46-
LocalObjectReference: corev1.LocalObjectReference{
47-
Name: mdb.Spec.Security.TLS.CaConfigMap.Name,
48-
},
46+
Secret: &corev1.SecretVolumeSource{
47+
SecretName: mdb.TLSOperatorCASecretNamespacedName().Name,
48+
DefaultMode: &permission,
4949
},
5050
},
5151
})
52-
permission := int32(416)
5352
assert.Contains(t, sts.Spec.Template.Spec.Volumes, corev1.Volume{
5453
Name: "tls-secret",
5554
VolumeSource: corev1.VolumeSource{
@@ -90,7 +89,7 @@ func TestAutomationConfig_IsCorrectlyConfiguredWithTLS(t *testing.T) {
9089
err = createTLSConfigMap(client, mdb)
9190
assert.NoError(t, err)
9291

93-
tlsModification, err := getTLSConfigModification(client, mdb)
92+
tlsModification, err := getTLSConfigModification(client, client, mdb)
9493
assert.NoError(t, err)
9594
ac, err := buildAutomationConfig(mdb, automationconfig.Auth{}, automationconfig.AutomationConfig{}, tlsModification)
9695
assert.NoError(t, err)
@@ -117,7 +116,7 @@ func TestAutomationConfig_IsCorrectlyConfiguredWithTLS(t *testing.T) {
117116
ac := createAC(mdb)
118117

119118
assert.Equal(t, &automationconfig.TLS{
120-
CAFilePath: tlsCAMountPath + tlsCACertName,
119+
CAFilePath: tlsCAMountPath + tlsOperatorSecretFileName("CERT"),
121120
ClientCertificateMode: automationconfig.ClientCertificateModeOptional,
122121
}, ac.TLSConfig)
123122

@@ -126,7 +125,7 @@ func TestAutomationConfig_IsCorrectlyConfiguredWithTLS(t *testing.T) {
126125

127126
assert.Equal(t, automationconfig.TLSModeRequired, process.Args26.Get("net.tls.mode").Data())
128127
assert.Equal(t, tlsOperatorSecretMountPath+operatorSecretFileName, process.Args26.Get("net.tls.certificateKeyFile").Data())
129-
assert.Equal(t, tlsCAMountPath+tlsCACertName, process.Args26.Get("net.tls.CAFile").Data())
128+
assert.Equal(t, tlsCAMountPath+tlsOperatorSecretFileName("CERT"), process.Args26.Get("net.tls.CAFile").Data())
130129
assert.True(t, process.Args26.Get("net.tls.allowConnectionsWithoutCertificates").MustBool())
131130
}
132131
})
@@ -137,7 +136,7 @@ func TestAutomationConfig_IsCorrectlyConfiguredWithTLS(t *testing.T) {
137136
ac := createAC(mdb)
138137

139138
assert.Equal(t, &automationconfig.TLS{
140-
CAFilePath: tlsCAMountPath + tlsCACertName,
139+
CAFilePath: tlsCAMountPath + tlsOperatorSecretFileName("CERT"),
141140
ClientCertificateMode: automationconfig.ClientCertificateModeOptional,
142141
}, ac.TLSConfig)
143142

@@ -146,7 +145,7 @@ func TestAutomationConfig_IsCorrectlyConfiguredWithTLS(t *testing.T) {
146145

147146
assert.Equal(t, automationconfig.TLSModePreferred, process.Args26.Get("net.tls.mode").Data())
148147
assert.Equal(t, tlsOperatorSecretMountPath+operatorSecretFileName, process.Args26.Get("net.tls.certificateKeyFile").Data())
149-
assert.Equal(t, tlsCAMountPath+tlsCACertName, process.Args26.Get("net.tls.CAFile").Data())
148+
assert.Equal(t, tlsCAMountPath+tlsOperatorSecretFileName("CERT"), process.Args26.Get("net.tls.CAFile").Data())
150149
assert.True(t, process.Args26.Get("net.tls.allowConnectionsWithoutCertificates").MustBool())
151150
}
152151
})

controllers/replica_set_controller.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,14 @@ func init() {
7272
func NewReconciler(mgr manager.Manager) *ReplicaSetReconciler {
7373
mgrClient := mgr.GetClient()
7474
secretWatcher := watch.New()
75+
configMapWatcher := watch.New()
7576

7677
return &ReplicaSetReconciler{
77-
client: kubernetesClient.NewClient(mgrClient),
78-
scheme: mgr.GetScheme(),
79-
log: zap.S(),
80-
secretWatcher: &secretWatcher,
78+
client: kubernetesClient.NewClient(mgrClient),
79+
scheme: mgr.GetScheme(),
80+
log: zap.S(),
81+
secretWatcher: &secretWatcher,
82+
configMapWatcher: &configMapWatcher,
8183
}
8284
}
8385

@@ -87,6 +89,7 @@ func (r *ReplicaSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
8789
WithOptions(controller.Options{MaxConcurrentReconciles: 3}).
8890
For(&mdbv1.MongoDBCommunity{}, builder.WithPredicates(predicates.OnlyOnSpecChange())).
8991
Watches(&source.Kind{Type: &corev1.Secret{}}, r.secretWatcher).
92+
Watches(&source.Kind{Type: &corev1.ConfigMap{}}, r.configMapWatcher).
9093
Owns(&appsv1.StatefulSet{}).
9194
Complete(r)
9295
}
@@ -95,10 +98,11 @@ func (r *ReplicaSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
9598
type ReplicaSetReconciler struct {
9699
// This client, initialized using mgr.Client() above, is a split client
97100
// that reads objects from the cache and writes to the apiserver
98-
client kubernetesClient.Client
99-
scheme *runtime.Scheme
100-
log *zap.SugaredLogger
101-
secretWatcher *watch.ResourceWatcher
101+
client kubernetesClient.Client
102+
scheme *runtime.Scheme
103+
log *zap.SugaredLogger
104+
secretWatcher *watch.ResourceWatcher
105+
configMapWatcher *watch.ResourceWatcher
102106
}
103107

104108
// +kubebuilder:rbac:groups=mongodbcommunity.mongodb.com,resources=mongodbcommunity,verbs=get;list;watch;create;update;patch;delete
@@ -297,6 +301,10 @@ func (r *ReplicaSetReconciler) ensureTLSResources(mdb mdbv1.MongoDBCommunity) er
297301
// the TLS secret needs to be created beforehand, as both the StatefulSet and AutomationConfig
298302
// require the contents.
299303
if mdb.Spec.Security.TLS.Enabled {
304+
r.log.Infof("TLS is enabled, creating/updating CA secret")
305+
if err := ensureCASecret(r.client, r.client, r.client, mdb); err != nil {
306+
return errors.Errorf("could not ensure CA secret: %s", err)
307+
}
300308
r.log.Infof("TLS is enabled, creating/updating TLS secret")
301309
if err := ensureTLSSecret(r.client, mdb); err != nil {
302310
return errors.Errorf("could not ensure TLS secret: %s", err)
@@ -597,7 +605,7 @@ func getCustomRolesModification(mdb mdbv1.MongoDBCommunity) (automationconfig.Mo
597605
}
598606

599607
func (r ReplicaSetReconciler) buildAutomationConfig(mdb mdbv1.MongoDBCommunity) (automationconfig.AutomationConfig, error) {
600-
tlsModification, err := getTLSConfigModification(r.client, mdb)
608+
tlsModification, err := getTLSConfigModification(r.client, r.client, mdb)
601609
if err != nil {
602610
return automationconfig.AutomationConfig{}, errors.Errorf("could not configure TLS modification: %s", err)
603611
}

deploy/e2e/role.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ rules:
173173
- watch
174174
- create
175175
- update
176+
- patch
176177
- delete
177178
- patch
178179
- deletecollection

test/e2e/client.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
mdbv1 "github.com/mongodb/mongodb-kubernetes-operator/api/v1"
2323

2424
// Needed for running tests on GCP
25+
"k8s.io/client-go/dynamic"
2526
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
2627
)
2728

@@ -88,8 +89,9 @@ type E2ETestClient struct {
8889
Client client.Client
8990
// We need the core API client for some operations that the controller-runtime client doesn't support
9091
// (e.g. exec into the container)
91-
CoreV1Client corev1client.CoreV1Client
92-
restConfig *rest.Config
92+
CoreV1Client corev1client.CoreV1Client
93+
DynamicClient dynamic.Interface
94+
restConfig *rest.Config
9395
}
9496

9597
// NewE2ETestClient creates a new E2ETestClient.
@@ -102,7 +104,11 @@ func newE2ETestClient(config *rest.Config, scheme *runtime.Scheme) (*E2ETestClie
102104
if err != nil {
103105
return nil, err
104106
}
105-
return &E2ETestClient{Client: cli, CoreV1Client: *coreClient, restConfig: config}, err
107+
dynamicClient, err := dynamic.NewForConfig(config)
108+
if err != nil {
109+
return nil, err
110+
}
111+
return &E2ETestClient{Client: cli, CoreV1Client: *coreClient, DynamicClient: dynamicClient, restConfig: config}, err
106112
}
107113

108114
// Create wraps client.Create to provide post-test cleanup functionality.

0 commit comments

Comments
 (0)