bucketwarden-server 0.1.0

BucketWarden storage server runtime.
Documentation
use super::*;
use crate::ops_health_diagnostics::{ops_scope_and_target, scoped_buckets};

impl BucketWarden {
    pub fn ops_console_report(
        &mut self,
        principal: &str,
        bucket: Option<&str>,
    ) -> Result<ConsoleRuntimeReport, RuntimeError> {
        let (scope, target) = ops_scope_and_target(bucket);
        self.require_operator_action(
            principal,
            OperatorAction::ReadDiagnostics,
            &target,
            "ops:GetConsoleReport",
        )?;
        self.require_operator_action(
            principal,
            OperatorAction::ReadAudit,
            &target,
            "ops:GetConsoleReport",
        )?;
        self.require_ops_bucket_scope(bucket)?;

        let config = self.ops_config_report(principal, bucket)?;
        let admin_surface = self.ops_admin_surface_report(principal, bucket)?;
        let audit = self.ops_evidence_export_report(principal, bucket)?;
        let health = self.ops_health_report(principal, bucket)?;

        let bucket_rows = console_bucket_rows(self, bucket);
        let object_rows = console_object_rows(self, bucket);
        let user_rows = if bucket.is_none() {
            self.auth
                .principal_console_rows()
                .into_iter()
                .map(
                    |(principal_id, tenant_id, kind, enabled, operator_role_count)| {
                        ConsoleUserRow {
                            principal_id,
                            tenant_id,
                            kind,
                            enabled,
                            operator_role_count,
                        }
                    },
                )
                .collect()
        } else {
            Vec::new()
        };
        let policy_rows = console_policy_rows(self, bucket);
        let metrics = console_metric_summary(
            &bucket_rows,
            audit.audit_event_count,
            audit.notification_event_count,
            audit.replication_record_count,
            health.multipart_upload_count,
        );
        let retained_version_count = object_rows
            .iter()
            .filter(|object| object.retain_until_epoch_seconds.is_some())
            .count();
        let retention_bucket_count = bucket_rows
            .iter()
            .filter(|bucket| bucket.has_object_lock)
            .count();
        let lifecycle_bucket_count = bucket_rows
            .iter()
            .filter(|bucket| bucket.has_lifecycle)
            .count();
        let lifecycle_rule_count = scoped_buckets(self, bucket)
            .iter()
            .map(|(_, bucket_state)| bucket_state.lifecycle_rules.len())
            .sum();
        let support = console_support_features();
        let product_caveats = console_product_caveats(&config, &admin_surface);

        let report = ConsoleRuntimeReport {
            scope: scope.to_string(),
            target: target.clone(),
            generated_at_epoch_seconds: self.clock_epoch_seconds,
            buckets: bucket_rows,
            objects: object_rows,
            users: user_rows,
            policies: policy_rows,
            metrics,
            audit,
            retention_bucket_count,
            retained_version_count,
            lifecycle_bucket_count,
            lifecycle_rule_count,
            native_support: support.clone(),
            semantic_parity: support,
            config,
            admin_surface,
            security_governance_findings: console_security_governance_findings(self, bucket),
            observability_evidence: vec![
                "ops:GetHealthReport".to_string(),
                "ops:GetConfigReport".to_string(),
                "ops:GetAdminSurfaceReport".to_string(),
                "ops:GetEvidenceExportReport".to_string(),
                "ops:GetConsoleReport".to_string(),
            ],
            failure_modes: health.issues,
            validation_tests: vec![
                "crates/bucketwarden-server/tests/ops_console.rs".to_string(),
                "tests/ssot/verify_future_console_ui_boundary.py".to_string(),
            ],
            product_caveats,
        };
        self.audit.append(
            principal,
            "ops:GetConsoleReport",
            &target,
            AuditOutcome::Allowed,
            Some(format!(
                "buckets={},objects={},users={},policies={}",
                report.buckets.len(),
                report.objects.len(),
                report.users.len(),
                report.policies.len()
            )),
        );
        Ok(report)
    }
}

