Back to Checks
openstack

Compute instances are isolated in private networks

compute_instance_isolated_private_network
Service: computeSeverity: medium
OfficialBuilt-inVerified
Categories:trust-boundaries
Resource Type:OS::Nova::Server

OpenStack compute instances (VMs) are evaluated to verify network isolation by ensuring they have private IP addresses without mixed public/private exposure. Proper network segmentation requires instances to be deployed in private networks and accessed via controlled entry points (bastion hosts, VPN, load balancers) rather than direct public exposure.

Risk

Instances with mixed public/private exposure or only public IPs lack network isolation, allowing unauthorized internet access that bypasses segmentation controls. Attackers can pivot from compromised public instances to internal infrastructure for lateral movement. Flat topology exposes internal services to internet attacks including DDoS and exploit attempts.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler openstack --checks compute_instance_isolated_private_network

Recommendation

Deploy instances in private networks (RFC1918) with tiered architecture: web tier with public IPs, app/database tiers private only. Never mix public and private IPs on same instance. Use bastion hosts or VPN for operator access, security groups for least-privilege policies, and NAT gateways for outbound access. Enable flow logs to detect lateral movement attempts.

Remediation

CLI

openstack server remove floating ip <instance_id> <floating_ip>

Terraform

# Terraform: deploy instance in isolated private network

# Private network (no external gateway)
resource "openstack_networking_network_v2" "private" {
  name           = "private-network"
  admin_state_up = true
}

resource "openstack_networking_subnet_v2" "private_subnet" {
  name       = "private-subnet"
  network_id = openstack_networking_network_v2.private.id
  cidr       = "10.0.1.0/24"
  ip_version = 4
  # No gateway_ip means no external routing
}

# Instance in private network (no public IP)
resource "openstack_compute_instance_v2" "app_server" {
  name            = "app-server"
  image_id        = var.image_id
  flavor_id       = var.flavor_id
  key_pair        = var.key_pair_name
  security_groups = ["app-tier-sg"]

  # Attach to private network ONLY
  network {
    uuid = openstack_networking_network_v2.private.id
  }

  # Do NOT associate floating IP
}

# Security group for app tier (least privilege)
resource "openstack_networking_secgroup_v2" "app_tier_sg" {
  name        = "app-tier-sg"
  description = "App tier security group - private network only"
}

# Allow SSH from bastion host only (not internet)
resource "openstack_networking_secgroup_rule_v2" "app_ssh_from_bastion" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = var.bastion_private_ip  # e.g., 10.0.0.5/32
  security_group_id = openstack_networking_secgroup_v2.app_tier_sg.id
}

# Allow app traffic from web tier only
resource "openstack_networking_secgroup_rule_v2" "app_backend" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 8080
  port_range_max    = 8080
  remote_ip_prefix  = var.web_tier_cidr  # e.g., 10.0.2.0/24
  security_group_id = openstack_networking_secgroup_v2.app_tier_sg.id
}

Other

1Navigate to **Compute > Instances**
2Select instance with public IP
3Click **Actions > Disassociate Floating IP**
4Confirm disassociation
5Access instance via bastion host or VPN instead

Check Code

from typing import List

from prowler.lib.check.models import Check, CheckReportOpenStack
from prowler.providers.openstack.services.compute.compute_client import compute_client
from prowler.providers.openstack.services.compute.lib.ip import is_public_ip


class compute_instance_isolated_private_network(Check):
    """Ensure compute instances are isolated in private networks without mixed public/private exposure."""

    def execute(self) -> List[CheckReportOpenStack]:
        findings: List[CheckReportOpenStack] = []

        for instance in compute_client.instances:
            report = CheckReportOpenStack(metadata=self.metadata(), resource=instance)

            private_ip_list = []
            public_ip_list = []

            # Classify IPs from networks dict using actual IP validation
            for ip_list in instance.networks.values():
                for ip in ip_list:
                    if is_public_ip(ip):
                        public_ip_list.append(ip)
                    else:
                        private_ip_list.append(ip)

            # Also check SDK fields for IPs not present in networks
            seen_ips = set(private_ip_list + public_ip_list)
            for ip in [
                instance.public_v4,
                instance.public_v6,
                instance.access_ipv4,
                instance.access_ipv6,
            ]:
                if ip and ip not in seen_ips and is_public_ip(ip):
                    public_ip_list.append(ip)
            for ip in [instance.private_v4, instance.private_v6]:
                if ip and ip not in seen_ips and not is_public_ip(ip):
                    private_ip_list.append(ip)

            has_private_ips = bool(private_ip_list)
            has_public_ips = bool(public_ip_list)

            # Determine status based on IP classification
            if has_private_ips and not has_public_ips:
                report.status = "PASS"
                ip_display = ", ".join(private_ip_list)
                report.status_extended = f"Instance {instance.name} ({instance.id}) is properly isolated in private network with private IPs ({ip_display}) and no public exposure."
            elif has_public_ips and has_private_ips:
                report.status = "FAIL"
                report.status_extended = f"Instance {instance.name} ({instance.id}) has mixed public and private network exposure (not properly isolated)."
            elif has_public_ips and not has_private_ips:
                report.status = "FAIL"
                report.status_extended = f"Instance {instance.name} ({instance.id}) has only public IP addresses (no private network isolation)."
            else:
                # No IPs at all (edge case)
                report.status = "FAIL"
                report.status_extended = f"Instance {instance.name} ({instance.id}) has no network configuration (no IPs assigned)."

            findings.append(report)

        return findings