bucketwarden-server 0.1.0

BucketWarden storage server runtime.
Documentation
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()
}