88import io .fabric8 .kubernetes .api .model .OwnerReferenceBuilder ;
99import io .fabric8 .kubernetes .api .model .Pod ;
1010import io .fabric8 .kubernetes .api .model .Secret ;
11+ import io .fabric8 .kubernetes .api .model .SecretBuilder ;
12+ import io .strimzi .api .ResourceAnnotations ;
1113import io .strimzi .api .kafka .model .common .CertificateAuthority ;
1214import io .strimzi .api .kafka .model .kafka .Kafka ;
1315import io .strimzi .api .kafka .model .kafka .KafkaResources ;
5254
5355import java .time .Clock ;
5456import java .util .ArrayList ;
57+ import java .util .HashMap ;
5558import java .util .List ;
5659import java .util .Map ;
5760import java .util .Set ;
5861import java .util .stream .Collectors ;
5962
63+ import static io .strimzi .operator .common .model .Ca .ANNO_STRIMZI_IO_CA_CERT_GENERATION ;
64+ import static io .strimzi .operator .common .model .Ca .ANNO_STRIMZI_IO_CA_KEY_GENERATION ;
65+ import static java .util .Collections .emptyList ;
66+ import static java .util .Collections .singletonList ;
67+
6068/**
6169 * Class used for reconciliation of Cluster and Client CAs. This class contains both the steps of the CA reconciliation
6270 * pipeline and is also used to store the state between them.
@@ -91,6 +99,7 @@ public class CaReconciler {
9199 // Fields used to store state during the reconciliation
92100 private ClusterCa clusterCa ;
93101 private ClientsCa clientsCa ;
102+ private Secret clusterCaCertSecret ;
94103 private Secret coSecret ;
95104
96105 /* test */ boolean isClusterCaNeedFullTrust ;
@@ -225,64 +234,79 @@ Future<Void> reconcileCas(Clock clock) {
225234
226235 return secretOperator .listAsync (reconciliation .namespace (), Labels .EMPTY .withStrimziKind (reconciliation .kind ()).withStrimziCluster (reconciliation .name ()))
227236 .compose (clusterSecrets -> {
228- Secret clusterCaCertSecret = null ;
229- Secret clusterCaKeySecret = null ;
230- Secret clientsCaCertSecret = null ;
231- Secret clientsCaKeySecret = null ;
237+ Secret existingClusterCaCertSecret = null ;
238+ Secret existingClusterCaKeySecret = null ;
239+ Secret existingClientsCaCertSecret = null ;
240+ Secret existingClientsCaKeySecret = null ;
232241
233242 for (Secret secret : clusterSecrets ) {
234243 String secretName = secret .getMetadata ().getName ();
235244 if (secretName .equals (clusterCaCertName )) {
236- clusterCaCertSecret = secret ;
245+ existingClusterCaCertSecret = secret ;
237246 } else if (secretName .equals (clusterCaKeyName )) {
238- clusterCaKeySecret = secret ;
247+ existingClusterCaKeySecret = secret ;
239248 } else if (secretName .equals (clientsCaCertName )) {
240- clientsCaCertSecret = secret ;
249+ existingClientsCaCertSecret = secret ;
241250 } else if (secretName .equals (clientsCaKeyName )) {
242- clientsCaKeySecret = secret ;
251+ existingClientsCaKeySecret = secret ;
243252 }
244253 }
245254
246- clusterCa = new ClusterCa (reconciliation , certManager , passwordGenerator , reconciliation .name (), clusterCaCertSecret ,
247- clusterCaKeySecret ,
255+ boolean generateClusterCa = clusterCaConfig == null || clusterCaConfig .isGenerateCertificateAuthority ();
256+ boolean generateClientsCa = clientsCaConfig == null || clientsCaConfig .isGenerateCertificateAuthority ();
257+
258+ clusterCa = new ClusterCa (reconciliation , certManager , passwordGenerator ,
259+ reconciliation .name (),
260+ existingClusterCaCertSecret ,
261+ existingClusterCaKeySecret ,
248262 ModelUtils .getCertificateValidity (clusterCaConfig ),
249263 ModelUtils .getRenewalDays (clusterCaConfig ),
250- clusterCaConfig == null || clusterCaConfig .isGenerateCertificateAuthority (), clusterCaConfig != null ? clusterCaConfig .getCertificateExpirationPolicy () : null );
251- clusterCa .createRenewOrReplace (
252- reconciliation .namespace (), caLabels ,
253- clusterCaCertLabels , clusterCaCertAnnotations ,
254- clusterCaConfig != null && !clusterCaConfig .isGenerateSecretOwnerReference () ? null : ownerRef ,
255- Util .isMaintenanceTimeWindowsSatisfied (reconciliation , maintenanceWindows , clock .instant ()));
256-
257- clientsCa = new ClientsCa (reconciliation , certManager ,
258- passwordGenerator , clientsCaCertName ,
259- clientsCaCertSecret , clientsCaKeyName ,
260- clientsCaKeySecret ,
264+ generateClusterCa ,
265+ clusterCaConfig != null ? clusterCaConfig .getCertificateExpirationPolicy () : null );
266+
267+
268+ clientsCa = new ClientsCa (reconciliation , certManager , passwordGenerator ,
269+ clientsCaCertName , existingClientsCaCertSecret ,
270+ clientsCaKeyName , existingClientsCaKeySecret ,
261271 ModelUtils .getCertificateValidity (clientsCaConfig ),
262272 ModelUtils .getRenewalDays (clientsCaConfig ),
263- clientsCaConfig == null || clientsCaConfig . isGenerateCertificateAuthority () ,
273+ generateClientsCa ,
264274 clientsCaConfig != null ? clientsCaConfig .getCertificateExpirationPolicy () : null );
265- clientsCa .createRenewOrReplace (reconciliation .namespace (),
266- caLabels , Map .of (), Map .of (),
267- clientsCaConfig != null && !clientsCaConfig .isGenerateSecretOwnerReference () ? null : ownerRef ,
268- Util .isMaintenanceTimeWindowsSatisfied (reconciliation , maintenanceWindows , clock .instant ()));
269-
270- Promise <Void > caUpdatePromise = Promise .promise ();
271275
272276 List <Future <ReconcileResult <Secret >>> secretReconciliations = new ArrayList <>(2 );
273277
274- if (clusterCaConfig == null || clusterCaConfig .isGenerateCertificateAuthority ()) {
275- Future <ReconcileResult <Secret >> clusterSecretReconciliation = secretOperator .reconcile (reconciliation , reconciliation .namespace (), clusterCaCertName , clusterCa .caCertSecret ())
276- .compose (ignored -> secretOperator .reconcile (reconciliation , reconciliation .namespace (), clusterCaKeyName , clusterCa .caKeySecret ()));
278+ if (generateClusterCa ) {
279+ clusterCa .createRenewOrReplace (Util .isMaintenanceTimeWindowsSatisfied (reconciliation , maintenanceWindows , clock .instant ()),
280+ isForceReplace (existingClusterCaKeySecret ),
281+ isForceRenew (existingClusterCaCertSecret ));
282+
283+ OwnerReference ownerReference = clusterCaConfig != null && !clusterCaConfig .isGenerateSecretOwnerReference () ? null : ownerRef ;
284+ clusterCaCertSecret = createCaCertSecret (clusterCaCertName , clusterCaCertLabels , clusterCaCertAnnotations , ownerReference , clusterCa , existingClusterCaCertSecret );
285+ Secret clusterCaKeySecret = createCaKeySecret (clusterCaKeyName , ownerReference , clusterCa , existingClusterCaKeySecret );
286+
287+ Future <ReconcileResult <Secret >> clusterSecretReconciliation = secretOperator .reconcile (reconciliation , reconciliation .namespace (), clusterCaCertName , clusterCaCertSecret )
288+ .compose (ignored -> secretOperator .reconcile (reconciliation , reconciliation .namespace (), clusterCaKeyName , clusterCaKeySecret ));
277289 secretReconciliations .add (clusterSecretReconciliation );
290+ } else {
291+ clusterCaCertSecret = existingClusterCaCertSecret ;
278292 }
279293
280- if (clientsCaConfig == null || clientsCaConfig .isGenerateCertificateAuthority ()) {
281- Future <ReconcileResult <Secret >> clientsSecretReconciliation = secretOperator .reconcile (reconciliation , reconciliation .namespace (), clientsCaCertName , clientsCa .caCertSecret ())
282- .compose (ignored -> secretOperator .reconcile (reconciliation , reconciliation .namespace (), clientsCaKeyName , clientsCa .caKeySecret ()));
294+ if (generateClientsCa ) {
295+ clientsCa .createRenewOrReplace (Util .isMaintenanceTimeWindowsSatisfied (reconciliation , maintenanceWindows , clock .instant ()),
296+ isForceReplace (existingClientsCaKeySecret ),
297+ isForceRenew (existingClientsCaCertSecret ));
298+
299+ OwnerReference ownerReference = clientsCaConfig != null && !clientsCaConfig .isGenerateSecretOwnerReference () ? null : ownerRef ;
300+ Secret clientsCaCertSecret = createCaCertSecret (clientsCaCertName , Map .of (), Map .of (), ownerReference , clientsCa , existingClientsCaCertSecret );
301+ Secret clientsCaKeySecret = createCaKeySecret (clientsCaKeyName , ownerReference , clientsCa , existingClientsCaKeySecret );
302+
303+ Future <ReconcileResult <Secret >> clientsSecretReconciliation = secretOperator .reconcile (reconciliation , reconciliation .namespace (), clientsCaCertName , clientsCaCertSecret )
304+ .compose (ignored -> secretOperator .reconcile (reconciliation , reconciliation .namespace (), clientsCaKeyName , clientsCaKeySecret ));
283305 secretReconciliations .add (clientsSecretReconciliation );
284306 }
285307
308+ Promise <Void > caUpdatePromise = Promise .promise ();
309+
286310 Future .join (secretReconciliations ).onComplete (res -> {
287311 if (res .succeeded ()) {
288312 caUpdatePromise .complete ();
@@ -334,7 +358,7 @@ Future<Void> reconcileClusterOperatorSecret(Clock clock) {
334358 * Maybe perform a rolling update of the cluster to update the CA certificates in component truststores.
335359 * This is only necessary when the Cluster CA certificate has changed due to a new CA key.
336360 * It is not necessary when the CA certificate is renewed while retaining the existing key.
337- *
361+ * <p>
338362 * If Strimzi did not replace the CA key during the current reconciliation, {@code isClusterCaNeedFullTrust} is used to:
339363 * * continue from a previous CA key replacement which didn't end successfully (i.e. CO stopped)
340364 * * track key replacements when the user is managing the CA
@@ -345,7 +369,7 @@ Future<Void> reconcileClusterOperatorSecret(Clock clock) {
345369 Future <Void > maybeRollingUpdateForNewClusterCaKey () {
346370 if (clusterCa .keyReplaced () || isClusterCaNeedFullTrust ) {
347371 RestartReason restartReason = RestartReason .CLUSTER_CA_CERT_KEY_REPLACED ;
348- TlsPemIdentity coTlsPemIdentity = new TlsPemIdentity (new PemTrustSet (clusterCa . caCertSecret () ), PemAuthIdentity .clusterOperator (coSecret ));
372+ TlsPemIdentity coTlsPemIdentity = new TlsPemIdentity (new PemTrustSet (clusterCaCertSecret ), PemAuthIdentity .clusterOperator (coSecret ));
349373 return patchClusterCaKeyGenerationAndReturnNodes ()
350374 .compose (nodes -> rollKafkaBrokers (nodes , RestartReasons .of (restartReason ), coTlsPemIdentity ))
351375 .compose (i -> rollDeploymentIfExists (KafkaResources .entityOperatorDeploymentName (reconciliation .name ()), restartReason ))
@@ -358,11 +382,11 @@ Future<Void> maybeRollingUpdateForNewClusterCaKey() {
358382
359383 /**
360384 * Gather the Kafka related components pods for checking Cluster CA key trust and Cluster CA certificate usage to sign servers certificate.
361- *
385+ * <p>
362386 * Verify that all the pods are already trusting the new CA certificate signed by a new CA key.
363387 * It checks each pod's CA key generation, compared with the new CA key generation.
364388 * When the trusting phase is not completed (i.e. because CO stopped), it needs to be recovered from where it was left.
365- *
389+ * <p>
366390 * Verify that all pods are already using the new CA certificate to sign server certificates.
367391 * It checks each pod's CA certificate generation, compared with the new CA certificate generation.
368392 * When the new CA certificate is used everywhere, the old CA certificate can be removed.
@@ -519,7 +543,7 @@ Future<Void> maybeRollingUpdateForNewClusterCaKey() {
519543 clusterCa .maybeDeleteOldCerts ();
520544
521545 if (clusterCa .certsRemoved ()) {
522- return secretOperator .reconcile (reconciliation , reconciliation .namespace (), AbstractModel .clusterCaCertSecretName (reconciliation .name ()), clusterCa . caCertSecret () )
546+ return secretOperator .reconcile (reconciliation , reconciliation .namespace (), AbstractModel .clusterCaCertSecretName (reconciliation .name ()), clusterCaCertSecret )
523547 .mapEmpty ();
524548 } else {
525549 return Future .succeededFuture ();
@@ -529,6 +553,68 @@ Future<Void> maybeRollingUpdateForNewClusterCaKey() {
529553 }
530554 }
531555
556+ private boolean isForceReplace (Secret caSecret ) {
557+ if (caSecret != null && caSecret .getMetadata () != null &&
558+ Annotations .hasAnnotation (caSecret , ResourceAnnotations .ANNO_STRIMZI_IO_FORCE_REPLACE )) {
559+ return Annotations .booleanAnnotation (caSecret , ResourceAnnotations .ANNO_STRIMZI_IO_FORCE_REPLACE , false );
560+ } else {
561+ return false ;
562+ }
563+ }
564+
565+ private boolean isForceRenew (Secret caSecret ) {
566+ if (caSecret != null && caSecret .getMetadata () != null &&
567+ Annotations .hasAnnotation (caSecret , ResourceAnnotations .ANNO_STRIMZI_IO_FORCE_RENEW )) {
568+ return Annotations .booleanAnnotation (caSecret , ResourceAnnotations .ANNO_STRIMZI_IO_FORCE_RENEW , false );
569+ } else {
570+ return false ;
571+ }
572+ }
573+
574+ private Secret createCaCertSecret (String secretName , Map <String , String > additionalLabels , Map <String , String > additionalAnnotations ,
575+ OwnerReference ownerReference , Ca ca , Secret existingCaCertSecret ) {
576+ Map <String , String > certAnnotations = new HashMap <>(2 );
577+ certAnnotations .put (ANNO_STRIMZI_IO_CA_CERT_GENERATION , String .valueOf (ca .caCertGeneration ()));
578+
579+ if (ca .postponed ()
580+ && existingCaCertSecret != null
581+ && Annotations .hasAnnotation (existingCaCertSecret , Annotations .ANNO_STRIMZI_IO_FORCE_RENEW )) {
582+ certAnnotations .put (Annotations .ANNO_STRIMZI_IO_FORCE_RENEW , Annotations .stringAnnotation (existingCaCertSecret , Annotations .ANNO_STRIMZI_IO_FORCE_RENEW , "false" ));
583+ }
584+ return createCaSecret (secretName , ca .caCertData (), Util .mergeLabelsOrAnnotations (caLabels , additionalLabels ),
585+ Util .mergeLabelsOrAnnotations (certAnnotations , additionalAnnotations ), ownerReference );
586+
587+ }
588+
589+ private Secret createCaKeySecret (String secretName , OwnerReference ownerReference , Ca ca , Secret existingCaKeySecret ) {
590+ Map <String , String > keyAnnotations = new HashMap <>(2 );
591+ keyAnnotations .put (ANNO_STRIMZI_IO_CA_KEY_GENERATION , String .valueOf (ca .caKeyGeneration ()));
592+
593+ if (ca .postponed ()
594+ && existingCaKeySecret != null
595+ && Annotations .hasAnnotation (existingCaKeySecret , Annotations .ANNO_STRIMZI_IO_FORCE_REPLACE )) {
596+ keyAnnotations .put (Annotations .ANNO_STRIMZI_IO_FORCE_REPLACE , Annotations .stringAnnotation (existingCaKeySecret , Annotations .ANNO_STRIMZI_IO_FORCE_REPLACE , "false" ));
597+ }
598+ return createCaSecret (secretName , ca .caKeyData (), caLabels , keyAnnotations , ownerReference );
599+
600+ }
601+
602+ private Secret createCaSecret (String name , Map <String , String > data ,
603+ Map <String , String > labels , Map <String , String > annotations , OwnerReference ownerReference ) {
604+ List <OwnerReference > or = ownerReference != null ? singletonList (ownerReference ) : emptyList ();
605+ return new SecretBuilder ()
606+ .withNewMetadata ()
607+ .withName (name )
608+ .withNamespace (reconciliation .namespace ())
609+ .withLabels (labels )
610+ .withAnnotations (annotations )
611+ .withOwnerReferences (or )
612+ .endMetadata ()
613+ .withType ("Opaque" )
614+ .withData (data )
615+ .build ();
616+ }
617+
532618 /**
533619 * Helper class to pass both Cluster and Clients CA as a result of the reconciliation
534620 *
0 commit comments