Skip to content

Commit f5bdbe3

Browse files
authored
Make CA lifetimes configurable (#357)
* Make CA lifetimes configurable Fixes #354 * Changelog * Rename autoTls.ca.caLifetime to caCertificateLifetime * Docs * Fix børked tests * Fix broken doc link
1 parent 7e7f37c commit f5bdbe3

File tree

7 files changed

+59
-24
lines changed

7 files changed

+59
-24
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ All notable changes to this project will be documented in this file.
1414
- Use new annotation builder ([#341]).
1515
- `autoTLS` certificate authorities will now be rotated regularly ([#350]).
1616
- [BREAKING] This changes the format of the CA secrets. Old secrets will be migrated automatically, but manual intervention will be required to downgrade back to 23.11.x.
17+
- `autoTLS` certificate authority lifetimes are now configurable ([#357]).
1718

1819
[#333]: https://github.com/stackabletech/secret-operator/pull/333
1920
[#341]: https://github.com/stackabletech/secret-operator/pull/341
2021
[#350]: https://github.com/stackabletech/secret-operator/pull/350
2122
[#352]: https://github.com/stackabletech/secret-operator/pull/352
23+
[#357]: https://github.com/stackabletech/secret-operator/pull/357
2224

2325

2426
## [23.11.0] - 2023-11-24

deploy/helm/secret-operator/crds/crds.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ spec:
4747
default: false
4848
description: Whether the certificate authority should be managed by Secret Operator, including being generated if it does not already exist.
4949
type: boolean
50+
caCertificateLifetime:
51+
default: 730d
52+
description: |-
53+
The lifetime of each generated certificate authority.
54+
55+
Should always be more than double `maxCertificateLifetime`.
56+
57+
If `autoGenerate: true` then the Secret Operator will prepare a new CA certificate the old CA approaches expiration. If `autoGenerate: false` then the Secret Operator will log a warning instead.
58+
type: string
5059
secret:
5160
description: Reference (name and namespace) to a Kubernetes Secret object where the CA certificate and key is stored in the keys `ca.crt` and `ca.key` respectively.
5261
properties:

docs/modules/secret-operator/pages/secretclass.adoc

+4-2
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ Users can use podOverrides to extend the certificate lifetime by adding volume a
6262

6363
Certificate authorities also have a limited lifetime, and need to be rotated before they expire to avoid cluster disruption.
6464

65-
If configured to provision its own CA (`autoTls.ca.autoGenerate`), the Secret Operator will create CA certificates that are valid for 2 years,
66-
and initiate rotation when there is less than 1 year remaining. If configured _not_ to provision its own CA, a warning will be issued when there is less than 1 year remaining.
65+
If configured to provision its own CA (`autoTls.ca.autoGenerate`), the Secret Operator will create CA certificates that are valid for 2 years (`autoTls.ca.caCertificateLifetime`),
66+
and initiate rotation once less than half of that time remains. If configured _not_ to provision its own CA, a warning will instead be issued in that case.
6767

6868
To avoid disruption and let the new CA propagate through the cluster, the Secret Operator will prefer using the oldest CA that will last for the entire lifetime of the issued certificate.
6969

@@ -81,6 +81,7 @@ spec:
8181
name: secret-provisioner-tls-ca
8282
namespace: default
8383
autoGenerate: true
84+
caCertificateLifetime: 700d
8485
maxCertificateLifetime: 15d # optional
8586
----
8687

@@ -89,6 +90,7 @@ spec:
8990
`autoTls.ca.secret`:: Reference (`name` and `namespace`) to a K8s `Secret` object where the CA certificate and key is stored in the keys `ca.crt`
9091
and `ca.key` respectively.
9192
`autoTls.ca.autoGenerate`:: Whether the certificate authority should be provisioned and managed by the Secret Operator.
93+
`autoTls.ca.caCertificateLifetime` :: The lifetime of the certificate authority's root certificate.
9294
`autoTls.maxCertificateLifetime`:: Maximum lifetime the created certificates are allowed to have. In case consumers request a longer lifetime than allowed by this setting, the lifetime will be the minimum of both.
9395

9496
[#backend-kerberoskeytab]

rust/operator-binary/src/backend/dynamic.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -101,17 +101,12 @@ pub async fn from_class(
101101
})
102102
}
103103
crd::SecretClassBackend::AutoTls(crd::AutoTlsBackend {
104-
ca:
105-
crd::AutoTlsCa {
106-
secret,
107-
auto_generate,
108-
},
104+
ca,
109105
max_certificate_lifetime,
110106
}) => from(
111107
super::TlsGenerate::get_or_create_k8s_certificate(
112108
client,
113-
&secret,
114-
auto_generate,
109+
&ca,
115110
max_certificate_lifetime,
116111
)
117112
.await?,

rust/operator-binary/src/backend/tls/ca.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ pub struct Config {
140140
pub manage_ca: bool,
141141

142142
/// The duration of any new CA certificates provisioned.
143-
pub ca_lifetime: Duration,
143+
pub ca_certificate_lifetime: Duration,
144144

145145
/// If no existing CA certificate outlives `rotate_if_ca_expires_before`, a new
146146
/// certificate will be generated.
@@ -151,7 +151,7 @@ pub struct Config {
151151
/// pods' truststores.
152152
///
153153
/// Hence, this value _should_ be larger than the PKI's maximum certificate lifetime,
154-
/// and smaller than [`Self::ca_lifetime`].
154+
/// and smaller than [`Self::ca_certificate_lifetime`].
155155
pub rotate_if_ca_expires_before: Option<Duration>,
156156
}
157157

@@ -185,7 +185,7 @@ impl CertificateAuthority {
185185
.build();
186186
let now = OffsetDateTime::now_utc();
187187
let not_before = now - Duration::from_minutes_unchecked(5);
188-
let not_after = now + config.ca_lifetime;
188+
let not_after = now + config.ca_certificate_lifetime;
189189
let conf = Conf::new(ConfMethod::default()).unwrap();
190190
let private_key = Rsa::generate(2048)
191191
.and_then(PKey::try_from)

rust/operator-binary/src/backend/tls/mod.rs

+14-7
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ use stackable_operator::{
2424
k8s_openapi::chrono::{self, FixedOffset, TimeZone},
2525
time::Duration,
2626
};
27-
use stackable_secret_operator_crd_utils::SecretReference;
2827
use time::OffsetDateTime;
2928

3029
use crate::{
30+
crd,
3131
format::{well_known, SecretData, WellKnownSecretData},
3232
utils::iterator_try_concat_bytes,
3333
};
@@ -40,6 +40,10 @@ use super::{
4040

4141
mod ca;
4242

43+
/// How long CA certificates should last for. Also used for calculating when they should be rotated.
44+
/// Must be less than half of [`DEFAULT_MAX_CERT_LIFETIME`].
45+
pub const DEFAULT_CA_CERT_LIFETIME: Duration = Duration::from_days_unchecked(365 * 2);
46+
4347
/// As the Pods will be evicted [`DEFAULT_CERT_RESTART_BUFFER`] before
4448
/// the cert actually expires, this results in a restart in approx every 2 weeks,
4549
/// which matches the rolling re-deploy of k8s nodes of e.g.:
@@ -130,18 +134,21 @@ impl TlsGenerate {
130134
/// an independent self-signed CA.
131135
pub async fn get_or_create_k8s_certificate(
132136
client: &stackable_operator::client::Client,
133-
secret_ref: &SecretReference,
134-
auto_generate: bool,
137+
crd::AutoTlsCa {
138+
secret: ca_secret,
139+
auto_generate: auto_generate_ca,
140+
ca_certificate_lifetime,
141+
}: &crd::AutoTlsCa,
135142
max_cert_lifetime: Duration,
136143
) -> Result<Self> {
137144
Ok(Self {
138145
ca_manager: ca::Manager::load_or_create(
139146
client,
140-
secret_ref,
147+
ca_secret,
141148
&ca::Config {
142-
manage_ca: auto_generate,
143-
ca_lifetime: Duration::from_days_unchecked(2 * 365),
144-
rotate_if_ca_expires_before: Some(Duration::from_days_unchecked(365)),
149+
manage_ca: *auto_generate_ca,
150+
ca_certificate_lifetime: *ca_certificate_lifetime,
151+
rotate_if_ca_expires_before: Some(*ca_certificate_lifetime / 2),
145152
},
146153
)
147154
.await

rust/operator-binary/src/crd.rs

+25-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use stackable_operator::{
99
};
1010
use stackable_secret_operator_crd_utils::SecretReference;
1111

12-
use crate::backend::tls::DEFAULT_MAX_CERT_LIFETIME;
12+
use crate::backend;
1313

1414
/// A [SecretClass](DOCS_BASE_URL_PLACEHOLDER/secret-operator/secretclass) is a cluster-global Kubernetes resource
1515
/// that defines a category of secrets that the Secret Operator knows how to provision.
@@ -82,12 +82,14 @@ pub struct AutoTlsBackend {
8282
/// In case consumers request a longer lifetime than allowed by this setting,
8383
/// the lifetime will be the minimum of both, so this setting takes precedence.
8484
/// The default value is 15 days.
85-
#[serde(default = "default_max_certificate_lifetime")]
85+
#[serde(default = "AutoTlsBackend::default_max_certificate_lifetime")]
8686
pub max_certificate_lifetime: Duration,
8787
}
8888

89-
fn default_max_certificate_lifetime() -> Duration {
90-
DEFAULT_MAX_CERT_LIFETIME
89+
impl AutoTlsBackend {
90+
fn default_max_certificate_lifetime() -> Duration {
91+
backend::tls::DEFAULT_MAX_CERT_LIFETIME
92+
}
9193
}
9294

9395
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -102,6 +104,21 @@ pub struct AutoTlsCa {
102104
// TODO: Consider renaming to `manage` for v1alpha2
103105
#[serde(default)]
104106
pub auto_generate: bool,
107+
108+
/// The lifetime of each generated certificate authority.
109+
///
110+
/// Should always be more than double `maxCertificateLifetime`.
111+
///
112+
/// If `autoGenerate: true` then the Secret Operator will prepare a new CA certificate the old CA approaches expiration.
113+
/// If `autoGenerate: false` then the Secret Operator will log a warning instead.
114+
#[serde(default = "AutoTlsCa::default_ca_certificate_lifetime")]
115+
pub ca_certificate_lifetime: Duration,
116+
}
117+
118+
impl AutoTlsCa {
119+
fn default_ca_certificate_lifetime() -> Duration {
120+
backend::tls::DEFAULT_CA_CERT_LIFETIME
121+
}
105122
}
106123

107124
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -256,7 +273,7 @@ mod test {
256273
use super::*;
257274

258275
use crate::{
259-
backend::tls::DEFAULT_MAX_CERT_LIFETIME,
276+
backend::tls::{DEFAULT_CA_CERT_LIFETIME, DEFAULT_MAX_CERT_LIFETIME},
260277
crd::{AutoTlsBackend, SecretClass, SecretClassSpec},
261278
};
262279

@@ -288,6 +305,7 @@ mod test {
288305
namespace: "default".to_string(),
289306
},
290307
auto_generate: false,
308+
ca_certificate_lifetime: DEFAULT_CA_CERT_LIFETIME,
291309
},
292310
max_certificate_lifetime: DEFAULT_MAX_CERT_LIFETIME,
293311
})
@@ -307,6 +325,7 @@ mod test {
307325
name: secret-provisioner-tls-ca
308326
namespace: default
309327
autoGenerate: true
328+
caCertificateLifetime: 100d
310329
maxCertificateLifetime: 31d
311330
"#;
312331
let deserializer = serde_yaml::Deserializer::from_str(input);
@@ -322,6 +341,7 @@ mod test {
322341
namespace: "default".to_string(),
323342
},
324343
auto_generate: true,
344+
ca_certificate_lifetime: Duration::from_days_unchecked(100)
325345
},
326346
max_certificate_lifetime: Duration::from_days_unchecked(31),
327347
})

0 commit comments

Comments
 (0)