Skip to content

Commit de46cf9

Browse files
committed
Improve agent certificate rotation
Pods now mount a secret containing both old and new certificates, with file names being the hash of the certificate. When it is time to rotate the certificate, the operator updates the automation config with a new path (including the hash) to the certificate. This eliminates the need to manually restart Pods during certificate rotation.
1 parent 58f3569 commit de46cf9

28 files changed

+265
-163
lines changed

api/v1/mdb/mongodb_types.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ func (m *MongoDB) GetSecretsMountedIntoDBPod() []string {
195195
secrets = append(secrets, tls)
196196
}
197197
}
198-
agentCerts := m.GetSecurity().AgentClientCertificateSecretName(m.Name).Name
198+
agentCerts := m.GetSecurity().AgentClientCertificateSecretName(m.Name)
199199
if agentCerts != "" {
200200
secrets = append(secrets, agentCerts)
201201
}
@@ -852,7 +852,7 @@ func (s *Security) ShouldUseX509(currentAgentAuthMode string) bool {
852852
// AgentClientCertificateSecretName returns the name of the Secret that holds the agent
853853
// client TLS certificates.
854854
// If no custom name has been defined, it returns the default one.
855-
func (s Security) AgentClientCertificateSecretName(resourceName string) corev1.SecretKeySelector {
855+
func (s Security) AgentClientCertificateSecretName(resourceName string) string {
856856
secretName := util.AgentSecretName
857857

858858
if s.CertificatesSecretsPrefix != "" {
@@ -862,10 +862,7 @@ func (s Security) AgentClientCertificateSecretName(resourceName string) corev1.S
862862
secretName = s.Authentication.Agents.ClientCertificateSecretRefWrap.ClientCertificateSecretRef.Name
863863
}
864864

865-
return corev1.SecretKeySelector{
866-
Key: util.AutomationAgentPemSecretKey,
867-
LocalObjectReference: corev1.LocalObjectReference{Name: secretName},
868-
}
865+
return secretName
869866
}
870867

871868
// The customer has set ClientCertificateSecretRef. This signals that client certs are required,

api/v1/mdb/mongodb_types_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -382,15 +382,15 @@ func TestAgentClientCertificateSecretName(t *testing.T) {
382382
rs := NewReplicaSetBuilder().SetSecurityTLSEnabled().EnableAuth([]AuthMode{util.X509}).Build()
383383

384384
// Default is the hardcoded "agent-certs"
385-
assert.Equal(t, util.AgentSecretName, rs.GetSecurity().AgentClientCertificateSecretName(rs.Name).Name)
385+
assert.Equal(t, util.AgentSecretName, rs.GetSecurity().AgentClientCertificateSecretName(rs.Name))
386386

387387
// If the top-level prefix is there, we use it
388388
rs.Spec.Security.CertificatesSecretsPrefix = "prefix"
389-
assert.Equal(t, fmt.Sprintf("prefix-%s-%s", rs.Name, util.AgentSecretName), rs.GetSecurity().AgentClientCertificateSecretName(rs.Name).Name)
389+
assert.Equal(t, fmt.Sprintf("prefix-%s-%s", rs.Name, util.AgentSecretName), rs.GetSecurity().AgentClientCertificateSecretName(rs.Name))
390390

391391
// If the name is provided (deprecated) we return it
392392
rs.GetSecurity().Authentication.Agents.ClientCertificateSecretRefWrap.ClientCertificateSecretRef.Name = "foo"
393-
assert.Equal(t, "foo", rs.GetSecurity().AgentClientCertificateSecretName(rs.Name).Name)
393+
assert.Equal(t, "foo", rs.GetSecurity().AgentClientCertificateSecretName(rs.Name))
394394
}
395395

396396
func TestInternalClusterAuthSecretName(t *testing.T) {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Improved agent certificate rotation
3+
kind: feature
4+
date: 2025-09-09
5+
---
6+
7+
* Improve automation agent certificate rotation: the agent now restarts automatically when its certificate is renewed, ensuring smooth operation without manual intervention and allowing seamless certificate updates without requiring manual Pod restarts.

controllers/om/automation_config_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ func TestCanResetAgentSSL(t *testing.T) {
365365
ac.AgentSSL = &AgentSSL{
366366
ClientCertificateMode: util.OptionalClientCertficates,
367367
CAFilePath: util.CAFilePathInContainer,
368-
AutoPEMKeyFilePath: util.AutomationAgentPemFilePath,
368+
AutoPEMKeyFilePath: "/fake/path/to/pem",
369369
}
370370

371371
if err := ac.Apply(); err != nil {
@@ -374,7 +374,7 @@ func TestCanResetAgentSSL(t *testing.T) {
374374

375375
tls := cast.ToStringMap(ac.Deployment["tls"])
376376
assert.Equal(t, tls["clientCertificateMode"], util.OptionalClientCertficates)
377-
assert.Equal(t, tls["autoPEMKeyFilePath"], util.AutomationAgentPemFilePath)
377+
assert.Equal(t, tls["autoPEMKeyFilePath"], "/fake/path/to/pem")
378378
assert.Equal(t, tls["CAFilePath"], util.CAFilePathInContainer)
379379

380380
ac.AgentSSL = &AgentSSL{

controllers/om/backup_agent_config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ func (bac *BackupAgentConfig) UnsetAgentPassword() {
4949
bac.BackupAgentTemplate.Password = util.MergoDelete
5050
}
5151

52-
func (bac *BackupAgentConfig) EnableX509Authentication(backupAgentSubject string) {
53-
bac.BackupAgentTemplate.SSLPemKeyFile = util.AutomationAgentPemFilePath
52+
func (bac *BackupAgentConfig) EnableX509Authentication(backupAgentSubject, automationAgentPemFilePath string) {
53+
bac.BackupAgentTemplate.SSLPemKeyFile = automationAgentPemFilePath
5454
bac.SetAgentUserName(backupAgentSubject)
5555
}
5656

controllers/om/backup_agent_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func TestFieldsAreUpdatedBackupConfig(t *testing.T) {
3232

3333
func TestBackupFieldsAreNotLost(t *testing.T) {
3434
config := getTestBackupConfig()
35-
config.EnableX509Authentication("namespace")
35+
config.EnableX509Authentication("namespace", "/fake/path/to/pem")
3636

3737
assert.Contains(t, config.BackingMap, "logPath")
3838
assert.Contains(t, config.BackingMap, "logRotate")
@@ -48,7 +48,7 @@ func TestBackupFieldsAreNotLost(t *testing.T) {
4848
func TestNestedFieldsAreNotLost(t *testing.T) {
4949
config := getTestBackupConfig()
5050

51-
config.EnableX509Authentication("namespace")
51+
config.EnableX509Authentication("namespace", "/fake/path/to/pem")
5252

5353
_ = config.Apply()
5454

controllers/om/monitoring_agent_config.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ func (m *MonitoringAgentConfig) UnsetAgentPassword() {
4545
m.MonitoringAgentTemplate.Password = util.MergoDelete
4646
}
4747

48-
func (m *MonitoringAgentConfig) EnableX509Authentication(MonitoringAgentSubject string) {
49-
m.MonitoringAgentTemplate.SSLPemKeyFile = util.AutomationAgentPemFilePath
50-
m.SetAgentUserName(MonitoringAgentSubject)
48+
func (m *MonitoringAgentConfig) EnableX509Authentication(monitoringAgentSubject, automationAgentPemFilePath string) {
49+
m.MonitoringAgentTemplate.SSLPemKeyFile = automationAgentPemFilePath
50+
m.SetAgentUserName(monitoringAgentSubject)
5151
}
5252

5353
func (m *MonitoringAgentConfig) DisableX509Authentication() {

controllers/operator/appdbreplicaset_controller.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,15 @@ func (r *ReconcileAppDbReplicaSet) ReconcileAppDB(ctx context.Context, opsManage
538538
return result.OK()
539539
}
540540

541-
podVars, err := r.tryConfigureMonitoringInOpsManager(ctx, opsManager, opsManagerUserPassword, log)
541+
var appdbSecretPath string
542+
if r.VaultClient != nil {
543+
appdbSecretPath = r.VaultClient.AppDBSecretPath()
544+
}
545+
546+
agentCertSecretName := opsManager.Spec.AppDB.GetSecurity().AgentClientCertificateSecretName(opsManager.Spec.AppDB.GetName())
547+
_, agentCertPath := r.agentCertHashAndPath(ctx, log, opsManager.Namespace, agentCertSecretName, appdbSecretPath)
548+
549+
podVars, err := r.tryConfigureMonitoringInOpsManager(ctx, opsManager, opsManagerUserPassword, agentCertPath, log)
542550
// it's possible that Ops Manager will not be available when we attempt to configure AppDB monitoring
543551
// in Ops Manager. This is not a blocker to continue with the rest of the reconciliation.
544552
if err != nil {
@@ -613,10 +621,6 @@ func (r *ReconcileAppDbReplicaSet) ReconcileAppDB(ctx context.Context, opsManage
613621
return r.updateStatus(ctx, opsManager, workflowStatus, log, appDbStatusOption)
614622
}
615623

616-
var appdbSecretPath string
617-
if r.VaultClient != nil {
618-
appdbSecretPath = r.VaultClient.AppDBSecretPath()
619-
}
620624
tlsSecretName := opsManager.Spec.AppDB.GetSecurity().MemberCertificateSecretName(opsManager.Spec.AppDB.Name())
621625
certHash := enterprisepem.ReadHashFromSecret(ctx, r.SecretClient, opsManager.Namespace, tlsSecretName, appdbSecretPath, log)
622626

@@ -1621,7 +1625,7 @@ func (r *ReconcileAppDbReplicaSet) ensureAppDbAgentApiKey(ctx context.Context, o
16211625

16221626
// tryConfigureMonitoringInOpsManager attempts to configure monitoring in Ops Manager. This might not be possible if Ops Manager
16231627
// has not been created yet, if that is the case, an empty PodVars will be returned.
1624-
func (r *ReconcileAppDbReplicaSet) tryConfigureMonitoringInOpsManager(ctx context.Context, opsManager *omv1.MongoDBOpsManager, opsManagerUserPassword string, log *zap.SugaredLogger) (env.PodEnvVars, error) {
1628+
func (r *ReconcileAppDbReplicaSet) tryConfigureMonitoringInOpsManager(ctx context.Context, opsManager *omv1.MongoDBOpsManager, opsManagerUserPassword string, agentCertPath string, log *zap.SugaredLogger) (env.PodEnvVars, error) {
16251629
var operatorVaultSecretPath string
16261630
if r.VaultClient != nil {
16271631
operatorVaultSecretPath = r.VaultClient.OperatorSecretPath()
@@ -1660,6 +1664,7 @@ func (r *ReconcileAppDbReplicaSet) tryConfigureMonitoringInOpsManager(ctx contex
16601664
Mechanisms: []string{util.SCRAM},
16611665
ClientCertificates: util.OptionalClientCertficates,
16621666
AutoUser: util.AutomationAgentUserName,
1667+
AutoPEMKeyFilePath: agentCertPath,
16631668
CAFilePath: util.CAFilePathInContainer,
16641669
}
16651670
err = authentication.Configure(conn, opts, false, log)

controllers/operator/appdbreplicaset_controller_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ func TestTryConfigureMonitoringInOpsManager(t *testing.T) {
377377
require.NoError(t, err)
378378

379379
// attempt configuring monitoring when there is no api key secret
380-
podVars, err := reconciler.tryConfigureMonitoringInOpsManager(ctx, opsManager, "password", zap.S())
380+
podVars, err := reconciler.tryConfigureMonitoringInOpsManager(ctx, opsManager, "password", "/fake/agent-cert/path", zap.S())
381381
assert.NoError(t, err)
382382

383383
assert.Empty(t, podVars.ProjectID)
@@ -408,7 +408,7 @@ func TestTryConfigureMonitoringInOpsManager(t *testing.T) {
408408
assert.NoError(t, err)
409409

410410
// once the secret exists, monitoring should be fully configured
411-
podVars, err = reconciler.tryConfigureMonitoringInOpsManager(ctx, opsManager, "password", zap.S())
411+
podVars, err = reconciler.tryConfigureMonitoringInOpsManager(ctx, opsManager, "password", "/fake/agent-cert/path", zap.S())
412412
assert.NoError(t, err)
413413

414414
assert.Equal(t, om.TestGroupID, podVars.ProjectID)
@@ -522,7 +522,7 @@ func TestTryConfigureMonitoringInOpsManagerWithExternalDomains(t *testing.T) {
522522
require.NoError(t, err)
523523

524524
// attempt configuring monitoring when there is no api key secret
525-
podVars, err := reconciler.tryConfigureMonitoringInOpsManager(ctx, opsManager, "password", zap.S())
525+
podVars, err := reconciler.tryConfigureMonitoringInOpsManager(ctx, opsManager, "password", "/fake/agent-cert/path", zap.S())
526526
assert.NoError(t, err)
527527

528528
assert.Empty(t, podVars.ProjectID)
@@ -553,7 +553,7 @@ func TestTryConfigureMonitoringInOpsManagerWithExternalDomains(t *testing.T) {
553553
assert.NoError(t, err)
554554

555555
// once the secret exists, monitoring should be fully configured
556-
podVars, err = reconciler.tryConfigureMonitoringInOpsManager(ctx, opsManager, "password", zap.S())
556+
podVars, err = reconciler.tryConfigureMonitoringInOpsManager(ctx, opsManager, "password", "/fake/agent-cert/path", zap.S())
557557
assert.NoError(t, err)
558558

559559
assert.Equal(t, om.TestGroupID, podVars.ProjectID)

controllers/operator/authentication/authentication.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ type Options struct {
4343
// so it is possible to use other auth mechanisms without needing to provide client certs.
4444
ClientCertificates string
4545

46+
AutoPEMKeyFilePath string
47+
4648
CAFilePath string
4749

4850
// Use Agent Client Auth
@@ -348,7 +350,7 @@ func addOrRemoveAgentClientCertificate(conn om.Connection, opts Options, log *za
348350

349351
if opts.AgentsShouldUseClientAuthentication {
350352
ac.AgentSSL = &om.AgentSSL{
351-
AutoPEMKeyFilePath: util.AutomationAgentPemFilePath,
353+
AutoPEMKeyFilePath: opts.AutoPEMKeyFilePath,
352354
CAFilePath: opts.CAFilePath,
353355
ClientCertificateMode: opts.ClientCertificates,
354356
}

0 commit comments

Comments
 (0)