Skip to content

Commit

Permalink
add S3 support to check-access, test setup
Browse files Browse the repository at this point in the history
Signed-off-by: Robert Detjens <[email protected]>
  • Loading branch information
detjensrobert committed Oct 29, 2024
1 parent 8396e89 commit 8b977f0
Show file tree
Hide file tree
Showing 8 changed files with 513 additions and 43 deletions.
421 changes: 391 additions & 30 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ tokio = { version = "1.38.0", features = ["rt", "macros"] }
bollard = "0.16.1"
futures-util = "0.3.30"
figment = { version = "0.10.19", features = ["env", "yaml"] }
rust-s3 = { version = "0.35.1", default-features = false, features = [
"fail-on-err",
"tokio-rustls-tls",
] }
85 changes: 82 additions & 3 deletions src/access_handlers/s3.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,88 @@
use anyhow::{Error, Result};
use anyhow::{anyhow, bail, Context, Error, Result};
use s3;
use simplelog::*;
use tokio;

use crate::configparser::{get_config, get_profile_config};
use crate::configparser::{
config::{ProfileConfig, S3Config},
get_config, get_profile_config,
};

/// s3 bucket access checks
pub fn check(profile_name: &str) -> Result<()> {
#[tokio::main(flavor = "current_thread")] // make this a sync function
pub async fn check(profile_name: &str) -> Result<()> {
let profile = get_profile_config(profile_name)?;

let bucket = bucket_client(&profile.s3)?;

if !bucket.exists().await? {
bail!("bucket {} does not exist!", profile.s3.bucket_name);
}

// try uploading file to bucket
debug!("uploading test file to bucket");
let test_file = ("/beavercds-test-file", "access test file!");
bucket
.put_object_with_content_type(test_file.0, test_file.1.as_bytes(), "text/plain")
.await
.with_context(|| format!("could not upload to bucket {:?}", profile.s3.bucket_name))?;

// download it to check
debug!("downloading test file");
let from_bucket = bucket.get_object(test_file.0).await?;
if from_bucket.bytes() != test_file.1 {
bail!("uploaded test file contents do not match, somehow!?");
}

// download as anonymous to check public access
debug!("downloading test file as public user");
let public_bucket = bucket_client_anonymous(&profile.s3)?;
let from_public = public_bucket
.get_object(test_file.0)
.await
.with_context(|| {
anyhow!(
"public download from qbucket {:?} failed",
profile.s3.bucket_name
)
})?;
if from_public.bytes() != test_file.1 {
bail!("contents of public bucket do not match uploaded file");
}

Ok(())
}

/// create bucket client for passed profile config
pub fn bucket_client(config: &S3Config) -> Result<Box<s3::Bucket>> {
trace!("creating bucket client");
// TODO: once_cell this so it reuses the same bucket?
let region = s3::Region::Custom {
region: config.region.clone(),
endpoint: config.endpoint.clone(),
};
let creds = s3::creds::Credentials::new(
Some(&config.access_key),
Some(&config.secret_key),
None,
None,
None,
)?;
let bucket = s3::Bucket::new(&config.bucket_name, region, creds)?.with_path_style();

Ok(bucket)
}

/// create public/anonymous bucket client for passed profile config
pub fn bucket_client_anonymous(config: &S3Config) -> Result<Box<s3::Bucket>> {
trace!("creating anon bucket client");
// TODO: once_cell this so it reuses the same bucket?
let region = s3::Region::Custom {
region: config.region.clone(),
endpoint: config.endpoint.clone(),
};
let creds = s3::creds::Credentials::anonymous()?;
let bucket = s3::Bucket::new(&config.bucket_name, region, creds)?.with_path_style();

Ok(bucket)
}
3 changes: 3 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,8 @@ pub enum Commands {

#[arg(short, long, help = "Check container registry access and permissions")]
registry: bool,

#[arg(short, long, help = "Check S3 asset bucket access and permissions")]
bucket: bool,
},
}
16 changes: 13 additions & 3 deletions src/commands/check_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use std::process::exit;
use crate::access_handlers as access;
use crate::configparser::{get_config, get_profile_config};

