Block storage snapshot metadata does not contain sensitive data
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 Cloudprowler openstack --checks blockstorage_snapshot_metadata_sensitive_dataRecommendation
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
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