Back to Checks
openstack

Block storage volume metadata does not contain sensitive data

blockstorage_volume_metadata_sensitive_data
Service: blockstorageSeverity: critical
OfficialBuilt-inVerified
Categories:secretsencryption
Resource Type:OS::Cinder::Volume

OpenStack block storage volume metadata is evaluated to detect sensitive data such as passwords, API keys, secrets, and private keys. Volume 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

Volume metadata containing sensitive data exposes credentials through the OpenStack API, accessible to any project member. Attackers with project access can enumerate volume 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_volume_metadata_sensitive_data

Recommendation

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

Remediation

CLI

openstack volume unset --property <sensitive_key> <volume_id>

Terraform

# Use Barbican for secrets instead of volume metadata
resource "openstack_blockstorage_volume_v3" "secure_volume" {
  name = "app-data"
  size = 10

  # Safe metadata (non-sensitive labels only)
  metadata = {
    environment = "production"
    application = "web-app"
  }
}

Other

1Navigate to **Block Storage > Volumes**
2Select volume 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_volume_metadata_sensitive_data(Check):
    """Ensure block storage volume 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)
        volumes = list(blockstorage_client.volumes)

        # Collect one payload per volume (its metadata) and scan them all in
        # batched Kingfisher invocations instead of one subprocess per volume.
        def payloads():
            for index, volume in enumerate(volumes):
                if volume.metadata:
                    yield index, json.dumps(dict(volume.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, volume in enumerate(volumes):
            report = CheckReportOpenStack(metadata=self.metadata(), resource=volume)
            report.status = "PASS"
            report.status_extended = f"Volume {volume.name} ({volume.id}) metadata does not contain sensitive data."

            if volume.metadata:
                if scan_error:
                    report.status = "MANUAL"
                    report.status_extended = (
                        f"Could not scan volume {volume.name} ({volume.id}) metadata "
                        f"for secrets: {scan_error}; manual review is required."
                    )
                    findings.append(report)
                    continue
                original_metadata_keys = list(volume.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"Volume {volume.name} ({volume.id}) metadata contains potential secrets -> {secrets_string}."
                    annotate_verified_secrets(report, detect_secrets_output)
            else:
                report.status_extended = f"Volume {volume.name} ({volume.id}) has no metadata (no sensitive data exposure risk)."

            findings.append(report)

        return findings