From c839b087ebb1ac0ec801777891ad65cdc4263c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 21 Jun 2023 19:46:49 +0200 Subject: [PATCH] Enable secret data conversion --- .../operator-binary/src/backend/k8s_search.rs | 6 ++-- rust/operator-binary/src/backend/mod.rs | 19 +++++++----- rust/operator-binary/src/csi_server/node.rs | 14 +++++++-- rust/operator-binary/src/format/mod.rs | 31 ++++++++++++++----- rust/operator-binary/src/format/well_known.rs | 7 ++++- 5 files changed, 58 insertions(+), 19 deletions(-) diff --git a/rust/operator-binary/src/backend/k8s_search.rs b/rust/operator-binary/src/backend/k8s_search.rs index 8682be34..42c863a6 100644 --- a/rust/operator-binary/src/backend/k8s_search.rs +++ b/rust/operator-binary/src/backend/k8s_search.rs @@ -5,7 +5,9 @@ use std::collections::{BTreeMap, HashSet}; use async_trait::async_trait; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ - k8s_openapi::{api::core::v1::Secret, apimachinery::pkg::apis::meta::v1::LabelSelector}, + k8s_openapi::{ + api::core::v1::Secret, apimachinery::pkg::apis::meta::v1::LabelSelector, ByteString, + }, kube::api::ListParams, }; @@ -85,7 +87,7 @@ impl SecretBackend for K8sSearch { .data .unwrap_or_default() .into_iter() - .map(|(k, v)| (k.into(), v.0)) + .map(|(k, ByteString(v))| (k, v)) .collect(), ))) } diff --git a/rust/operator-binary/src/backend/mod.rs b/rust/operator-binary/src/backend/mod.rs index 0b984233..059ee525 100644 --- a/rust/operator-binary/src/backend/mod.rs +++ b/rust/operator-binary/src/backend/mod.rs @@ -10,11 +10,7 @@ pub mod tls; use async_trait::async_trait; use serde::{Deserialize, Deserializer}; use stackable_operator::k8s_openapi::chrono::{DateTime, FixedOffset}; -use std::{ - collections::{HashMap, HashSet}, - convert::Infallible, - path::PathBuf, -}; +use std::{collections::HashSet, convert::Infallible}; pub use dynamic::Dynamic; pub use k8s_search::K8sSearch; @@ -54,7 +50,18 @@ pub struct SecretVolumeSelector { /// The name of the `Pod`'s `Namespace`, provided by Kubelet #[serde(rename = "csi.storage.k8s.io/pod.namespace")] pub namespace: String, + /// The desired format of the mounted secrets + /// + /// Currently supported formats: + /// - (TLS) `pem-certificate` - A Kubernetes-style triple of PEM-encoded certificate files (`tls.crt`, `tls.key`, `ca.crt`). + /// - (TLS) `pkcs12-certificate` - A PKCS#12 trust store named `truststore.p12`. + /// - (Kerberos) `kerberos-keytab` - A Kerberos keytab named `keytab`. + /// + /// Defaults to passing through the native format of the secret backend. + #[serde(rename = "secrets.stackable.tech/secret.format")] + pub format: Option, + /// The Kerberos service names (`SERVICE_NAME/hostname@realm`) #[serde( rename = "secrets.stackable.tech/kerberos.service.names", default = "SecretVolumeSelector::default_kerberos_service_names", @@ -108,8 +115,6 @@ impl SecretVolumeSelector { } } -type SecretFiles = HashMap>; - #[derive(Debug)] pub struct SecretContents { pub data: SecretData, diff --git a/rust/operator-binary/src/csi_server/node.rs b/rust/operator-binary/src/csi_server/node.rs index f556f69b..b5071fa1 100644 --- a/rust/operator-binary/src/csi_server/node.rs +++ b/rust/operator-binary/src/csi_server/node.rs @@ -2,6 +2,7 @@ use crate::{ backend::{ self, pod_info, pod_info::PodInfo, SecretBackendError, SecretContents, SecretVolumeSelector, }, + format::{self, SecretFormat}, grpc::csi::v1::{ node_server::Node, NodeExpandVolumeRequest, NodeExpandVolumeResponse, NodeGetCapabilitiesRequest, NodeGetCapabilitiesResponse, NodeGetInfoRequest, @@ -58,6 +59,8 @@ enum PublishError { source: std::io::Error, path: PathBuf, }, + #[snafu(display("failed to convert secret data into desired format"))] + FormatData { source: format::IntoFilesError }, #[snafu(display("failed to set volume permissions for {}", path.display()))] SetDirPermissions { source: std::io::Error, @@ -94,6 +97,7 @@ impl From for Status { } PublishError::CreateDir { .. } => Status::unavailable(full_msg), PublishError::Mount { .. } => Status::unavailable(full_msg), + PublishError::FormatData { .. } => Status::unavailable(full_msg), PublishError::SetDirPermissions { .. } => Status::unavailable(full_msg), PublishError::CreateFile { .. } => Status::unavailable(full_msg), PublishError::WriteFile { .. } => Status::unavailable(full_msg), @@ -186,6 +190,7 @@ impl SecretProvisionerNode { &self, target_path: &Path, data: SecretContents, + format: Option, ) -> Result<(), PublishError> { let create_secret = { let mut opts = OpenOptions::new(); @@ -197,7 +202,11 @@ impl SecretProvisionerNode { .mode(0o640); opts }; - for (k, v) in data.data.into_files() { + for (k, v) in data + .data + .into_files(format) + .context(publish_error::FormatDataSnafu)? + { let item_path = target_path.join(k); if let Some(item_path_parent) = item_path.parent() { create_dir_all(item_path_parent) @@ -348,7 +357,8 @@ impl Node for SecretProvisionerNode { self.tag_pod(&self.client, &request.volume_id, &selector, &data) .await?; self.prepare_secret_dir(&target_path).await?; - self.save_secret_data(&target_path, data).await?; + self.save_secret_data(&target_path, data, selector.format) + .await?; Ok(Response::new(NodePublishVolumeResponse {})) } .await, diff --git a/rust/operator-binary/src/format/mod.rs b/rust/operator-binary/src/format/mod.rs index 384f507e..4316b296 100644 --- a/rust/operator-binary/src/format/mod.rs +++ b/rust/operator-binary/src/format/mod.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use snafu::Snafu; + pub use self::{ convert::ConvertError, well_known::{FromFilesError as ParseError, SecretFormat, WellKnownSecretData}, @@ -17,17 +19,32 @@ pub enum SecretData { Unknown(SecretFiles), } impl SecretData { - pub fn into_files(self) -> SecretFiles { - match self { - SecretData::WellKnown(data) => data.into_files(), - SecretData::Unknown(files) => files, - } - } - pub fn parse(self) -> Result { match self { Self::WellKnown(x) => Ok(x), Self::Unknown(files) => WellKnownSecretData::from_files(files), } } + + pub fn into_files(self, format: Option) -> Result { + if let Some(format) = format { + Ok(self.parse()?.convert_to(format)?.into_files()) + } else { + Ok(match self { + SecretData::WellKnown(data) => data.into_files(), + SecretData::Unknown(files) => files, + }) + } + } +} + +#[derive(Snafu, Debug)] +pub enum IntoFilesError { + #[snafu(display("failed to parse secret data"), context(false))] + Parse { source: ParseError }, + #[snafu( + display("failed to convert secret data into desired format"), + context(false) + )] + Convert { source: ConvertError }, } diff --git a/rust/operator-binary/src/format/well_known.rs b/rust/operator-binary/src/format/well_known.rs index 17ba5597..7854fb6b 100644 --- a/rust/operator-binary/src/format/well_known.rs +++ b/rust/operator-binary/src/format/well_known.rs @@ -1,4 +1,5 @@ use super::{convert, ConvertError, SecretFiles}; +use serde::Deserialize; use snafu::{OptionExt, Snafu}; use strum::EnumDiscriminants; @@ -32,7 +33,11 @@ pub struct KerberosKeytab { } #[derive(Debug, EnumDiscriminants)] -#[strum_discriminants(name(SecretFormat))] +#[strum_discriminants( + name(SecretFormat), + derive(Deserialize), + serde(rename_all = "kebab-case") +)] pub enum WellKnownSecretData { PemCertificate(PemCertificate), Pkcs12Certificate(Pkcs12Certificate),