@@ -80,9 +80,9 @@ pub struct CertgenArgs {
8080 dry_run : bool ,
8181
8282 /// Name of a `ConfigMap` to create containing the CA certificate (key: ca.crt)
83- /// for `BackendTLSPolicy` backend validation. In full PKI mode, the CA comes
84- /// from the generated bundle. In --jwt-only mode, the CA is read from
85- /// --backend-ca-source-secret.
83+ /// for `BackendTLSPolicy` backend validation. The CA is always read from the
84+ /// authoritative server Secret: --server-secret-name in full PKI mode,
85+ /// --backend-ca-source-secret in --jwt-only mode .
8686 #[ arg( long, value_name = "NAME" ) ]
8787 backend_ca_configmap_name : Option < String > ,
8888
@@ -127,7 +127,7 @@ pub async fn run(args: CertgenArgs) -> Result<()> {
127127 run_kubernetes ( & args, & bundle) . await ?;
128128
129129 if let Some ( ref cm_name) = args. backend_ca_configmap_name {
130- create_backend_ca_configmap_if_needed ( & args, & bundle , cm_name) . await ?;
130+ create_backend_ca_configmap_if_needed ( & args, cm_name) . await ?;
131131 }
132132
133133 Ok ( ( ) )
@@ -326,9 +326,21 @@ async fn create_tls_secrets(
326326 Ok ( ( ) )
327327}
328328
329+ fn extract_ca_from_secret ( secret : & Secret , name : & str ) -> Result < String > {
330+ let data = secret
331+ . data
332+ . as_ref ( )
333+ . ok_or_else ( || miette:: miette!( "secret {name} has no data" ) ) ?;
334+ let ca = data
335+ . get ( "ca.crt" )
336+ . ok_or_else ( || miette:: miette!( "secret {name} has no ca.crt key" ) ) ?;
337+ String :: from_utf8 ( ca. 0 . clone ( ) )
338+ . into_diagnostic ( )
339+ . wrap_err ( "ca.crt is not valid UTF-8" )
340+ }
341+
329342async fn create_backend_ca_configmap_if_needed (
330343 args : & CertgenArgs ,
331- bundle : & PkiBundle ,
332344 configmap_name : & str ,
333345) -> Result < ( ) > {
334346 let namespace = args
@@ -340,31 +352,13 @@ async fn create_backend_ca_configmap_if_needed(
340352 . await
341353 . into_diagnostic ( )
342354 . wrap_err ( "failed to construct Kubernetes client for backend CA ConfigMap" ) ?;
343- let api: Api < ConfigMap > = Api :: namespaced ( client. clone ( ) , namespace) ;
344-
345- if api
346- . get_opt ( configmap_name)
347- . await
348- . into_diagnostic ( )
349- . wrap_err_with ( || format ! ( "failed to read configmap {configmap_name}" ) ) ?
350- . is_some ( )
351- {
352- info ! (
353- namespace = %namespace,
354- configmap = %configmap_name,
355- "Backend CA ConfigMap already exists, skipping."
356- ) ;
357- return Ok ( ( ) ) ;
358- }
359-
360- let ca_pem = if !args. jwt_only {
361- bundle. ca_cert_pem . clone ( )
362- } else if let Some ( source_secret) = & args. backend_ca_source_secret {
363- let secret_api: Api < Secret > = Api :: namespaced ( client, namespace) ;
355+ let secret_api: Api < Secret > = Api :: namespaced ( client. clone ( ) , namespace) ;
356+ let cm_api: Api < ConfigMap > = Api :: namespaced ( client, namespace) ;
364357
365- // Poll for the cert-manager Secret with timeout to handle cert issuance delay.
366- // The Helm chart sets this to (Job activeDeadlineSeconds - 30) leaving
367- // margin for ConfigMap creation and hook completion.
358+ // Resolve the CA from the authoritative server Secret rather than the
359+ // in-memory bundle so upgrades that enable BackendTLSPolicy on an
360+ // existing release use the CA that actually signed the server cert.
361+ let ca_pem = if let Some ( source_secret) = & args. backend_ca_source_secret {
368362 let poll_timeout = std:: time:: Duration :: from_secs ( args. backend_ca_poll_timeout_seconds ) ;
369363 let poll_interval = std:: time:: Duration :: from_secs ( 2 ) ;
370364 let start = std:: time:: Instant :: now ( ) ;
@@ -416,23 +410,58 @@ async fn create_backend_ca_configmap_if_needed(
416410 elapsed_secs = start. elapsed( ) . as_secs( ) ,
417411 "cert-manager TLS certificate found."
418412 ) ;
419-
420- let data = secret
421- . data
422- . ok_or_else ( || miette:: miette!( "secret {source_secret} has no data" ) ) ?;
423- let ca = data
424- . get ( "ca.crt" )
425- . ok_or_else ( || miette:: miette!( "secret {source_secret} has no ca.crt key" ) ) ?;
426- String :: from_utf8 ( ca. 0 . clone ( ) )
413+ extract_ca_from_secret ( & secret, source_secret) ?
414+ } else if let Some ( server_secret) = & args. server_secret_name {
415+ let secret = secret_api
416+ . get ( server_secret)
417+ . await
427418 . into_diagnostic ( )
428- . wrap_err ( "ca.crt is not valid UTF-8" ) ?
419+ . wrap_err_with ( || format ! ( "failed to read server secret {server_secret}" ) ) ?;
420+ extract_ca_from_secret ( & secret, server_secret) ?
429421 } else {
430422 return Err ( miette:: miette!(
431- "--backend-ca-source-secret is required with --jwt-only \
432- and --backend-ca-configmap-name"
423+ "--backend-ca-source-secret or --server-secret-name is required \
424+ with --backend-ca-configmap-name"
433425 ) ) ;
434426 } ;
435427
428+ // Reconcile: create or update so the backend CA stays current across
429+ // CA rotations and upgrades.
430+ if let Some ( existing) = cm_api
431+ . get_opt ( configmap_name)
432+ . await
433+ . into_diagnostic ( )
434+ . wrap_err_with ( || format ! ( "failed to read configmap {configmap_name}" ) ) ?
435+ {
436+ let up_to_date = existing
437+ . data
438+ . as_ref ( )
439+ . and_then ( |d| d. get ( "ca.crt" ) )
440+ . map ( String :: as_str)
441+ == Some ( & ca_pem) ;
442+ if up_to_date {
443+ info ! (
444+ namespace = %namespace,
445+ configmap = %configmap_name,
446+ "Backend CA ConfigMap is up-to-date, skipping."
447+ ) ;
448+ return Ok ( ( ) ) ;
449+ }
450+ let mut updated = existing;
451+ updated. data = Some ( BTreeMap :: from ( [ ( "ca.crt" . to_string ( ) , ca_pem) ] ) ) ;
452+ cm_api
453+ . replace ( configmap_name, & PostParams :: default ( ) , & updated)
454+ . await
455+ . into_diagnostic ( )
456+ . wrap_err_with ( || format ! ( "failed to update configmap {configmap_name}" ) ) ?;
457+ info ! (
458+ namespace = %namespace,
459+ configmap = %configmap_name,
460+ "Backend CA ConfigMap updated with current CA."
461+ ) ;
462+ return Ok ( ( ) ) ;
463+ }
464+
436465 let configmap = ConfigMap {
437466 metadata : ObjectMeta {
438467 name : Some ( configmap_name. to_string ( ) ) ,
@@ -442,7 +471,8 @@ async fn create_backend_ca_configmap_if_needed(
442471 ..Default :: default ( )
443472 } ;
444473
445- api. create ( & PostParams :: default ( ) , & configmap)
474+ cm_api
475+ . create ( & PostParams :: default ( ) , & configmap)
446476 . await
447477 . into_diagnostic ( )
448478 . wrap_err_with ( || format ! ( "failed to create configmap {configmap_name}" ) ) ?;
0 commit comments