tenable / terrascan

Detect compliance and security violations across Infrastructure as Code to mitigate risk before provisioning cloud native infrastructure.
https://runterrascan.io
Apache License 2.0
4.77k stars 501 forks source link

AC_AZURE_0285 policy does not work with inline rules #1453

Open jakub-i opened 2 years ago

jakub-i commented 2 years ago

Description

Azure Policiy AC_AZURE_0285 for Network security rule allowing communication to Tcp port 22 is not caught by terrascan if the network rule is defined inline as security_rule property of the azurerm_network_security_group

I have not checked any other similar policies, but I imagine this issue is also relevant for those.

I see this as a bug, but it may also be a missing policy for azurerm_network_security_group resource, but the end result is that using inline rules circumvents the AC_AZURE_0285 policy. Meaning that the deployed resource will be the same disregarding if the rules are defined inline or as independent azurerm resource.

Either a policy should work disregarding which way a resource is configured, or several separate policies should exist covering the same issue. One cannot know or expect that developers are following one or the other way of configuring a resource.

What I Did

Following terraform code with inline rule:

resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-network"
  address_space       = ["10.0.0.0/16"]
  location            = data.azurerm_resource_group.rg.location
  resource_group_name = data.azurerm_resource_group.rg.name
}

resource "azurerm_subnet" "subnet" {
  name                 = "subnet1"
  resource_group_name  = data.azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.2.0/24"]
}

resource "azurerm_network_security_group" "allow_ssh" {
  name = "allow_ssh"
  location = data.azurerm_resource_group.rg.location
  resource_group_name = data.azurerm_resource_group.rg.name

  security_rule {
    name = "allow_ssh"
    priority = 100
    direction = "Inbound"
    access = "Allow"
    protocol = "TCP"
    source_port_range = "*"
    source_address_prefix = "*"
    destination_port_range = "22"
    destination_address_prefix = "*"
  }
}

resource "azurerm_subnet_network_security_group_association" "subnet1_nsg" {
  network_security_group_id = azurerm_network_security_group.allow_ssh.id
  subnet_id = azurerm_subnet.subnet.id
}

Produced following terrascan output:

>terrascan scan -i terraform -t azure

Scan Summary -

        File/Folder         :   ~\workspace\StaticCodeAnalysis\tf\subnet-without-module-inline-rules
        IaC Type            :   terraform
        Scanned At          :   2022-11-15 10:22:51.8402614 +0000 UTC
        Policies Validated  :   1
        Violated Policies   :   0
        Low                 :   0
        Medium              :   0
        High                :   0

Using an azurerm resource for network rule with following code:

resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-network"
  address_space       = ["10.0.0.0/16"]
  location            = data.azurerm_resource_group.rg.location
  resource_group_name = data.azurerm_resource_group.rg.name
}

resource "azurerm_subnet" "subnet" {
  name                 = "subnet1"
  resource_group_name  = data.azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.2.0/24"]
}

resource "azurerm_network_security_group" "allow_ssh" {
  name = "allow_ssh"
  location = data.azurerm_resource_group.rg.location
  resource_group_name = data.azurerm_resource_group.rg.name
}

resource "azurerm_network_security_rule" "ssh_allow" {
  network_security_group_name = azurerm_network_security_group.allow_ssh.name
  name = "allow_ssh"
  priority = 100
  direction = "Inbound"
  access = "Allow"
  protocol = "Tcp"
  source_port_range = "*"
  source_address_prefix = "*"
  destination_port_range = "22"
  destination_address_prefix = "*"
}

resource "azurerm_subnet_network_security_group_association" "subnet1_nsg" {
  network_security_group_id = azurerm_network_security_group.allow_ssh.id
  subnet_id = azurerm_subnet.subnet.id
}

Produces following expected output:

>terrascan scan -i terraform -t azure

Violation Details -

        Description    :        Ensure SSH (Tcp:22) is not exposed to entire internet for Azure Network Security Rule
        File           :        main.tf
        Module Name    :        root
        Plan Root      :        .\
        Line           :        29
        Severity       :        HIGH
        -----------------------------------------------------------------------

Scan Summary -

        File/Folder         : ~\workspace\StaticCodeAnalysis\tf\subnet-without-module
        IaC Type            :   terraform
        Scanned At          :   2022-11-15 10:25:31.1276851 +0000 UTC
        Policies Validated  :   46
        Violated Policies   :   1
        Low                 :   0
        Medium              :   0
        High                :   1
jakub-i commented 2 years ago

Did some more detective work, and tested following terraform snippet:

resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-network"
  address_space       = ["10.0.0.0/16"]
  location            = data.azurerm_resource_group.rg.location
  resource_group_name = data.azurerm_resource_group.rg.name
}

resource "azurerm_subnet" "subnet" {
  name                 = "subnet1"
  resource_group_name  = data.azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.2.0/24"]
}

resource "azurerm_network_security_group" "nsg" {
  name = "ngs1"
  location = data.azurerm_resource_group.rg.location
  resource_group_name = data.azurerm_resource_group.rg.name

  security_rule {
    name = "allow_ssh"
    priority = 100
    direction = "Inbound"
    access = "Allow"
    protocol = "TCP"
    source_port_range = "*"
    source_address_prefix = "*"
    destination_port_range = "22"
    destination_address_prefix = "*"
  }
}

resource "azurerm_network_security_rule" "rule1" {
  network_security_group_name = azurerm_network_security_group.nsg.name
  name = "rule1"
  priority = 101
  direction = "Inbound"
  access = "Deny"
  protocol = "Tcp"
  source_port_range = "*"
  source_address_prefix = "*"
  destination_port_range = "443"
  destination_address_prefix = "*"
}

resource "azurerm_subnet_network_security_group_association" "subnet1_nsg" {
  network_security_group_id = azurerm_network_security_group.nsg.id
  subnet_id = azurerm_subnet.subnet.id
}

Which returned following output:

> terrascan scan -i terraform -t azure

Violation Details -

        Description    :        Ensure SSH (Tcp:22) is not exposed to entire internet for Azure Network Security Rule
        File           :        main.tf
        Module Name    :        root
        Plan Root      :        .\
        Line           :        23
        Severity       :        HIGH
        -----------------------------------------------------------------------

Scan Summary -

        File/Folder         :   ~\workspace\Zure.IaC.StaticCodeAnalysis\tf\subnet-without-module-inline-rules
        IaC Type            :   terraform
        Scanned At          :   2022-11-16 12:12:21.9045731 +0000 UTC
        Policies Validated  :   46
        Violated Policies   :   1
        Low                 :   0
        Medium              :   0
        High                :   1

So the .rego implementation works, but the policy is only asserted for in-line and out-line network security rules if there is a azurerm_network_security_rule resource defined. Terraform provider documentation for azurerm_network_security_group and for azurerm_network_security_rule states that only in-line or out-line approach should be used and not both.