Back to Checks
openstack

Security groups do not allow SSH from the Internet

networking_security_group_allows_ssh_from_internet
Service: networkingSeverity: high
OfficialBuilt-inVerified
Categories:internet-exposedtrust-boundaries
Resource Type:OS::Neutron::SecurityGroup

OpenStack security groups are evaluated to verify that SSH (port 22) is not exposed to the Internet (0.0.0.0/0 or ::/0). Security groups act as virtual firewalls controlling instance traffic. Unrestricted SSH access violates least privilege and creates significant attack surface. Best practices recommend restricting SSH to known IP ranges, bastion hosts, or VPN gateways.

Risk

Unrestricted SSH exposes instances to brute-force attacks, password testing, and SSH vulnerability exploitation (CVE-2023-38408, CVE-2021-41617) for initial access. Compromised instances enable persistence, privilege escalation, lateral movement, crypto mining, DDoS botnets, data exfiltration, and ransomware deployment. SSH exposure bypasses defense-in-depth and enables direct production access.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler openstack --checks networking_security_group_allows_ssh_from_internet

Recommendation

Restrict SSH access to known IP ranges using bastion hosts or VPN gateways instead of allowing 0.0.0.0/0. Implement multi-factor authentication, disable password authentication, and use SSH certificates for centralized key management. Monitor SSH logs for failed login attempts and enforce fail2ban or similar IP blocking mechanisms.

Remediation

Terraform

# Terraform: restrict SSH to known IP ranges, never 0.0.0.0/0

resource "openstack_networking_secgroup_v2" "app_servers" {
  name        = "app-servers-sg"
  description = "Security group for application servers"
}

# GOOD: SSH restricted to corporate office IP
resource "openstack_networking_secgroup_rule_v2" "ssh_from_office" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = "203.0.113.0/24"  # Corporate office network
  security_group_id = openstack_networking_secgroup_v2.app_servers.id
}

# GOOD: SSH from bastion security group (reference-based rule)
resource "openstack_networking_secgroup_rule_v2" "ssh_from_bastion" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_group_id   = openstack_networking_secgroup_v2.bastion.id  # Reference, not CIDR
  security_group_id = openstack_networking_secgroup_v2.app_servers.id
}

# Bastion security group (least privilege)
resource "openstack_networking_secgroup_v2" "bastion" {
  name        = "bastion-sg"
  description = "Security group for bastion host - only entry point for SSH"
}

resource "openstack_networking_secgroup_rule_v2" "bastion_ssh" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = var.corporate_cidr  # e.g., 203.0.113.0/24
  security_group_id = openstack_networking_secgroup_v2.bastion.id
}

# BAD: Never do this - SSH from anywhere (0.0.0.0/0)
# resource "openstack_networking_secgroup_rule_v2" "ssh_from_anywhere" {
#   direction         = "ingress"
#   ethertype         = "IPv4"
#   protocol          = "tcp"
#   port_range_min    = 22
#   port_range_max    = 22
#   remote_ip_prefix  = "0.0.0.0/0"  # DANGEROUS - DO NOT USE
#   security_group_id = openstack_networking_secgroup_v2.app_servers.id
# }

Other

1Navigate to Network > Security Groups
2Locate the security group with unrestricted SSH access
3Click Manage Rules
4Delete SSH rules with Remote 0.0.0.0/0 or ::/0
5Click Add Rule and create a new SSH rule
6Set Remote to CIDR with your trusted IP (e.g., 203.0.113.50/32)
7Save changes and verify connectivity

Check Code

from typing import List

from prowler.lib.check.models import Check, CheckReportOpenStack
from prowler.providers.openstack.lib.security_groups import check_security_group_rule
from prowler.providers.openstack.services.networking.networking_client import (
    networking_client,
)


class networking_security_group_allows_ssh_from_internet(Check):
    """Ensure security groups do not allow SSH from the Internet."""

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

        for sg in networking_client.security_groups:
            report = CheckReportOpenStack(metadata=self.metadata(), resource=sg)
            report.resource_id = sg.id
            report.resource_name = sg.name
            report.region = sg.region

            # Check if any rule allows SSH from 0.0.0.0/0 or ::/0
            ssh_exposed = False
            exposed_rules = []

            for rule in sg.security_group_rules:
                if check_security_group_rule(
                    rule=rule,
                    protocol="tcp",
                    ports=[22],
                    any_address=True,
                    direction="ingress",
                ):
                    ssh_exposed = True
                    cidr = rule.remote_ip_prefix or "0.0.0.0/0"
                    exposed_rules.append(
                        f"rule {rule.id} ({rule.protocol}/{cidr}:{rule.port_range_min}-{rule.port_range_max})"
                    )

            if ssh_exposed:
                report.status = "FAIL"
                rules_str = ", ".join(exposed_rules)
                report.status_extended = f"Security group {sg.name} ({sg.id}) allows unrestricted SSH access (port 22) from the Internet via {rules_str}."
            else:
                report.status = "PASS"
                report.status_extended = f"Security group {sg.name} ({sg.id}) does not allow SSH (port 22) from the Internet."

            findings.append(report)

        return findings