fn console_bucket_rows(runtime: &BucketWarden, bucket: Option<&str>) -> Vec<ConsoleBucketRow> {
    scoped_buckets(runtime, bucket)
        .into_iter()
        .map(|(bucket_name, bucket_state)| {
            let mut object_count = 0usize;
            let mut version_count = 0usize;
            let mut delete_marker_count = 0usize;
            let mut total_bytes = 0usize;
            for object in bucket_state.objects.values() {
                if object.has_current_version() {
                    object_count += 1;
                }
                for version in &object.versions {
                    version_count += 1;
                    if version.delete_marker {
                        delete_marker_count += 1;
                    }
                    total_bytes += version.content_length();
                }
            }
            ConsoleBucketRow {
                name: bucket_name.clone(),
                tenant_id: bucket_state.tenant_id.clone(),
                owner: bucket_state.owner.clone(),
                region: bucket_state.region.clone(),
                versioning_status: format!("{:?}", bucket_state.versioning),
                object_count,
                version_count,
                delete_marker_count,
                total_bytes,
                request_count: bucket_state.request_count,
                has_policy: bucket_state.policy.is_some(),
                has_lifecycle: !bucket_state.lifecycle_rules.is_empty(),
                has_object_lock: bucket_state.object_lock.enabled,
                has_encryption: bucket_state.encryption.is_some(),
                has_replication: !bucket_state.replication.rules.is_empty(),
                has_metrics: !bucket_state.metrics_configurations.is_empty(),
            }
        })
        .collect()
}

fn console_object_rows(runtime: &BucketWarden, bucket: Option<&str>) -> Vec<ConsoleObjectRow> {
    let mut rows = Vec::new();
    for (bucket_name, bucket_state) in scoped_buckets(runtime, bucket) {
        for (key, object) in &bucket_state.objects {
            let current = object.current_version();
            rows.push(ConsoleObjectRow {
                bucket: bucket_name.clone(),
                key: key.clone(),
                current_version_id: current.map(|version| version.version_id.clone()),
                version_count: object.versions.len(),
                delete_marker_count: object
                    .versions
                    .iter()
                    .filter(|version| version.delete_marker)
                    .count(),
                total_bytes: object
                    .versions
                    .iter()
                    .map(StoredVersion::content_length)
                    .sum(),
                latest_modified_epoch_seconds: object
                    .versions
                    .iter()
                    .map(|version| version.last_modified_epoch_seconds)
                    .max(),
                owner: current
                    .map(|version| stored_owner(bucket_state, version))
                    .unwrap_or_else(|| bucket_state.owner.clone()),
                legal_hold: current
                    .map(|version| version.lock.legal_hold)
                    .unwrap_or_default(),
                retention_mode: current.and_then(|version| {
                    version.lock.retention_mode.map(|mode| format!("{mode:?}"))
                }),
                retain_until_epoch_seconds: current
                    .and_then(|version| version.lock.retain_until_epoch_seconds),
                encryption: current.and_then(|version| version.metadata.encryption.clone()),
                replication_status: current.and_then(|version| version.replication_status.clone()),
            });
        }
    }
    rows.sort_by(|left, right| {
        left.bucket
            .cmp(&right.bucket)
            .then(left.key.cmp(&right.key))
    });
    rows
}

fn console_policy_rows(runtime: &BucketWarden, bucket: Option<&str>) -> Vec<ConsolePolicyRow> {
    scoped_buckets(runtime, bucket)
        .into_iter()
        .filter_map(|(bucket_name, bucket_state)| {
            bucket_state.policy.as_ref().map(|policy| ConsolePolicyRow {
                scope: "bucket".to_string(),
                target: bucket_name.clone(),
                statement_count: bucket_policy_statement_count(&policy.json),
                byte_length: policy.json.len(),
            })
        })
        .collect()
}

fn bucket_policy_statement_count(policy_json: &str) -> usize {
    let Ok(value) = serde_json::from_str::<serde_json::Value>(policy_json) else {
        return 0;
    };
    match value.get("Statement") {
        Some(serde_json::Value::Array(statements)) => statements.len(),
        Some(_) => 1,
        None => 0,
    }
}

