Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(terraform): check cognitive services restrict outbound network #6919

Merged
merged 6 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import Any, List, Dict

from checkov.common.models.enums import CheckCategories, CheckResult
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck


class OpenAICognitiveServicesRestrictOutboundNetwork(BaseResourceCheck):
def __init__(self):
name = "Ensure that Azure Cognitive Services account hosted with OpenAI is configured with data loss prevention"
id = "CKV_AZURE_247"
supported_resources = ('azurerm_cognitive_account', )
categories = (CheckCategories.NETWORKING, )
super().__init__(
name=name,
id=id,
categories=categories,
supported_resources=supported_resources,
)

def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
if conf.get("kind", [""])[0].lower() != 'openai':
return CheckResult.PASSED

outbound_network_access_restricted = conf.get('outbound_network_access_restricted', [None])[0]
fqdns = conf.get('fqdns', [[]])[0]
if not outbound_network_access_restricted or not fqdns:
return CheckResult.FAILED

return CheckResult.PASSED


check = OpenAICognitiveServicesRestrictOutboundNetwork()
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
resource "azurerm_cognitive_account" "pass_openai" {
name = "openai-account"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
kind = "OpenAI"
identity {
type = "a"
}
sku_name = "S0"

outbound_network_access_restricted = true
fqdns = ["openai.example.com"] # Valid FQDN should pass the check

tags = {
Acceptance = "Test"
}
}

resource "azurerm_cognitive_account" "fail_openai_missing_fqdns" {
name = "openai-account-missing-fqdns"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
kind = "OpenAI"
identity {
type = "a"
}
sku_name = "S0"

outbound_network_access_restricted = true
fqdns = [] # Empty list of FQDNs should trigger failure

tags = {
Acceptance = "Test"
}
}

resource "azurerm_cognitive_account" "fail_openai_missing_outbound_network_access" {
name = "openai-account-missing-outbound-network-access"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
kind = "OpenAI"
identity {
type = "a"
}
sku_name = "S0"

# Missing outbound_network_access_restricted field should trigger failure
fqdns = ["openai.example.com"]

tags = {
Acceptance = "Test"
}
}

resource "azurerm_cognitive_account" "pass_non_openai" {
name = "non-openai-account"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
kind = "TextAnalytics" # Non-OpenAI kind should automatically pass the check
identity {
type = "a"
}
sku_name = "S0"

outbound_network_access_restricted = false
fqdns = [] # Doesn't matter since kind is not OpenAI

tags = {
Acceptance = "Test"
}
}

resource "azurerm_cognitive_account" "fail_openai_missing_fqdns_and_outbound_network_access" {
name = "openai-account-missing-both"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
kind = "OpenAI"
identity {
type = "a"
}
sku_name = "S0"

# Missing outbound access should trigger failure
# Empty FQDNs list should trigger failure

tags = {
Acceptance = "Test"
}
}

resource "azurerm_cognitive_account" "pass_openai_multiple_fqdns" {
name = "openai-account-multiple-fqdns"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
kind = "OpenAI"
identity {
type = "a"
}
sku_name = "S0"

outbound_network_access_restricted = true
fqdns = ["openai1.example.com", "openai2.example.com", "openai3.example.com"] # Multiple FQDNs should pass

tags = {
Acceptance = "Test"
}
}

resource "azurerm_cognitive_account" "fail_openai_missing_fqdns_but_present_outbound_network_access" {
name = "openai-account-failed-missing-fqdns"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
kind = "OpenAI"
identity {
type = "a"
}
sku_name = "S0"

outbound_network_access_restricted = true # Present outbound access but missing FQDNs
fqdns = [] # Empty list of FQDNs should trigger failure

tags = {
Acceptance = "Test"
}
}

resource "azurerm_cognitive_account" "fail_openai_no_outbound_access_and_multiple_fqdns" {
name = "openai-account-failed-no-outbound-access-multiple-fqdns"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
kind = "OpenAI"
identity {
type = "a"
}
sku_name = "S0"

# Missing outbound access but multiple FQDNs present
fqdns = ["openai1.example.com", "openai2.example.com", "openai3.example.com"] # Multiple FQDNs should trigger failure due to missing outbound access

tags = {
Acceptance = "Test"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import unittest
from pathlib import Path

from checkov.runner_filter import RunnerFilter
from checkov.terraform.checks.resource.azure.OpenAICognitiveServicesRestrictOutboundNetwork import check
from checkov.terraform.runner import Runner


class TestOpenAICognitiveServicesRestrictedOutboundNetwork(unittest.TestCase):
def test(self):
test_files_dir = Path(__file__).parent / "example_OpenAICognitiveServicesRestrictOutboundNetwork"

report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
summary = report.get_summary()

passing_resources = {
"azurerm_cognitive_account.pass_openai",
"azurerm_cognitive_account.pass_non_openai",
"azurerm_cognitive_account.pass_openai_multiple_fqdns",
}
failing_resources = {
"azurerm_cognitive_account.fail_openai_missing_fqdns",
"azurerm_cognitive_account.fail_openai_missing_outbound_network_access",
"azurerm_cognitive_account.fail_openai_missing_fqdns_and_outbound_network_access",
"azurerm_cognitive_account.fail_openai_missing_fqdns_but_present_outbound_network_access",
"azurerm_cognitive_account.fail_openai_no_outbound_access_and_multiple_fqdns",
}

passed_check_resources = {c.resource for c in report.passed_checks}
failed_check_resources = {c.resource for c in report.failed_checks}

self.assertEqual(summary["passed"], len(passing_resources))
self.assertEqual(summary["failed"], len(failing_resources))
self.assertEqual(summary["skipped"], 0)
self.assertEqual(summary["parsing_errors"], 0)

self.assertEqual(passing_resources, passed_check_resources)
self.assertEqual(failing_resources, failed_check_resources)


if __name__ == "__main__":
unittest.main()
Loading