pub fn run(profile: &str, kubernetes: &bool, frontend: &bool, registry: &bool) {
pub fn run(profile: &str, kubernetes: &bool, frontend: &bool, registry: &bool, bucket: &bool) {
// if user did not give a specific check, check all of them
let check_all = !kubernetes && !frontend && !registry;
let check_all = !kubernetes && !frontend && !registry && !bucket;

let config = get_config().unwrap();

Expand All @@ -24,6 +24,7 @@ pub fn run(profile: &str, kubernetes: &bool, frontend: &bool, registry: &bool) {
*kubernetes || check_all,
*frontend || check_all,
*registry || check_all,
*bucket || check_all,
)
});

Expand All @@ -38,7 +39,13 @@ pub fn run(profile: &str, kubernetes: &bool, frontend: &bool, registry: &bool) {
}

/// checks a single profile (`profile`) for the given accesses
fn check_profile(name: &str, kubernetes: bool, frontend: bool, registry: bool) -> Result<()> {
fn check_profile(
name: &str,
kubernetes: bool,
frontend: bool,
registry: bool,
bucket: bool,
) -> Result<()> {
info!("checking profile {name}...");

// todo: this works but ehhh
Expand All @@ -53,6 +60,9 @@ fn check_profile(name: &str, kubernetes: bool, frontend: bool, registry: bool) -
if registry {
results.push(access::docker::check(name));
}
if bucket {
results.push(access::s3::check(name));
}

// takes first Err in vec as Result() return
results
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ fn main() {
kubernetes,
frontend,
registry,
bucket,
} => {
commands::validate::run();
commands::check_access::run(profile, kubernetes, frontend, registry)
commands::check_access::run(profile, kubernetes, frontend, registry, bucket)
}

cli::Commands::Build { profile, push } => {
Expand Down
22 changes: 17 additions & 5 deletions tests/services.compose.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# compose to create registry container and ui to see if images pushed ok
services:
registry-server:
container_name: beavercds-registry
image: registry
ports:
- 5000:5000

registry-ui:
container_name: beavercds-registry-ui
image: joxit/docker-registry-ui
ports:
- 8000:80
Expand All @@ -17,12 +15,26 @@ services:
- NGINX_PROXY_PASS_URL=http://registry-server:5000

minio:
container_name: beavercds-minio
image: quay.io/minio/minio
command: server /data --console-address ":9001"
command: server /data --console-address ':9001'
ports:
- 9000:9000
- 9001:9001
environment:
MINIO_ROOT_USER: testuser
MINIO_ROOT_PASSWORD: this_is_not_secure
MINIO_ROOT_PASSWORD: notsecure

# minio image does not set up default buckets or permissions from envvars, so
# use sidecar image to set up test bucket and allow public downloads
createbuckets:
image: quay.io/minio/minio
depends_on:
- minio
entrypoint: >
/bin/sh -xec "
while ! curl --silent http://minio:9001 > /dev/null ; do sleep 1 ; done;
/usr/bin/mc alias set myminio http://minio:9000 testuser notsecure;
/usr/bin/mc mb myminio/testbucket;
/usr/bin/mc anonymous set download myminio/testbucket;
exit 0;
"
2 changes: 1 addition & 1 deletion tests/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ start_stuff (){
# export variables if sourced or echo them if run
export BEAVERCDS_REGISTRY_DOMAIN="host.minikube.internal:5000/testing"
export BEAVERCDS_PROFILES_TESTING_KUBECONTEXT="$MINIKUBE_PROFILE"
export BEAVERCDS_PROFILES_TESTING_S3_ENDPOINT="localhost:9000"
export BEAVERCDS_PROFILES_TESTING_S3_ENDPOINT="http://localhost:9000"
export BEAVERCDS_PROFILES_TESTING_S3_REGION=""
export BEAVERCDS_PROFILES_TESTING_S3_ACCESS_KEY=$(cat $COMPOSE_FILE | yq -r .services.minio.environment.MINIO_ROOT_USER)
export BEAVERCDS_PROFILES_TESTING_S3_SECRET_KEY=$(cat $COMPOSE_FILE | yq -r .services.minio.environment.MINIO_ROOT_PASSWORD)
Expand Down

0 comments on commit 8b977f0

Please sign in to comment.