Compute instances are isolated in private networks
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 Cloudprowler openstack --checks compute_instance_isolated_private_networkRecommendation
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
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