Automatically create secret for bucket if missing

This commit is contained in:
Pierre HUBERT 2023-05-06 11:47:18 +02:00
parent 36aaf5fb4d
commit 76c22150c0
8 changed files with 136 additions and 13 deletions

1
Cargo.lock generated
View File

@ -868,6 +868,7 @@ dependencies = [
"k8s-openapi",
"kube",
"log",
"rand",
"schemars",
"serde",
"serde_json",

View File

@ -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"

View File

@ -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";
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;

View File

@ -1,4 +1,4 @@
pub mod constants;
pub mod crd;
pub mod minio;
pub mod secrets;
pub mod minio;

View File

@ -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(())
}
}

View File

@ -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(),
}
}
}

View File

@ -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<String> {
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())?)
}
}
/// Create a secret consisting only of string key / value pairs
pub async fn create_secret(
secrets: &Api<Secret>,
name: &str,
values: BTreeMap<String, String>,
) -> anyhow::Result<Secret> {
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?)
}

28
test/bucket-policy.yaml Normal file
View File

@ -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/*"
]
}
]
}