Skip to content

Commit 66a8d96

Browse files
committed
refactor to also repair possible broken existing Secret objects
1 parent 8a68412 commit 66a8d96

2 files changed

Lines changed: 87 additions & 74 deletions

File tree

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

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1-
use snafu::{ResultExt, Snafu};
1+
use std::collections::BTreeMap;
2+
3+
use snafu::{OptionExt, ResultExt, Snafu};
24
use stackable_operator::{
3-
builder::pod::volume::SecretFormat, client::Client, k8s_openapi::api::core::v1::Volume,
5+
builder::{meta::ObjectMetaBuilder, pod::volume::SecretFormat},
6+
client::Client,
7+
k8s_openapi::api::core::v1::{Secret, Volume},
8+
kube::ResourceExt,
49
shared::time::Duration,
510
};
611

7-
use crate::crd::v1alpha1;
12+
use crate::{
13+
crd::v1alpha1,
14+
security::{
15+
authentication::STACKABLE_ADMIN_USERNAME, oidc::build_oidc_admin_password_secret_name,
16+
},
17+
};
818

919
pub mod authentication;
1020
pub mod authorization;
@@ -16,14 +26,24 @@ type Result<T, E = Error> = std::result::Result<T, E>;
1626

1727
#[derive(Snafu, Debug)]
1828
pub enum Error {
29+
#[snafu(display("the NiFi object defines no namespace"))]
30+
ObjectHasNoNamespace,
31+
1932
#[snafu(display("tls failure"))]
2033
Tls { source: tls::Error },
2134

2235
#[snafu(display("sensistive key failure"))]
2336
SensitiveKey { source: sensitive_key::Error },
2437

25-
#[snafu(display("failed to ensure OIDC admin password exists"))]
26-
OidcAdminPassword { source: oidc::Error },
38+
#[snafu(display("failed to fetch or create OIDC admin password secret"))]
39+
OidcAdminPasswordSecret {
40+
source: stackable_operator::client::Error,
41+
},
42+
43+
#[snafu(display("failed to build OIDC admin password secret metadata"))]
44+
BuildOidcAdminPasswordSecretMetadata {
45+
source: stackable_operator::builder::meta::Error,
46+
},
2747
}
2848

2949
pub async fn check_or_generate_sensitive_key(
@@ -35,14 +55,43 @@ pub async fn check_or_generate_sensitive_key(
3555
.context(SensitiveKeySnafu)
3656
}
3757

58+
/// Build a Secret containing the OIDC admin password.
59+
///
60+
/// If the Secret object already exists and contains the expected key, the existing password is preserved.
61+
/// Otherwise a new Secret object is created with a random password.
62+
///
3863
pub async fn build_oidc_admin_password_secret(
3964
client: &Client,
4065
nifi: &v1alpha1::NifiCluster,
4166
labels: stackable_operator::kvp::ObjectLabels<'_, v1alpha1::NifiCluster>,
4267
) -> Result<stackable_operator::k8s_openapi::api::core::v1::Secret> {
43-
oidc::build_oidc_admin_password_secret(client, nifi, labels)
68+
tracing::debug!("Checking for OIDC admin password configuration");
69+
70+
let namespace: &str = &nifi.namespace().context(ObjectHasNoNamespaceSnafu)?;
71+
let kubernetes_secret_name = build_oidc_admin_password_secret_name(nifi);
72+
73+
let oidc_admin_pass_secret = client
74+
.get_opt::<Secret>(&kubernetes_secret_name, namespace)
4475
.await
45-
.context(OidcAdminPasswordSnafu)
76+
.context(OidcAdminPasswordSecretSnafu)?;
77+
78+
let password = oidc::build_oidc_admin_password_secret(oidc_admin_pass_secret);
79+
80+
Ok(Secret {
81+
metadata: ObjectMetaBuilder::new()
82+
.name_and_namespace(nifi)
83+
.name(build_oidc_admin_password_secret_name(nifi))
84+
.ownerreference_from_resource(nifi, None, Some(true))
85+
.context(BuildOidcAdminPasswordSecretMetadataSnafu)?
86+
.with_recommended_labels(labels)
87+
.context(BuildOidcAdminPasswordSecretMetadataSnafu)?
88+
.build(),
89+
string_data: Some(BTreeMap::from([(
90+
STACKABLE_ADMIN_USERNAME.to_string(),
91+
password,
92+
)])),
93+
..Secret::default()
94+
})
4695
}
4796

4897
pub fn build_tls_volume(

rust/operator-binary/src/security/oidc.rs

Lines changed: 31 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
use std::collections::BTreeMap;
22

33
use rand::{RngExt, distr::Alphanumeric};
4-
use snafu::{OptionExt, ResultExt, Snafu};
4+
use snafu::{ResultExt, Snafu};
55
use stackable_operator::{
6-
builder::meta::ObjectMetaBuilder,
7-
client::Client,
86
commons::tls_verification::{CaCert, TlsServerVerification, TlsVerification},
97
crd::authentication::oidc,
10-
k8s_openapi::api::core::v1::Secret,
11-
kube::{ResourceExt, runtime::reflector::ObjectRef},
12-
kvp::ObjectLabels,
8+
k8s_openapi::{ByteString, api::core::v1::Secret},
9+
kube::ResourceExt,
1310
};
1411

1512
use crate::{crd::v1alpha1, security::authentication::STACKABLE_ADMIN_USERNAME};
@@ -18,93 +15,60 @@ type Result<T, E = Error> = std::result::Result<T, E>;
1815

1916
#[derive(Snafu, Debug)]
2017
pub enum Error {
21-
#[snafu(display("the NiFi object defines no namespace"))]
22-
ObjectHasNoNamespace,
23-
24-
#[snafu(display("failed to fetch or create OIDC admin password secret"))]
25-
OidcAdminPasswordSecret {
26-
source: stackable_operator::client::Error,
27-
},
28-
29-
#[snafu(display(
30-
"found existing admin password secret {secret:?}, but the key {STACKABLE_ADMIN_USERNAME} is missing",
31-
))]
32-
MissingAdminPasswordKey { secret: ObjectRef<Secret> },
33-
3418
#[snafu(display("invalid well-known OIDC configuration URL"))]
3519
InvalidWellKnownConfigUrl {
3620
source: stackable_operator::crd::authentication::oidc::v1alpha1::Error,
3721
},
3822

