Back to Checks
openstack

Compute instance metadata does not contain sensitive data

compute_instance_metadata_sensitive_data
Service: computeSeverity: critical
OfficialBuilt-inVerified
Categories:secretsencryption
Resource Type:OS::Nova::Server

OpenStack compute instance metadata is evaluated to detect sensitive data such as passwords, API keys, secrets, and private keys. Instance metadata is accessible via the metadata service (169.254.169.254) to any process inside the instance. Storing secrets in metadata exposes them to SSRF attacks, compromised applications, and unauthorized access.

Risk

Instance metadata containing sensitive data exposes credentials through the metadata service (169.254.169.254), accessible to any process inside the instance. Attackers exploiting SSRF, compromised applications, or insider threats can extract passwords, API keys, and private keys. Stolen credentials enable unauthorized modifications, privilege escalation, resource deletion, and cryptomining.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler openstack --checks compute_instance_metadata_sensitive_data

Recommendation

Never store secrets in metadata; use Barbican (OpenStack Key Manager), Vault, or external secrets management instead. Retrieve secrets at runtime via APIs. Implement least privilege access to secrets with RBAC. Enable secrets audit logging. Use envelope encryption for secrets at rest. Implement automatic rotation every 90 days. Scan metadata for hardcoded secrets using tools like TruffleHog.

Remediation

CLI

openstack server unset --property <sensitive_key> <instance_id>

Terraform

# Terraform: use Barbican for secrets instead of metadata

# Store secret in Barbican
resource "openstack_keymanager_secret_v1" "db_password" {
  name                 = "database-password"
  payload              = random_password.db_password.result
  payload_content_type = "text/plain"
  secret_type          = "passphrase"
}

resource "random_password" "db_password" {
  length  = 32
  special = true
}

# Instance WITHOUT sensitive data in metadata
resource "openstack_compute_instance_v2" "secure_instance" {
  name            = "app-server"
  image_id        = var.image_id
  flavor_id       = var.flavor_id
  key_pair        = var.key_pair_name
  security_groups = ["app-sg"]

  # Safe metadata (non-sensitive labels only)
  metadata = {
    environment       = "production"
    application       = "web-app"
    cost_center       = "engineering"
    barbican_secret_id = openstack_keymanager_secret_v1.db_password.secret_ref
  }

  network {
    name = "private-network"
  }

  # Use cloud-init to retrieve secrets from Barbican
  user_data = <<-EOF
    #!/bin/bash
    # Retrieve database password from Barbican
    SECRET_REF="${openstack_keymanager_secret_v1.db_password.secret_ref}"
    DB_PASSWORD=$(openstack secret get "$SECRET_REF" --payload -f value)
    
    # Configure application with retrieved secret
    echo "DATABASE_URL=<scheme>://user:$DB_PASSWORD@db-host/dbname" > /etc/app/config.env
    chmod 600 /etc/app/config.env
  EOF
}

# ANTI-PATTERN: DO NOT DO THIS
# resource "openstack_compute_instance_v2" "insecure_instance" {
#   metadata = {
#     db_password = "hardcoded-secret-123"  # NEVER store secrets in metadata
#     api_key     = "sk-1234567890abcdef"   # Exposed via metadata service
#   }
# }

Other

1Navigate to **Compute > Instances**
2Select instance 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.compute.compute_client import compute_client


class compute_instance_metadata_sensitive_data(Check):
    """Ensure compute instance metadata does not contain sensitive data like passwords or API keys."""

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

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

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

            findings.append(report)

        return findings