Back to Checks
openstack

Block storage snapshot metadata does not contain sensitive data

blockstorage_snapshot_metadata_sensitive_data
Service: blockstorageSeverity: critical
OfficialBuilt-inVerified
Categories:secretsencryption
Resource Type:OS::Cinder::Snapshot

OpenStack block storage snapshot metadata is evaluated to detect sensitive data such as passwords, API keys, secrets, and private keys. Snapshot metadata is accessible via the OpenStack API to any user with access to the project. Storing secrets in metadata exposes them to unauthorized access through the API.

Risk

Snapshot metadata containing sensitive data exposes credentials through the OpenStack API, accessible to any project member. Attackers with project access can enumerate snapshot metadata to extract passwords, API keys, and private keys. Stolen credentials enable unauthorized modifications, privilege escalation, and data breaches.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler openstack --checks blockstorage_snapshot_metadata_sensitive_data

Recommendation

Never store secrets in snapshot metadata; use Barbican (OpenStack Key Manager), Vault, or external secrets management instead. Remove any sensitive data currently stored in snapshot metadata and rotate exposed credentials immediately. Implement metadata policies to prevent sensitive data from being added.

Remediation

CLI

openstack volume snapshot unset --property <sensitive_key> <snapshot_id>

Other

1Navigate to **Block Storage > Snapshots**
2Select snapshot with sensitive metadata
3Remove sensitive metadata keys using CLI command
4Rotate exposed credentials immediately
5Store secrets in Barbican or external secrets manager instead

Check Code

import json
from typing import List

from prowler.lib.check.models import Check, CheckReportOpenStack
from prowler.lib.utils.utils import (
    SecretsScanError,
    annotate_verified_secrets,
    detect_secrets_scan_batch,
)
from prowler.providers.openstack.services.blockstorage.blockstorage_client import (
    blockstorage_client,
)


class blockstorage_snapshot_metadata_sensitive_data(Check):
    """Ensure block storage snapshot metadata does not contain sensitive data like passwords or API keys."""

    def execute(self) -> List[CheckReportOpenStack]:
        findings: List[CheckReportOpenStack] = []
        secrets_ignore_patterns = blockstorage_client.audit_config.get(
            "secrets_ignore_patterns", []
        )
        validate = blockstorage_client.audit_config.get("secrets_validate", False)
        snapshots = list(blockstorage_client.snapshots)

        # Collect one payload per snapshot (its metadata) and scan them all in
        # batched Kingfisher invocations instead of one subprocess per snapshot.
        def payloads():
            for index, snapshot in enumerate(snapshots):
                if snapshot.metadata:
                    yield index, json.dumps(dict(snapshot.metadata), indent=2)

        scan_error = None
        try:
            batch_results = detect_secrets_scan_batch(
                payloads(), excluded_secrets=secrets_ignore_patterns, validate=validate
            )
        except SecretsScanError as error:
            batch_results = {}
            scan_error = error

        for index, snapshot in enumerate(snapshots):
            report = CheckReportOpenStack(metadata=self.metadata(), resource=snapshot)
            report.status = "PASS"
            report.status_extended = f"Snapshot {snapshot.name} ({snapshot.id}) metadata does not contain sensitive data."

            if snapshot.metadata:
                if scan_error:
                    report.status = "MANUAL"
                    report.status_extended = (
                        f"Could not scan snapshot {snapshot.name} ({snapshot.id}) "
                        f"metadata for secrets: {scan_error}; manual review is "
                        "required."
                    )
                    findings.append(report)
                    continue
                original_metadata_keys = list(snapshot.metadata.keys())
                detect_secrets_output = batch_results.get(index)
                if detect_secrets_output:
                    # Map line numbers back to metadata keys using the parallel list
                    # Line numbering: line 1 = "{", line 2 = first key-value, etc.
                    secrets_string = ", ".join(
                        [
                            f"{secret['type']} in metadata key '{original_metadata_keys[secret['line_number'] - 2]}'"
                            for secret in detect_secrets_output
                            if 0
                            <= secret["line_number"] - 2
                            < len(original_metadata_keys)
                        ]
                    )
                    report.status = "FAIL"
                    report.status_extended = f"Snapshot {snapshot.name} ({snapshot.id}) metadata contains potential secrets -> {secrets_string}."
                    annotate_verified_secrets(report, detect_secrets_output)
            else:
                report.status_extended = f"Snapshot {snapshot.name} ({snapshot.id}) has no metadata (no sensitive data exposure risk)."

            findings.append(report)

        return findings