Skip to content

Commit e9e9011

Browse files
committed
Merge remote-tracking branch 'origin/main' into spike/generic-databases
2 parents 106dba4 + 930396f commit e9e9011

15 files changed

Lines changed: 609 additions & 23 deletions

File tree

crates/stackable-operator/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ All notable changes to this project will be documented in this file.
1414
### Changed
1515

1616
- Implement `Deref` for `kvp::Key` to be more ergonomic to use ([#1182]).
17+
- BREAKING: Add mandatory `provision_parts` argument to `SecretOperatorVolumeSourceBuilder::new` ([#1165]).
18+
It now forces the caller to make an explicit choice if the public parts are sufficient or if private
19+
(e.g. a certificate for the Pod) parts are needed as well. This is done to avoid accidentally requesting
20+
too much parts. For details see [this issue](https://github.com/stackabletech/issues/issues/547).
21+
22+
Additionally, `SecretClassVolume::to_volume` and `SecretClassVolume::to_ephemeral_volume_source`
23+
also take the same new argument.
1724
- BREAKING: Change signature of `ContainerBuilder::add_env_vars` from `Vec<EnvVar>` to `IntoIterator<Item = EnvVar>` ([#1163]).
1825

1926
### Removed
@@ -23,6 +30,7 @@ All notable changes to this project will be documented in this file.
2330

2431
[#1154]: https://github.com/stackabletech/operator-rs/pull/1154
2532
[#1163]: https://github.com/stackabletech/operator-rs/pull/1163
33+
[#1165]: https://github.com/stackabletech/operator-rs/pull/1165
2634
[#1178]: https://github.com/stackabletech/operator-rs/pull/1178
2735
[#1182]: https://github.com/stackabletech/operator-rs/pull/1182
2836

crates/stackable-operator/src/builder/pod/volume.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use tracing::warn;
1414

1515
use crate::{
1616
builder::meta::ObjectMetaBuilder,
17+
commons::secret_class::SecretClassVolumeProvisionParts,
1718
kvp::{Annotation, AnnotationError, Annotations, LabelError, Labels},
1819
};
1920

@@ -281,17 +282,29 @@ pub struct SecretOperatorVolumeSourceBuilder {
281282
kerberos_service_names: Vec<String>,
282283
tls_pkcs12_password: Option<String>,
283284
auto_tls_cert_lifetime: Option<Duration>,
285+
provision_parts: SecretClassVolumeProvisionParts,
284286
}
285287

286288
impl SecretOperatorVolumeSourceBuilder {
287-
pub fn new(secret_class: impl Into<String>) -> Self {
289+
/// Creates a builder for a secret-operator volume that uses the specified SecretClass to
290+
/// request the specified [`SecretClassVolumeProvisionParts`].
291+
///
292+
/// This function forces the caller to make an explicit choice if the public parts are
293+
/// sufficient or if private (e.g. a certificate for the Pod) parts are needed as well.
294+
/// This is done to avoid accidentally requesting too much parts. For details see
295+
/// [this issue](https://github.com/stackabletech/issues/issues/547).
296+
pub fn new(
297+
secret_class: impl Into<String>,
298+
provision_parts: SecretClassVolumeProvisionParts,
299+
) -> Self {
288300
Self {
289301
secret_class: secret_class.into(),
290302
scopes: Vec::new(),
291303
format: None,
292304
kerberos_service_names: Vec::new(),
293305
tls_pkcs12_password: None,
294306
auto_tls_cert_lifetime: None,
307+
provision_parts,
295308
}
296309
}
297310

@@ -340,8 +353,10 @@ impl SecretOperatorVolumeSourceBuilder {
340353
pub fn build(&self) -> Result<EphemeralVolumeSource, SecretOperatorVolumeSourceBuilderError> {
341354
let mut annotations = Annotations::new();
342355

356+
#[rustfmt::skip]
343357
annotations
344-
.insert(Annotation::secret_class(&self.secret_class).context(ParseAnnotationSnafu)?);
358+
.insert(Annotation::secret_class(&self.secret_class).context(ParseAnnotationSnafu)?)
359+
.insert(Annotation::secret_provision_parts(&self.provision_parts).context(ParseAnnotationSnafu)?);
345360

346361
if !self.scopes.is_empty() {
347362
annotations

crates/stackable-operator/src/commons/secret_class.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ impl SecretClassVolume {
3838

3939
pub fn to_ephemeral_volume_source(
4040
&self,
41+
provision_parts: SecretClassVolumeProvisionParts,
4142
) -> Result<EphemeralVolumeSource, SecretClassVolumeError> {
4243
let mut secret_operator_volume_builder =
43-
SecretOperatorVolumeSourceBuilder::new(&self.secret_class);
44+
SecretOperatorVolumeSourceBuilder::new(&self.secret_class, provision_parts);
4445

4546
if let Some(scope) = &self.scope {
4647
if scope.pod {
@@ -62,8 +63,12 @@ impl SecretClassVolume {
6263
.context(SecretOperatorVolumeSnafu)
6364
}
6465

65-
pub fn to_volume(&self, volume_name: &str) -> Result<Volume, SecretClassVolumeError> {
66-
let ephemeral = self.to_ephemeral_volume_source()?;
66+
pub fn to_volume(
67+
&self,
68+
volume_name: &str,
69+
provision_parts: SecretClassVolumeProvisionParts,
70+
) -> Result<Volume, SecretClassVolumeError> {
71+
let ephemeral = self.to_ephemeral_volume_source(provision_parts)?;
6772
Ok(VolumeBuilder::new(volume_name).ephemeral(ephemeral).build())
6873
}
6974
}
@@ -94,14 +99,33 @@ pub struct SecretClassVolumeScope {
9499
pub listener_volumes: Vec<String>,
95100
}
96101

102+
/// What parts of secret material should be provisioned into the requested volume.
103+
//
104+
// There intentionally isn't a global [`Default`] impl, as it is secret-operator's concern what it
105+
// chooses as a default.
106+
// TODO (@Techassi): This to me is a HUGE indicator this lives in the wrong place. All these secret
107+
// volume builders/helpers should be defined as part of a secret-operator library to be as close as
108+
// possible to secret-operator, which is the authoritative source of truth for all of this.
109+
#[derive(Copy, Clone, Debug, PartialEq, Eq, strum::AsRefStr)]
110+
#[strum(serialize_all = "kebab-case")]
111+
pub enum SecretClassVolumeProvisionParts {
112+
/// Only provision public parts, such as the CA certificate (either as PEM or truststore) or
113+
/// `krb5.conf`.
114+
Public,
115+
116+
/// Provision all parts, which includes all [`Public`](Self::Public) ones as well as additional
117+
/// private parts, such as a TLS cert + private key, a keystore or a keytab.
118+
PublicPrivate,
119+
}
120+
97121
#[cfg(test)]
98122
mod tests {
99123
use std::collections::BTreeMap;
100124

101125
use super::*;
102126

103127
#[test]
104-
fn volume_to_csi_volume_source() {
128+
fn volume_to_ephemeral_volume_source() {
105129
let secret_class_volume_source = SecretClassVolume {
106130
secret_class: "myclass".to_string(), // pragma: allowlist secret
107131
scope: Some(SecretClassVolumeScope {
@@ -111,7 +135,8 @@ mod tests {
111135
listener_volumes: vec!["mylistener".to_string()],
112136
}),
113137
}
114-
.to_ephemeral_volume_source()
138+
// Let's assume we need some form of private data (e.g. a certificate or S3 credentials)
139+
.to_ephemeral_volume_source(SecretClassVolumeProvisionParts::PublicPrivate)
115140
.unwrap();
116141

117142
let expected_volume_attributes = BTreeMap::from([
@@ -123,6 +148,10 @@ mod tests {
123148
"secrets.stackable.tech/scope".to_string(),
124149
"pod,service=myservice,listener-volume=mylistener".to_string(),
125150
),
151+
(
152+
"secrets.stackable.tech/provision-parts".to_string(),
153+
"public-private".to_string(),
154+
),
126155
]);
127156

128157
assert_eq!(

crates/stackable-operator/src/commons/tls_verification.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use crate::{
88
self,
99
pod::{PodBuilder, container::ContainerBuilder, volume::VolumeMountBuilder},
1010
},
11-
commons::secret_class::{SecretClassVolume, SecretClassVolumeError},
11+
commons::secret_class::{
12+
SecretClassVolume, SecretClassVolumeError, SecretClassVolumeProvisionParts,
13+
},
1214
constants::secret::SECRET_BASE_PATH,
1315
};
1416

@@ -107,7 +109,8 @@ impl TlsClientDetails {
107109
let volume_name = format!("{secret_class}-ca-cert");
108110
let secret_class_volume = SecretClassVolume::new(secret_class.clone(), None);
109111
let volume = secret_class_volume
110-
.to_volume(&volume_name)
112+
// We only need the public CA cert
113+
.to_volume(&volume_name, SecretClassVolumeProvisionParts::Public)
111114
.context(SecretClassVolumeSnafu)?;
112115

113116
volumes.push(volume);

crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ use crate::{
77
self,
88
pod::{PodBuilder, container::ContainerBuilder, volume::VolumeMountBuilder},
99
},
10-
commons::{secret_class::SecretClassVolumeError, tls_verification::TlsClientDetailsError},
10+
commons::{
11+
secret_class::{SecretClassVolumeError, SecretClassVolumeProvisionParts},
12+
tls_verification::TlsClientDetailsError,
13+
},
1114
constants::secret::SECRET_BASE_PATH,
1215
crd::authentication::ldap::v1alpha1::{AuthenticationProvider, FieldNames},
1316
};
@@ -94,7 +97,8 @@ impl AuthenticationProvider {
9497
let secret_class = &bind_credentials.secret_class;
9598
let volume_name = format!("{secret_class}-bind-credentials");
9699
let volume = bind_credentials
97-
.to_volume(&volume_name)
100+
// We need the private LDAP bind credentials
101+
.to_volume(&volume_name, SecretClassVolumeProvisionParts::PublicPrivate)
98102
.context(BindCredentialsSnafu)?;
99103

100104
volumes.push(volume);
@@ -234,7 +238,10 @@ mod tests {
234238
secret_class: "ldap-ca-cert".to_string(),
235239
scope: None,
236240
}
237-
.to_volume("ldap-ca-cert-ca-cert")
241+
.to_volume(
242+
"ldap-ca-cert-ca-cert",
243+
SecretClassVolumeProvisionParts::Public
244+
)
238245
.unwrap()
239246
]
240247
);
@@ -263,13 +270,19 @@ mod tests {
263270
secret_class: "openldap-bind-credentials".to_string(),
264271
scope: None,
265272
}
266-
.to_volume("openldap-bind-credentials-bind-credentials")
273+
.to_volume(
274+
"openldap-bind-credentials-bind-credentials",
275+
SecretClassVolumeProvisionParts::PublicPrivate
276+
)
267277
.unwrap(),
268278
SecretClassVolume {
269279
secret_class: "ldap-ca-cert".to_string(),
270280
scope: None,
271281
}
272-
.to_volume("ldap-ca-cert-ca-cert")
282+
.to_volume(
283+
"ldap-ca-cert-ca-cert",
284+
SecretClassVolumeProvisionParts::Public
285+
)
273286
.unwrap()
274287
]
275288
);

crates/stackable-operator/src/crd/git_sync/v1alpha2_impl.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{
1515
commons::{
1616
self,
1717
product_image_selection::ResolvedProductImage,
18-
secret_class::SecretClassVolume,
18+
secret_class::{SecretClassVolume, SecretClassVolumeProvisionParts},
1919
tls_verification::{CaCert, TlsServerVerification, TlsVerification},
2020
},
2121
crd::git_sync::v1alpha2::{Credentials, GitSync},
@@ -276,7 +276,8 @@ impl GitSyncResources {
276276
let secret_class_volume = SecretClassVolume::new(secret_class.clone(), None);
277277
let volume_name = format!("{CA_CERT_VOLUME_NAME_PREFIX}-{i}");
278278
let ca_cert_secret_volume = secret_class_volume
279-
.to_volume(&volume_name)
279+
// We only need the public CA cert
280+
.to_volume(&volume_name, SecretClassVolumeProvisionParts::Public)
280281
.context(SecretClassVolumeSnafu)?;
281282
resources.git_ca_cert_volumes.push(ca_cert_secret_volume);
282283
}
@@ -1390,6 +1391,7 @@ name: content-from-git-0
13901391
metadata:
13911392
annotations:
13921393
secrets.stackable.tech/class: git-tls-ca
1394+
secrets.stackable.tech/provision-parts: public
13931395
spec:
13941396
accessModes:
13951397
- ReadWriteOnce

crates/stackable-operator/src/crd/s3/connection/mod.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,16 @@ mod tests {
174174
.unwrap()
175175
.annotations
176176
.unwrap(),
177-
&BTreeMap::from([(
178-
"secrets.stackable.tech/class".to_string(),
179-
"ionos-s3-credentials".to_string()
180-
)]),
177+
&BTreeMap::from([
178+
(
179+
"secrets.stackable.tech/class".to_string(),
180+
"ionos-s3-credentials".to_string()
181+
),
182+
(
183+
"secrets.stackable.tech/provision-parts".to_string(),
184+
"public-private".to_string()
185+
)
186+
]),
181187
);
182188

183189
assert_eq!(mount.name, volume.name);

crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use url::Url;
55
use crate::{
66
builder::pod::{PodBuilder, container::ContainerBuilder, volume::VolumeMountBuilder},
77
client::Client,
8-
commons::{secret_class::SecretClassVolumeError, tls_verification::TlsClientDetailsError},
8+
commons::{
9+
secret_class::{SecretClassVolumeError, SecretClassVolumeProvisionParts},
10+
tls_verification::TlsClientDetailsError,
11+
},
912
constants::secret::SECRET_BASE_PATH,
1013
crd::s3::{
1114
connection::ResolvedConnection,
@@ -110,7 +113,8 @@ impl ConnectionSpec {
110113

111114
volumes.push(
112115
credentials
113-
.to_volume(&volume_name)
116+
// We need the private S3 credentials
117+
.to_volume(&volume_name, SecretClassVolumeProvisionParts::PublicPrivate)
114118
.context(AddS3CredentialVolumesSnafu)?,
115119
);
116120
mounts.push(

crates/stackable-operator/src/kvp/annotation/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use delegate::delegate;
1919

2020
use crate::{
2121
builder::pod::volume::SecretOperatorVolumeScope,
22+
commons::secret_class::SecretClassVolumeProvisionParts,
2223
iter::TryFromIterator,
2324
kvp::{Key, KeyValuePair, KeyValuePairError, KeyValuePairs, KeyValuePairsError},
2425
};
@@ -80,6 +81,15 @@ impl Annotation {
8081
self.0
8182
}
8283

84+
/// Constructs a `secrets.stackable.tech/provision-parts` annotation.
85+
pub fn secret_provision_parts(
86+
provision_parts: &SecretClassVolumeProvisionParts,
87+
) -> Result<Self, AnnotationError> {
88+
let kvp =
89+
KeyValuePair::try_from(("secrets.stackable.tech/provision-parts", provision_parts))?;
90+
Ok(Self(kvp))
91+
}
92+
8393
/// Constructs a `secrets.stackable.tech/class` annotation.
8494
pub fn secret_class(secret_class: &str) -> Result<Self, AnnotationError> {
8595
let kvp = KeyValuePair::try_from(("secrets.stackable.tech/class", secret_class))?;

crates/stackable-versioned-macros/src/attrs/module.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,11 @@ pub struct ModuleSkipArguments {
9595

9696
/// This struct contains crate overrides to be passed to `#[kube]`.
9797
#[derive(Clone, Debug, FromMeta)]
98+
#[darling(and_then = "CrateArguments::dynamic_default")]
9899
pub struct CrateArguments {
100+
#[darling(default = default_kube)]
101+
pub kube: Override<Path>,
102+
99103
#[darling(default = default_kube_core)]
100104
pub kube_core: Override<Path>,
101105

@@ -124,6 +128,7 @@ pub struct CrateArguments {
124128
impl Default for CrateArguments {
125129
fn default() -> Self {
126130
Self {
131+
kube: default_kube(),
127132
kube_core: default_kube_core(),
128133
kube_client: default_kube_client(),
129134
k8s_openapi: default_k8s_openapi(),
@@ -136,6 +141,30 @@ impl Default for CrateArguments {
136141
}
137142
}
138143

144+
impl CrateArguments {
145+
fn dynamic_default(mut self) -> Result<Self> {
146+
// Adjust the kube_core and kube_client paths automatically if only the kube path is
147+
// overridden.
148+
if let Override::Explicit(kube_path) = &self.kube {
149+
if self.kube_core.is_explicit() || self.kube_client.is_explicit() {
150+
return Err(
151+
Error::custom("the `kube` crate override is mutually exclusive with `kube_core` and `kube_client`")
152+
.with_span(&kube_path)
153+
);
154+
}
155+
156+
self.kube_core = Override::Explicit(parse_quote! { #kube_path::core });
157+
self.kube_client = Override::Explicit(parse_quote! { #kube_path::client });
158+
}
159+
160+
Ok(self)
161+
}
162+
}
163+
164+
fn default_kube() -> Override<Path> {
165+
Override::Default(parse_quote! { ::kube })
166+
}
167+
139168
fn default_kube_core() -> Override<Path> {
140169
Override::Default(parse_quote! { ::kube::core })
141170
}
@@ -262,3 +291,12 @@ impl<T> Deref for Override<T> {
262291
}
263292
}
264293
}
294+
295+
impl<T> Override<T> {
296+
pub fn is_explicit(&self) -> bool {
297+
match self {
298+
Override::Default(_) => false,
299+
Override::Explicit(_) => true,
300+
}
301+
}
302+
}

0 commit comments

Comments
 (0)