3923
#[snafu(display("Nifi doesn't support skipping the OIDC TLS verification"))]
4024
SkippingTlsVerificationNotSupported {},
41-
42-
#[snafu(display("failed to build OIDC admin password secret metadata"))]
43-
BuildOidcAdminPasswordSecretMetadata {
44-
source: stackable_operator::builder::meta::Error,
45-
},
4625
}
4726

48-
/// Build a Secret containing the OIDC admin password.
49-
///
50-
/// If the secret already exists, the existing password is preserved.
27+
/// Returns a password to be used by the OIDC admin user.
28+
/// If the Secret containing the password already exists and contains the expected key, the existing password is returned.
5129
/// Otherwise a new random password is generated.
52-
pub(crate) async fn build_oidc_admin_password_secret(
53-
client: &Client,
54-
nifi: &v1alpha1::NifiCluster,
55-
labels: ObjectLabels<'_, v1alpha1::NifiCluster>,
56-
) -> Result<Secret, Error> {
57-
let namespace: &str = &nifi.namespace().context(ObjectHasNoNamespaceSnafu)?;
58-
tracing::debug!("Checking for OIDC admin password configuration");
59-
60-
let password = match client
61-
.get_opt::<Secret>(&build_oidc_admin_password_secret_name(nifi), namespace)
62-
.await
63-
.context(OidcAdminPasswordSecretSnafu)?
64-
{
30+
pub(crate) fn build_oidc_admin_password_secret(oidc_admin_secret: Option<Secret>) -> String {
31+
match oidc_admin_secret {
6532
Some(secret) => {
6633
let existing_password = secret
6734
.data
6835
.as_ref()
6936
.and_then(|data| data.get(STACKABLE_ADMIN_USERNAME))
70-
.map(|bytes| String::from_utf8_lossy(&bytes.0).into_owned());
37+
.map(decode_admin_password);
7138

7239
match existing_password {
7340
Some(password) => password,
74-
None => MissingAdminPasswordKeySnafu {
75-
secret: ObjectRef::from_obj(&secret),
41+
None => {
42+
tracing::info!(
43+
expected_key = STACKABLE_ADMIN_USERNAME,
44+
"Found existing OIDC admin password secret, but it doesn't contain the expected key, generating new password"
45+
);
46+
encode_admin_password(15)
7647
}
77-
.fail()?,
7848
}
7949
}
8050
None => {
8151
tracing::info!("No existing OIDC admin password secret found, generating new one");
82-
rand::rng()
83-
.sample_iter(&Alphanumeric)
84-
.take(15)
85-
.map(char::from)
86-
.collect()
52+
encode_admin_password(15)
8753
}
88-
};
89-
90-
Ok(Secret {
91-
metadata: ObjectMetaBuilder::new()
92-
.name_and_namespace(nifi)
93-
.name(build_oidc_admin_password_secret_name(nifi))
94-
.ownerreference_from_resource(nifi, None, Some(true))
95-
.context(BuildOidcAdminPasswordSecretMetadataSnafu)?
96-
.with_recommended_labels(labels)
97-
.context(BuildOidcAdminPasswordSecretMetadataSnafu)?
98-
.build(),
99-
string_data: Some(BTreeMap::from([(
100-
STACKABLE_ADMIN_USERNAME.to_string(),
101-
password,
102-
)])),
103-
..Secret::default()
104-
})
54+
}
55+
}
56+
57+
// TODO: maybe switch to get_random_base64() (not public atm) from op-rs which is ASCII clean and thus more suitable for passwords:
58+
// https://github.com/stackabletech/operator-rs/blob/main/crates/stackable-operator/src/commons/random_secret_creation.rs#L127-L127
59+
fn encode_admin_password(size_bytes: usize) -> String {
60+
rand::rng()
61+
.sample_iter(&Alphanumeric)
62+
.take(size_bytes)
63+
.map(char::from)
64+
.collect()
65+
}
66+
67+
fn decode_admin_password(encoded: &ByteString) -> String {
68+
String::from_utf8_lossy(&encoded.0).into_owned()
10569
}
10670

107-
pub fn build_oidc_admin_password_secret_name(nifi: &v1alpha1::NifiCluster) -> String {
71+
pub(crate) fn build_oidc_admin_password_secret_name(nifi: &v1alpha1::NifiCluster) -> String {
10872
format!("{}-oidc-admin-password", nifi.name_any())
10973
}
11074

0 commit comments

Comments
 (0)