From 76c22150c00b13918d7fa7436f7e7e61228c5a7a Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Sat, 6 May 2023 11:47:18 +0200 Subject: [PATCH] Automatically create secret for bucket if missing --- Cargo.lock | 1 + Cargo.toml | 1 + src/constants.rs | 5 ++++- src/lib.rs | 2 +- src/main.rs | 43 ++++++++++++++++++++++++++++------- src/minio.rs | 19 ++++++++++++++++ src/secrets.rs | 50 ++++++++++++++++++++++++++++++++++++++--- test/bucket-policy.yaml | 28 +++++++++++++++++++++++ 8 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 test/bucket-policy.yaml diff --git a/Cargo.lock b/Cargo.lock index b641fe1..9c74a0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -868,6 +868,7 @@ dependencies = [ "k8s-openapi", "kube", "log", + "rand", "schemars", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index c9882b1..ac465dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ kube = { version = "0.82.2", features = ["runtime", "derive"] } k8s-openapi = { version = "0.18.0", features = ["v1_26"] } # TODO : switch to v1_27 futures = "0.3.28" thiserror = "1.0.40" +rand = "0.8.5" diff --git a/src/constants.rs b/src/constants.rs index 53f5800..f9d9341 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -3,4 +3,7 @@ pub const SECRET_MINIO_INSTANCE_ACCESS_KEY: &str = "accessKey"; pub const SECRET_MINIO_INSTANCE_SECRET_KEY: &str = "secretKey"; pub const SECRET_MINIO_BUCKET_ACCESS_KEY: &str = "accessKey"; -pub const SECRET_MINIO_BUCKET_SECRET_KEY: &str = "secretKey"; \ No newline at end of file +pub const SECRET_MINIO_BUCKET_SECRET_KEY: &str = "secretKey"; + +pub const SECRET_MINIO_BUCKET_ACCESS_LEN: usize = 20; +pub const SECRET_MINIO_BUCKET_SECRET_LEN: usize = 35; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 2aab6f6..2130aff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ pub mod constants; pub mod crd; +pub mod minio; pub mod secrets; -pub mod minio; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c8e9878..8863459 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,15 @@ +use std::collections::BTreeMap; use futures::TryStreamExt; use k8s_openapi::api::core::v1::Secret; -use kube::{Api, Client}; use kube::runtime::{watcher, WatchStreamExt}; -use minio_operator::constants::{SECRET_MINIO_INSTANCE_ACCESS_KEY, SECRET_MINIO_INSTANCE_SECRET_KEY}; +use kube::{Api, Client}; +use minio_operator::constants::{ + SECRET_MINIO_BUCKET_ACCESS_KEY, SECRET_MINIO_BUCKET_SECRET_KEY, + SECRET_MINIO_INSTANCE_ACCESS_KEY, SECRET_MINIO_INSTANCE_SECRET_KEY, +}; use minio_operator::crd::{MinioBucket, MinioInstance}; -use minio_operator::minio::MinioService; -use minio_operator::secrets::read_secret_str; +use minio_operator::minio::{MinioService, MinioUser}; +use minio_operator::secrets::{create_secret, read_secret_str}; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -22,14 +26,17 @@ async fn main() -> anyhow::Result<()> { while let Some(b) = bw.try_next().await? { if let Err(e) = apply_bucket(&b, &client).await { - log::error!("Failed to apply desired configuration for applied bucket {} : {}", b.spec.name, e) + log::error!( + "Failed to apply desired configuration for applied bucket {} : {}", + b.spec.name, + e + ) } } Ok(()) } - /// Make sure a bucket is compliant with a desired configuration async fn apply_bucket(b: &MinioBucket, client: &Client) -> anyhow::Result<()> { log::info!("Apply configuration for bucket {}", b.spec.name); @@ -46,8 +53,28 @@ async fn apply_bucket(b: &MinioBucket, client: &Client) -> anyhow::Result<()> { access_key: read_secret_str(&instance_secret, SECRET_MINIO_INSTANCE_ACCESS_KEY)?, secret_key: read_secret_str(&instance_secret, SECRET_MINIO_INSTANCE_SECRET_KEY)?, }; - + + // Get user key & password + let user_secret = match secrets.get_opt(&b.spec.secret).await? { + Some(s) => s, + None => { + log::info!("Needs to create the secret {} for the bucket {}", b.spec.secret, b.spec.name); + + // The secret needs to be created + let new_user = MinioUser::gen_random(); + create_secret(&secrets, &b.spec.secret, BTreeMap::from([ + (SECRET_MINIO_BUCKET_ACCESS_KEY.to_string(), new_user.username), + (SECRET_MINIO_BUCKET_SECRET_KEY.to_string(), new_user.password) + ])).await? + } + }; + let user = MinioUser { + username: read_secret_str(&user_secret, SECRET_MINIO_BUCKET_ACCESS_KEY)?, + password: read_secret_str(&user_secret, SECRET_MINIO_BUCKET_SECRET_KEY)?, + }; + println!("{:?}", service); + println!("{:?}", user); Ok(()) -} \ No newline at end of file +} diff --git a/src/minio.rs b/src/minio.rs index 00a6cb3..408f906 100644 --- a/src/minio.rs +++ b/src/minio.rs @@ -1,6 +1,25 @@ +use rand::distributions::Alphanumeric; +use rand::Rng; +use crate::constants::{SECRET_MINIO_BUCKET_ACCESS_LEN, SECRET_MINIO_BUCKET_SECRET_LEN}; + #[derive(Debug, Clone)] pub struct MinioService { pub hostname: String, pub access_key: String, pub secret_key: String, +} + +#[derive(Debug, Clone)] +pub struct MinioUser { + pub username: String, + pub password: String, +} + +impl MinioUser { + pub fn gen_random() -> Self { + Self { + username: rand::thread_rng().sample_iter(&Alphanumeric).take(SECRET_MINIO_BUCKET_ACCESS_LEN).map(char::from).collect(), + password: rand::thread_rng().sample_iter(&Alphanumeric).take(SECRET_MINIO_BUCKET_SECRET_LEN).map(char::from).collect(), + } + } } \ No newline at end of file diff --git a/src/secrets.rs b/src/secrets.rs index fac72e6..c03b608 100644 --- a/src/secrets.rs +++ b/src/secrets.rs @@ -1,4 +1,8 @@ use k8s_openapi::api::core::v1::Secret; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; +use kube::{Api}; +use std::collections::{BTreeMap}; +use kube::api::PostParams; #[derive(thiserror::Error, Debug)] enum SecretError { @@ -11,10 +15,50 @@ enum SecretError { /// Attempt to read a value contained in a secret. Returns an error in case /// of failure pub fn read_secret_str(s: &Secret, key: &str) -> anyhow::Result { - let data = s.data.as_ref().ok_or(SecretError::MissingData)?; + let data = s.data.as_ref().ok_or(SecretError::MissingData)?; - let value = data.get(key) + let value = data + .get(key) .ok_or(SecretError::MissingKey(key.to_string()))?; Ok(String::from_utf8(value.0.clone())?) -} \ No newline at end of file +} + +/// Create a secret consisting only of string key / value pairs +pub async fn create_secret( + secrets: &Api, + name: &str, + values: BTreeMap, +) -> anyhow::Result { + Ok(secrets + .create( + &PostParams::default(), + &Secret { + data: None, + immutable: None, + metadata: ObjectMeta { + annotations: None, + creation_timestamp: None, + deletion_grace_period_seconds: None, + deletion_timestamp: None, + finalizers: None, + generate_name: None, + generation: None, + labels: Some(BTreeMap::from([( + "created-by".to_string(), + "miniok8sbuckets".to_string(), + )])), + managed_fields: None, + name: Some(name.to_string()), + namespace: None, + owner_references: None, + resource_version: None, + self_link: None, + uid: None, + }, + string_data: Some(values), + type_: None, + }, + ) + .await?) +} diff --git a/test/bucket-policy.yaml b/test/bucket-policy.yaml new file mode 100644 index 0000000..951f4d8 --- /dev/null +++ b/test/bucket-policy.yaml @@ -0,0 +1,28 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ListObjectsInBucket", + "Effect": "Allow", + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::bucket" + ] + }, + { + "Sid": "AllObjectActions", + "Effect": "Allow", + "Action": [ + "s3:DeleteObject", + "s3:Get*", + "s3:PutObject", + "s3:*Object" + ], + "Resource": [ + "arn:aws:s3:::bucket/*" + ] + } + ] +} \ No newline at end of file