use super::*;
impl BucketWarden {
pub fn get_bucket_abac(
&mut self,
principal: &str,
bucket: &str,
) -> Result<BucketAbacStatus, RuntimeError> {
self.authorize(principal, S3Action::GetBucketAbac, bucket)?;
let status = if self.require_bucket(bucket)?.abac_enabled {
"Enabled"
} else {
"Disabled"
}
.to_string();
self.audit_allowed(
principal,
S3Action::GetBucketAbac,
bucket,
Some(status.clone()),
);
Ok(BucketAbacStatus {
bucket: bucket.to_string(),
status,
})
}
pub fn put_bucket_abac(
&mut self,
principal: &str,
bucket: &str,
status: String,
) -> Result<BucketAbacStatus, RuntimeError> {
self.authorize(principal, S3Action::PutBucketAbac, bucket)?;
if crate::bucket_basics::is_directory_bucket_name(bucket) {
return Err(RuntimeError::InvalidBucketName(
"PutBucketAbac is only supported for general purpose buckets".to_string(),
));
}
let abac_enabled = match status.as_str() {
"Enabled" => true,
"Disabled" => false,
other => return Err(RuntimeError::InvalidBucketAbac(other.to_string())),
};
self.require_bucket_mut(bucket)?.abac_enabled = abac_enabled;
self.audit_allowed(
principal,
S3Action::PutBucketAbac,
bucket,
Some(status.clone()),
);
Ok(BucketAbacStatus {
bucket: bucket.to_string(),
status,
})
}
pub fn create_session(
&mut self,
principal: &str,
request: CreateSessionRequest,
) -> Result<CreateSessionResult, RuntimeError> {
self.authorize(principal, S3Action::CreateSession, &request.bucket)?;
let bucket_state = self.require_bucket(&request.bucket)?.clone();
let session_mode = request
.session_mode
.unwrap_or_else(|| "ReadWrite".to_string());
let scope = create_session_scope(&request.bucket, &session_mode)?;
let encryption = create_session_encryption(
bucket_state.encryption.as_ref(),
&request.server_side_encryption,
&request.kms_key_id,
&request.encryption_context,
request.bucket_key_enabled,
)?;
self.auth.create_local_user(principal.to_string());
let request_id = self.next_request_id();
let parent_access_key_id = format!("create-session-parent:{principal}");
if self.auth.credential(&parent_access_key_id).is_none() {
self.auth
.put_access_key(AccessKey::active(
principal.to_string(),
parent_access_key_id.clone(),
format!("bucketwarden-create-session-parent-{principal}"),
))
.map_err(RuntimeError::Auth)?;
}
let access_key_id = format!(
"ASIABW{}",
request_id.trim_start_matches("bwreq").to_ascii_uppercase()
);
let secret_access_key = format!("bucketwarden-secret-{request_id}");
let session_token = format!("bucketwarden-session-{request_id}");
let expires_at_epoch_seconds = self.clock_epoch_seconds.saturating_add(900);
self.auth
.put_session(
SessionCredential::new(
principal.to_string(),
parent_access_key_id,
access_key_id.clone(),
secret_access_key.clone(),
session_token.clone(),
expires_at_epoch_seconds,
)
.with_scope(scope),
)
.map_err(RuntimeError::Auth)?;
self.audit_allowed(
principal,
S3Action::CreateSession,
&request.bucket,
Some(format!("{session_mode}:{access_key_id}")),
);
Ok(CreateSessionResult {
bucket: request.bucket,
session_mode,
credentials: bucketwarden_s3::CreateSessionCredentials {
access_key_id,
expiration: epoch_seconds_to_iso8601(expires_at_epoch_seconds),
secret_access_key,
session_token,
},
server_side_encryption: encryption.as_ref().map(|value| value.algorithm.clone()),
kms_key_id: encryption
.as_ref()
.and_then(|value| value.kms_key_id.clone()),
encryption_context: request.encryption_context,
bucket_key_enabled: None,
})
}
}
fn create_session_scope(bucket: &str, session_mode: &str) -> Result<CredentialScope, RuntimeError> {
match session_mode {
"ReadWrite" => Ok(CredentialScope::new(
vec!["s3:*".to_string(), "s3express:CreateSession".to_string()],
vec![bucket.to_string(), format!("{bucket}/")],
)),
"ReadOnly" => Ok(CredentialScope::new(
vec![
"s3:GetObject".to_string(),
"s3:GetObjectAttributes".to_string(),
"s3:ListBucket".to_string(),
"s3:ListBucketMultipartUploads".to_string(),
"s3:ListMultipartUploadParts".to_string(),
],
vec![bucket.to_string(), format!("{bucket}/")],
)),
other => Err(RuntimeError::InvalidCreateSession(format!(
"unsupported x-amz-create-session-mode: {other}"
))),
}
}
fn create_session_encryption(
bucket_default: Option<&ServerSideEncryption>,
requested_algorithm: &Option<String>,
requested_kms_key_id: &Option<String>,
requested_encryption_context: &Option<String>,
requested_bucket_key_enabled: Option<bool>,
) -> Result<Option<ServerSideEncryption>, RuntimeError> {
let Some(bucket_default) = bucket_default.cloned() else {
if requested_algorithm.is_some()
|| requested_kms_key_id.is_some()
|| requested_encryption_context.is_some()
|| requested_bucket_key_enabled.is_some()
{
return Err(RuntimeError::InvalidCreateSession(
"CreateSession encryption overrides require a bucket default encryption configuration"
.to_string(),
));
}
return Ok(None);
};
if let Some(algorithm) = requested_algorithm.as_ref() {
if algorithm != &bucket_default.algorithm {
return Err(RuntimeError::InvalidCreateSession(
"CreateSession encryption overrides must match the bucket default encryption"
.to_string(),
));
}
}
if requested_kms_key_id.is_some()
&& requested_kms_key_id.as_ref() != bucket_default.kms_key_id.as_ref()
{
return Err(RuntimeError::InvalidCreateSession(
"CreateSession KMS key overrides must match the bucket default encryption".to_string(),
));
}
if requested_bucket_key_enabled.is_some() {
return Err(RuntimeError::InvalidCreateSession(
"CreateSession does not support bucket key override headers".to_string(),
));
}
if requested_encryption_context.is_some() {
return Err(RuntimeError::InvalidCreateSession(
"CreateSession does not support overriding encryption context".to_string(),
));
}
Ok(Some(bucket_default))
}
fn epoch_seconds_to_iso8601(epoch_seconds: u64) -> String {
let days = epoch_seconds / 86_400;
let seconds_of_day = epoch_seconds % 86_400;
let hour = seconds_of_day / 3_600;
let minute = (seconds_of_day % 3_600) / 60;
let second = seconds_of_day % 60;
let z = days as i64 + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let year = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = doy - (153 * mp + 2) / 5 + 1;
let month = mp + if mp < 10 { 3 } else { -9 };
let year = year + i64::from(month <= 2);
format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z")
}