Skip to content

Commit 76ebb88

Browse files
committed
Mark pods for expiry when secrets are no longer applicable (#114)
## Description Fixes #92.
1 parent 6087d2c commit 76ebb88

File tree

12 files changed

+235
-72
lines changed

12 files changed

+235
-72
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
### Added
8+
9+
- Pods that use autoTls volumes are now evicted when their certificates are about to expire ([#114], [commons-#20]).
10+
711
### Changed
812

913
- autoTls CA generation now requires opt-in ([#77]).
1014
- The default `tls` `SecretClass` now has this opt-in by default.
1115

1216
[#77]: https://github.com/stackabletech/secret-operator/pull/77
17+
[#114]: https://github.com/stackabletech/secret-operator/pull/114
18+
[commons-#20]: https://github.com/stackabletech/commons-operator/pull/20
1319

1420
## [0.2.0] - 2022-02-14
1521

Tiltfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
custom_build(
22
'docker.stackable.tech/teozkr/secret-provisioner',
33
'nix run -f . crate2nix generate && nix-build . -A docker --arg dockerTag null && ./result/load-image | docker load',
4-
deps=['src', 'Cargo.toml', 'Cargo.lock', 'default.nix', 'build.rs', 'vendor'],
4+
deps=['rust', 'Cargo.toml', 'Cargo.lock', 'default.nix', 'build.rs', 'vendor'],
55
# ignore=['result*', 'Cargo.nix', 'target', *.yaml],
66
outputs_image_ref_to='result/ref',
77
)

deploy/helm/secret-operator/templates/roles.yaml

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ rules:
1515
- apiGroups:
1616
- ""
1717
resources:
18-
- pods
1918
- nodes
2019
verbs:
2120
- get
21+
- apiGroups:
22+
- ""
23+
resources:
24+
- pods
25+
verbs:
26+
- get
27+
- patch
2228
- apiGroups:
2329
- secrets.stackable.tech
2430
resources:

deploy/manifests/roles.yaml

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ rules:
1515
- apiGroups:
1616
- ""
1717
resources:
18-
- pods
1918
- nodes
2019
verbs:
2120
- get
21+
- apiGroups:
22+
- ""
23+
resources:
24+
- pods
25+
verbs:
26+
- get
27+
- patch
2228
- apiGroups:
2329
- secrets.stackable.tech
2430
resources:

provisioner.yaml

+27-21
Original file line numberDiff line numberDiff line change
@@ -60,27 +60,33 @@ kind: ClusterRole
6060
metadata:
6161
name: secret-provisioner
6262
rules:
63-
- apiGroups:
64-
- ""
65-
resources:
66-
- secrets
67-
verbs:
68-
- list
69-
- create
70-
- get
71-
- apiGroups:
72-
- ""
73-
resources:
74-
- pods
75-
- nodes
76-
verbs:
77-
- get
78-
- apiGroups:
79-
- secrets.stackable.tech
80-
resources:
81-
- secretclasses
82-
verbs:
83-
- get
63+
- apiGroups:
64+
- ""
65+
resources:
66+
- secrets
67+
verbs:
68+
- list
69+
- create
70+
- get
71+
- apiGroups:
72+
- ""
73+
resources:
74+
- nodes
75+
verbs:
76+
- get
77+
- apiGroups:
78+
- ""
79+
resources:
80+
- pods
81+
verbs:
82+
- get
83+
- patch
84+
- apiGroups:
85+
- secrets.stackable.tech
86+
resources:
87+
- secretclasses
88+
verbs:
89+
- get
8490
---
8591
apiVersion: rbac.authorization.k8s.io/v1
8692
kind: ClusterRoleBinding

rust/operator-binary/Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ repository = "https://github.com/stackabletech/secret-operator"
1010
[dependencies]
1111
anyhow = "1.0.53"
1212
async-trait = "0.1.52"
13-
clap = { version = "3.1.9", features = ["derive", "env"] }
13+
clap = { version = "3.1.11", features = ["derive", "env"] }
1414
futures = "0.3.21"
1515
libc = "0.2.117"
1616
openssl = "0.10.38"
@@ -34,6 +34,9 @@ tracing = "0.1.30"
3434
# Need to keep this in sync with our patched h2 build
3535
h2 = "=0.3.7"
3636

37+
[dev-dependencies]
38+
time = { version = "0.3.7", features = ["parsing"] }
39+
3740
[build-dependencies]
3841
built = { version = "0.5.1", features = ["chrono", "git2"] }
3942
tonic-build = "0.6.2"

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ impl<B: SecretBackend + Send + Sync> SecretBackend for DynamicAdapter<B> {
3434

3535
async fn get_secret_data(
3636
&self,
37-
selector: super::SecretVolumeSelector,
37+
selector: &super::SecretVolumeSelector,
3838
pod_info: PodInfo,
39-
) -> Result<super::SecretFiles, Self::Error> {
39+
) -> Result<super::SecretContents, Self::Error> {
4040
self.0
4141
.get_secret_data(selector, pod_info)
4242
.await

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

+14-11
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use stackable_operator::{
1212
use crate::crd::SearchNamespace;
1313

1414
use super::{
15-
pod_info::PodInfo, scope::SecretScope, SecretBackend, SecretBackendError, SecretFiles,
15+
pod_info::PodInfo, scope::SecretScope, SecretBackend, SecretBackendError, SecretContents,
1616
SecretVolumeSelector,
1717
};
1818

@@ -52,15 +52,15 @@ impl SecretBackend for K8sSearch {
5252

5353
async fn get_secret_data(
5454
&self,
55-
selector: SecretVolumeSelector,
55+
selector: &SecretVolumeSelector,
5656
pod_info: PodInfo,
57-
) -> Result<SecretFiles, Self::Error> {
57+
) -> Result<SecretContents, Self::Error> {
5858
let mut label_selector = self.secret_labels.clone();
5959
label_selector.insert(
6060
"secrets.stackable.tech/class".to_string(),
6161
selector.class.to_string(),
6262
);
63-
for scope in selector.scope {
63+
for scope in &selector.scope {
6464
match scope {
6565
SecretScope::Node => {
6666
label_selector.insert(
@@ -75,7 +75,8 @@ impl SecretBackend for K8sSearch {
7575
);
7676
}
7777
SecretScope::Service { name } => {
78-
label_selector.insert("secrets.stackable.tech/service".to_string(), name);
78+
label_selector
79+
.insert("secrets.stackable.tech/service".to_string(), name.clone());
7980
}
8081
}
8182
}
@@ -101,11 +102,13 @@ impl SecretBackend for K8sSearch {
101102
.into_iter()
102103
.next()
103104
.context(NoSecretSnafu { label_selector })?;
104-
Ok(secret
105-
.data
106-
.unwrap_or_default()
107-
.into_iter()
108-
.map(|(k, v)| (k.into(), v.0))
109-
.collect())
105+
Ok(SecretContents::new(
106+
secret
107+
.data
108+
.unwrap_or_default()
109+
.into_iter()
110+
.map(|(k, v)| (k.into(), v.0))
111+
.collect(),
112+
))
110113
}
111114
}

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

+23-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod tls;
88

99
use async_trait::async_trait;
1010
use serde::Deserialize;
11+
use stackable_operator::k8s_openapi::chrono::{DateTime, FixedOffset};
1112
use std::{collections::HashMap, convert::Infallible, path::PathBuf};
1213

1314
pub use dynamic::Dynamic;
@@ -85,6 +86,26 @@ impl SecretVolumeSelector {
8586

8687
type SecretFiles = HashMap<PathBuf, Vec<u8>>;
8788

89+
#[derive(Default, Debug)]
90+
pub struct SecretContents {
91+
pub files: SecretFiles,
92+
pub expires_after: Option<DateTime<FixedOffset>>,
93+
}
94+
95+
impl SecretContents {
96+
fn new(files: SecretFiles) -> Self {
97+
Self {
98+
files,
99+
..Self::default()
100+
}
101+
}
102+
103+
fn expires_after(mut self, deadline: DateTime<FixedOffset>) -> Self {
104+
self.expires_after = Some(deadline);
105+
self
106+
}
107+
}
108+
88109
/// This trait needs to be implemented by all secret providers.
89110
/// It gets the pod information as well as volume definition and has to
90111
/// return any number of files.
@@ -94,9 +115,9 @@ pub trait SecretBackend: Send + Sync {
94115

95116
async fn get_secret_data(
96117
&self,
97-
selector: SecretVolumeSelector,
118+
selector: &SecretVolumeSelector,
98119
pod_info: pod_info::PodInfo,
99-
) -> Result<SecretFiles, Self::Error>;
120+
) -> Result<SecretContents, Self::Error>;
100121
}
101122

102123
pub trait SecretBackendError: std::error::Error + Send + Sync + 'static {

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

+54-23
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ use stackable_operator::{
2222
builder::ObjectMetaBuilder,
2323
k8s_openapi::{
2424
api::core::v1::{Secret, SecretReference},
25+
chrono::{self, FixedOffset, TimeZone},
2526
ByteString,
2627
},
2728
kube::runtime::reflector::ObjectRef,
2829
};
2930
use time::{Duration, OffsetDateTime};
3031

31-
use super::{pod_info::Address, pod_info::PodInfo, SecretBackend, SecretBackendError, SecretFiles};
32+
use super::{
33+
pod_info::Address, pod_info::PodInfo, SecretBackend, SecretBackendError, SecretContents,
34+
};
3235

3336
#[derive(Debug, Snafu)]
3437
pub enum Error {
@@ -238,12 +241,13 @@ impl SecretBackend for TlsGenerate {
238241
/// Then add the ca certificate and return these files for provisioning to the volume.
239242
async fn get_secret_data(
240243
&self,
241-
selector: super::SecretVolumeSelector,
244+
selector: &super::SecretVolumeSelector,
242245
pod_info: PodInfo,
243-
) -> Result<SecretFiles, Self::Error> {
246+
) -> Result<SecretContents, Self::Error> {
244247
let now = OffsetDateTime::now_utc();
245248
let not_before = now - Duration::minutes(5);
246249
let not_after = now + Duration::days(1);
250+
let expire_pod_after = not_after - Duration::minutes(30);
247251
let conf = Conf::new(ConfMethod::default()).unwrap();
248252
let pod_key = Rsa::generate(2048)
249253
.and_then(PKey::try_from)
@@ -307,26 +311,53 @@ impl SecretBackend for TlsGenerate {
307311
})
308312
.context(BuildCertificateSnafu { tpe: CertType::Pod })?
309313
.build();
310-
Ok([
311-
(
312-
"ca.crt".into(),
313-
self.ca_cert
314-
.to_pem()
315-
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
316-
),
317-
(
318-
"tls.crt".into(),
319-
pod_cert
320-
.to_pem()
321-
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
322-
),
323-
(
324-
"tls.key".into(),
325-
pod_key
326-
.private_key_to_pem_pkcs8()
327-
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
314+
Ok(SecretContents::new(
315+
[
316+
(
317+
"ca.crt".into(),
318+
self.ca_cert
319+
.to_pem()
320+
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
321+
),
322+
(
323+
"tls.crt".into(),
324+
pod_cert
325+
.to_pem()
326+
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
327+
),
328+
(
329+
"tls.key".into(),
330+
pod_key
331+
.private_key_to_pem_pkcs8()
332+
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
333+
),
334+
]
335+
.into(),
336+
)
337+
.expires_after(time_datetime_to_chrono(expire_pod_after)))
338+
}
339+
}
340+
341+
fn time_datetime_to_chrono(dt: time::OffsetDateTime) -> chrono::DateTime<FixedOffset> {
342+
let tz = chrono::FixedOffset::east(dt.offset().whole_seconds());
343+
tz.timestamp(dt.unix_timestamp(), dt.nanosecond())
344+
}
345+
346+
#[cfg(test)]
347+
mod tests {
348+
use time::format_description::well_known::Rfc3339;
349+
350+
use super::chrono;
351+
use super::time_datetime_to_chrono;
352+
353+
#[test]
354+
fn datetime_conversion() {
355+
// Conversion should preserve timezone and fractional seconds
356+
assert_eq!(
357+
time_datetime_to_chrono(
358+
time::OffsetDateTime::parse("2021-02-04T05:23:00.123+01:00", &Rfc3339).unwrap()
328359
),
329-
]
330-
.into())
360+
chrono::DateTime::parse_from_rfc3339("2021-02-04T06:23:00.123+02:00").unwrap()
361+
);
331362
}
332363
}

0 commit comments

Comments
 (0)