use super::*;
pub(super) fn validate_bucket_metadata_configuration(
configuration: &BucketMetadataConfiguration,
) -> Result<(), RuntimeError> {
validate_metadata_table_encryption(
configuration
.journal_table_configuration
.encryption_configuration
.as_ref(),
)?;
validate_record_expiration(&configuration.journal_table_configuration.record_expiration)?;
if let Some(inventory) = configuration.inventory_table_configuration.as_ref() {
validate_metadata_table_encryption(inventory.encryption_configuration.as_ref())?;
match inventory.configuration_state.as_str() {
"ENABLED" | "DISABLED" => {}
other => {
return Err(RuntimeError::InvalidBucketMetadataConfiguration(format!(
"invalid inventory configuration state: {other}"
)))
}
}
}
Ok(())
}
pub(super) fn validate_metadata_table_encryption(
encryption: Option<&MetadataTableEncryptionConfiguration>,
) -> Result<(), RuntimeError> {
let Some(encryption) = encryption else {
return Ok(());
};
match encryption.sse_algorithm.as_str() {
"AES256" => Ok(()),
"aws:kms"
if encryption
.kms_key_arn
.as_deref()
.is_some_and(|value| !value.trim().is_empty()) =>
{
Ok(())
}
"aws:kms" => Err(RuntimeError::InvalidBucketMetadataConfiguration(
"KmsKeyArn is required when SseAlgorithm is aws:kms".to_string(),
)),
other => Err(RuntimeError::InvalidBucketMetadataConfiguration(format!(
"invalid metadata table encryption algorithm: {other}"
))),
}
}
pub(super) fn validate_bucket_metadata_table_configuration(
configuration: &BucketMetadataTableConfiguration,
) -> Result<(), RuntimeError> {
if configuration
.s3_tables_destination
.table_bucket_arn
.trim()
.is_empty()
{
return Err(RuntimeError::InvalidBucketMetadataTableConfiguration(
"S3TablesDestination TableBucketArn must not be empty".to_string(),
));
}
if !configuration
.s3_tables_destination
.table_bucket_arn
.starts_with("arn:aws:s3tables:")
{
return Err(RuntimeError::InvalidBucketMetadataTableConfiguration(
"S3TablesDestination TableBucketArn must be an s3tables ARN".to_string(),
));
}
if configuration
.s3_tables_destination
.table_name
.trim()
.is_empty()
{
return Err(RuntimeError::InvalidBucketMetadataTableConfiguration(
"S3TablesDestination TableName must not be empty".to_string(),
));
}
Ok(())
}
pub(super) fn validate_record_expiration(
expiration: &RecordExpiration,
) -> Result<(), RuntimeError> {
match expiration.expiration.as_str() {
"ENABLED" => {
let days = expiration.days.ok_or_else(|| {
RuntimeError::InvalidBucketMetadataConfiguration(
"journal record expiration requires Days when Expiration is ENABLED"
.to_string(),
)
})?;
if !(7..=2_147_483_647).contains(&days) {
return Err(RuntimeError::InvalidBucketMetadataConfiguration(format!(
"journal record expiration days must be between 7 and 2147483647: {days}"
)));
}
Ok(())
}
"DISABLED" => Ok(()),
other => Err(RuntimeError::InvalidBucketMetadataConfiguration(format!(
"invalid journal record expiration state: {other}"
))),
}
}
pub(super) fn synthesize_metadata_configuration_result(
configuration: &BucketMetadataConfiguration,
) -> MetadataConfigurationResult {
let destination_result = DestinationResult {
table_bucket_arn: "arn:aws:s3tables:us-east-1:000000000000:bucket/bucketwarden-managed"
.to_string(),
table_bucket_type: "aws".to_string(),
table_namespace: METADATA_TABLE_NAMESPACE.to_string(),
};
let journal_table_name = format!(
"{}_journal",
sanitize_metadata_table_component(&configuration.bucket)
);
let journal_table_configuration_result = JournalTableConfigurationResult {
error: None,
record_expiration: configuration
.journal_table_configuration
.record_expiration
.clone(),
table_arn: format!(
"{}/table/{}/{}",
destination_result.table_bucket_arn,
destination_result.table_namespace,
journal_table_name
),
table_name: journal_table_name,
table_status: "ACTIVE".to_string(),
};
let inventory_table_configuration_result = configuration
.inventory_table_configuration
.as_ref()
.map(|inventory| {
let table_name = format!(
"{}_inventory",
sanitize_metadata_table_component(&configuration.bucket)
);
InventoryTableConfigurationResult {
configuration_state: inventory.configuration_state.clone(),
error: None,
table_arn: format!(
"{}/table/{}/{}",
destination_result.table_bucket_arn,
destination_result.table_namespace,
table_name
),
table_name,
table_status: if inventory.configuration_state == "ENABLED" {
"ACTIVE".to_string()
} else {
"CREATING".to_string()
},
}
});
MetadataConfigurationResult {
destination_result,
inventory_table_configuration_result,
journal_table_configuration_result,
}
}
pub(super) fn synthesize_v2_result_from_v1(
configuration: &BucketMetadataTableConfiguration,
) -> MetadataConfigurationResult {
let destination_result = DestinationResult {
table_bucket_arn: configuration.s3_tables_destination.table_bucket_arn.clone(),
table_bucket_type: "customer".to_string(),
table_namespace: METADATA_TABLE_NAMESPACE.to_string(),
};
MetadataConfigurationResult {
destination_result: destination_result.clone(),
inventory_table_configuration_result: None,
journal_table_configuration_result: JournalTableConfigurationResult {
error: None,
record_expiration: RecordExpiration {
expiration: "DISABLED".to_string(),
days: None,
},
table_arn: format!(
"{}/table/{}/{}",
destination_result.table_bucket_arn,
destination_result.table_namespace,
configuration.s3_tables_destination.table_name
),
table_name: configuration.s3_tables_destination.table_name.clone(),
table_status: "ACTIVE".to_string(),
},
}
}
pub(super) fn synthesize_metadata_table_configuration_result(
configuration: &BucketMetadataTableConfiguration,
) -> GetBucketMetadataTableConfigurationResult {
let destination_result = S3TablesDestinationResult {
table_arn: format!(
"{}/table/{}/{}",
configuration.s3_tables_destination.table_bucket_arn,
METADATA_TABLE_NAMESPACE,
configuration.s3_tables_destination.table_name
),
table_bucket_arn: configuration.s3_tables_destination.table_bucket_arn.clone(),
table_name: configuration.s3_tables_destination.table_name.clone(),
table_namespace: METADATA_TABLE_NAMESPACE.to_string(),
};
GetBucketMetadataTableConfigurationResult {
metadata_table_configuration_result: MetadataTableConfigurationResult {
s3_tables_destination_result: destination_result,
},
status: "ACTIVE".to_string(),
error: None,
}
}
pub(super) fn metadata_configuration_audit_detail(
configuration: &BucketMetadataConfiguration,
) -> String {
format!(
"journal={};inventory={}",
configuration
.journal_table_configuration
.record_expiration
.expiration,
configuration
.inventory_table_configuration
.as_ref()
.map(|inventory| inventory.configuration_state.as_str())
.unwrap_or("DISABLED")
)
}
pub(super) fn metadata_table_configuration_audit_detail(
configuration: &BucketMetadataTableConfiguration,
) -> String {
format!(
"table_bucket_arn={};table_name={}",
configuration.s3_tables_destination.table_bucket_arn,
configuration.s3_tables_destination.table_name
)
}
fn sanitize_metadata_table_component(value: &str) -> String {
value
.chars()
.map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' })
.collect()
}