fn console_metric_summary(
    buckets: &[ConsoleBucketRow],
    audit_event_count: usize,
    notification_event_count: usize,
    replication_record_count: usize,
    multipart_upload_count: usize,
) -> ConsoleMetricSummary {
    ConsoleMetricSummary {
        bucket_count: buckets.len(),
        object_count: buckets.iter().map(|bucket| bucket.object_count).sum(),
        version_count: buckets.iter().map(|bucket| bucket.version_count).sum(),
        delete_marker_count: buckets
            .iter()
            .map(|bucket| bucket.delete_marker_count)
            .sum(),
        total_bytes: buckets.iter().map(|bucket| bucket.total_bytes).sum(),
        request_count: buckets.iter().map(|bucket| bucket.request_count).sum(),
        audit_event_count,
        notification_event_count,
        replication_record_count,
        multipart_upload_count,
    }
}

fn console_security_governance_findings(
    runtime: &BucketWarden,
    bucket: Option<&str>,
) -> Vec<String> {
    let mut findings = Vec::new();
    for (bucket_name, bucket_state) in scoped_buckets(runtime, bucket) {
        if bucket_state.policy.is_none() {
            findings.push(format!("bucket:{bucket_name}:policy:not-configured"));
        }
        if bucket_state.encryption.is_none() {
            findings.push(format!("bucket:{bucket_name}:encryption:not-configured"));
        }
        if !bucket_state.object_lock.enabled {
            findings.push(format!("bucket:{bucket_name}:object-lock:not-enabled"));
        }
        if bucket_state.public_access_block.is_none() {
            findings.push(format!(
                "bucket:{bucket_name}:public-access-block:not-configured"
            ));
        }
    }
    findings.sort();
    findings
}

fn console_support_features() -> Vec<ConsoleSupportFeature> {
    [
        ("001", "Buckets", "console.bucket.index"),
        ("002", "Objects", "console.object.index"),
        ("003", "Users", "console.identity.index"),
        ("004", "Policies", "console.policy.index"),
        ("005", "Metrics", "console.metrics.summary"),
        ("006", "Audit", "console.audit.export"),
        ("007", "Retention", "console.retention.summary"),
        ("008", "Lifecycle", "console.lifecycle.summary"),
        (
            "009",
            "Native support state",
            "console.native-support.summary",
        ),
        ("010", "Semantic parity", "console.semantic-parity.summary"),
        (
            "011",
            "Configuration and admin surface",
            "console.config-admin.summary",
        ),
        (
            "012",
            "Security and governance impact",
            "console.security-governance.findings",
        ),
        (
            "013",
            "Observability and evidence",
            "console.observability-evidence.summary",
        ),
        (
            "014",
            "Failure-mode behavior",
            "console.failure-modes.summary",
        ),
        (
            "015",
            "Validation test coverage",
            "console.validation-tests.summary",
        ),
        (
            "016",
            "Product-specific caveats",
            "console.product-caveats.summary",
        ),
    ]
    .into_iter()
    .map(|(number, title, runtime_surface)| ConsoleSupportFeature {
        feature_id: format!("feat:bucketwarden.req.ops-j08-{number}"),
        title: format!("OPS-J08-{number} - {title}"),
        runtime_surface: runtime_surface.to_string(),
        native_support_state: "implemented".to_string(),
        semantic_parity_state: "runtime-console-api".to_string(),
    })
    .collect()
}

fn console_product_caveats(
    config: &RuntimeConfigReport,
    admin_surface: &OpsAdminSurfaceReport,
) -> Vec<String> {
    let mut caveats = Vec::new();
    caveats.extend(config.storage_backend_caveats.iter().cloned());
    caveats.extend(config.replication_strategy_caveats.iter().cloned());
    caveats.extend(config.erasure_coding_caveats.iter().cloned());
    caveats.extend(config.placement_domain_caveats.iter().cloned());
    caveats.extend(config.consistency_model_caveats.iter().cloned());
    caveats.extend(config.metadata_architecture_caveats.iter().cloned());
    caveats.extend(config.object_layout_caveats.iter().cloned());
    caveats.extend(config.small_object_optimization_caveats.iter().cloned());
    caveats.extend(config.large_object_optimization_caveats.iter().cloned());
    caveats.extend(admin_surface.storage_backend_caveats.iter().cloned());
    caveats.sort();
    caveats.dedup();
    caveats
}