From 8e8bbbfa050fb309b9827d9d216e655c85b50a58 Mon Sep 17 00:00:00 2001 From: Ablaze005 Date: Sat, 8 Aug 2026 23:37:36 +0100 Subject: [PATCH 1/7] chore(scanner): add placeholder for storage Account HTTPS enforcement rule (AZ-STORAGE-HTTPS-001) --- scanner/rules/az_stor_006.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 scanner/rules/az_stor_006.py diff --git a/scanner/rules/az_stor_006.py b/scanner/rules/az_stor_006.py new file mode 100644 index 0000000..e69de29 From c3cb109f9090a98a4f121cbe0b1bb204664360cd Mon Sep 17 00:00:00 2001 From: Ablaze005 Date: Sat, 8 Aug 2026 23:37:36 +0100 Subject: [PATCH 2/7] chore(scanner): add placeholder for storage Account HTTPS enforcement rule (AZ-STORAGE-HTTPS-001) Signed-off-by: Ablaze005 --- scanner/rules/az_stor_006.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 scanner/rules/az_stor_006.py diff --git a/scanner/rules/az_stor_006.py b/scanner/rules/az_stor_006.py new file mode 100644 index 0000000..e69de29 From 6e0e9cdaa82be115464d432456f4a3b589704f61 Mon Sep 17 00:00:00 2001 From: Ablaze005 Date: Mon, 10 Aug 2026 10:50:06 +0100 Subject: [PATCH 3/7] Add HTTPS enforcement rule and tests --- scanner/rules/az_stor_006.py | 41 ++++++++++++++++++++++++++++++ tests/test_az_stor_006.py | 49 ++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 tests/test_az_stor_006.py diff --git a/scanner/rules/az_stor_006.py b/scanner/rules/az_stor_006.py index e69de29..c8181a1 100644 --- a/scanner/rules/az_stor_006.py +++ b/scanner/rules/az_stor_006.py @@ -0,0 +1,41 @@ +""" +Rule ID: AZ-STORAGE-HTTPS-001 +Title: Storage Account HTTPS Enforcement +Severity: HIGH +Category: Storage +Description: Detects Azure Storage Accounts that do not enforce HTTPS-only traffic. +""" + +def get_storage_accounts(subscription_id): + """ + Placeholder. The scanner engine will inject real storage accounts. + Tests will mock this function. + """ + raise NotImplementedError("Scanner engine must provide storage accounts") + +def scan(subscription_id): + """ + Scans all Storage Accounts in the given subscription + and returns those that do NOT enforce HTTPS-only. + """ + + storage_accounts = get_storage_accounts(subscription_id) + findings = [] + + for account in storage_accounts: + props = account.get("properties", {}) + https_only = props.get("supportsHttpsTrafficOnly", True) + + if not https_only: + findings.append({ + "id": "AZ-STORAGE-HTTPS-001", + "resource_id": account.get("id"), + "resource_name": account.get("name"), + "resource_group": account.get("resourceGroup"), + "subscription_id": subscription_id, + "severity": "HIGH", + "category": "Storage", + "description": "Storage Account does not enforce HTTPS-only traffic.", + }) + + return findings diff --git a/tests/test_az_stor_006.py b/tests/test_az_stor_006.py new file mode 100644 index 0000000..ea7ad1a --- /dev/null +++ b/tests/test_az_stor_006.py @@ -0,0 +1,49 @@ +import unittest +import scanner.rules.az_stor_006 as rule + +class TestStorageHttpsRule(unittest.TestCase): + + def test_storage_https_disabled(self): + # Mock storage accounts + def mock_list_storage_accounts(subscription_id): + return [ + { + "id": "/subscriptions/test-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/teststorage", + "name": "teststorage", + "resourceGroup": "rg", + "properties": { + "supportsHttpsTrafficOnly": False + } + } + ] + + # Patch the function inside the rule + rule.get_storage_accounts = mock_list_storage_accounts + + findings = rule.scan("test-sub") + + self.assertEqual(len(findings), 1) + self.assertEqual(findings[0]["id"], "AZ-STORAGE-HTTPS-001") + self.assertEqual(findings[0]["severity"], "HIGH") + + def test_storage_https_enabled(self): + def mock_list_storage_accounts(subscription_id): + return [ + { + "id": "/subscriptions/test-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/teststorage", + "name": "teststorage", + "resourceGroup": "rg", + "properties": { + "supportsHttpsTrafficOnly": True + } + } + ] + + rule.get_storage_accounts = mock_list_storage_accounts + + findings = rule.scan("test-sub") + + self.assertEqual(findings, []) + +if __name__ == "__main__": + unittest.main() From 9d64e5124fdedf909722b83c4a11dd133f7c15a5 Mon Sep 17 00:00:00 2001 From: Ablaze005 Date: Mon, 10 Aug 2026 21:42:45 +0100 Subject: [PATCH 4/7] Updated code --- playbooks/cli/fix_az_stor_006.sh | 0 scanner/rules/az_stor_006.py | 164 ++++++++++++++++++++++++------- tests/test_az_stor_006.py | 97 +++++++++--------- 3 files changed, 178 insertions(+), 83 deletions(-) create mode 100644 playbooks/cli/fix_az_stor_006.sh diff --git a/playbooks/cli/fix_az_stor_006.sh b/playbooks/cli/fix_az_stor_006.sh new file mode 100644 index 0000000..e69de29 diff --git a/scanner/rules/az_stor_006.py b/scanner/rules/az_stor_006.py index c8181a1..032b141 100644 --- a/scanner/rules/az_stor_006.py +++ b/scanner/rules/az_stor_006.py @@ -1,41 +1,137 @@ -""" -Rule ID: AZ-STORAGE-HTTPS-001 -Title: Storage Account HTTPS Enforcement -Severity: HIGH -Category: Storage -Description: Detects Azure Storage Accounts that do not enforce HTTPS-only traffic. +"""AZ-STOR-006: Storage account should enforce HTTPS-only traffic. + +This rule implementation is written to be tolerant of both calling +conventions seen in the repository's tests and rule modules: + +1. scan(azure_client, subscription_id) — iterates azure_client.get_storage_accounts() +2. scan(cache, resource) — evaluates a single resource object/dict + +The function will detect which form was called and behave accordingly. """ -def get_storage_accounts(subscription_id): - """ - Placeholder. The scanner engine will inject real storage accounts. - Tests will mock this function. - """ - raise NotImplementedError("Scanner engine must provide storage accounts") +from typing import Any, Dict, List, Optional -def scan(subscription_id): - """ - Scans all Storage Accounts in the given subscription - and returns those that do NOT enforce HTTPS-only. +RULE_ID = "AZ-STOR-006" +RULE_NAME = "Storage accounts should enforce HTTPS-only traffic" +SEVERITY = "HIGH" +CATEGORY = "Storage" +FRAMEWORKS = { + "CIS": "CIS-Azure-1.4.0", + "NIST": "AC-17", + "ISO": "A.10.1", + "SOC2": "CC6.1", +} +REMEDIATION = "Enable httpsOnly on the storage account: az storage account update --name --resource-group --https-only true" +PLAYBOOK = "playbooks/cli/fix_az_stor_006.sh" +REFERENCES = ["https://learn.microsoft.com/azure/storage/common/secure-your-storage-account"] + + +def _extract_properties(resource: Any) -> Dict[str, Any]: + """Return a dict-like view of resource properties regardless of input type.""" + # resource may be a dict-like object with .get or a SimpleNamespace/object + if resource is None: + return {} + if hasattr(resource, "get"): + # dict-like + props = resource.get("properties") or {} + if isinstance(props, dict): + return props + # props might be SimpleNamespace + if hasattr(props, "__dict__"): + return vars(props) + return {} + + # object-like + props_obj = getattr(resource, "properties", None) + if props_obj is None: + # maybe flags exist at top-level on the resource + out: Dict[str, Any] = {} + for attr in ("supportsHttpsTrafficOnly", "enableHttpsTrafficOnly", "httpsOnly"): + val = getattr(resource, attr, None) + if val is not None: + out[attr] = val + return out + + # props_obj may be namespace or object + if hasattr(props_obj, "get"): + return props_obj + if hasattr(props_obj, "__dict__"): + return vars(props_obj) + return {} + + +def _is_https_disabled(props: Dict[str, Any]) -> bool: + """Return True if HTTPS-only is explicitly disabled for the resource.""" + https_only = props.get("supportsHttpsTrafficOnly") + if https_only is None: + https_only = props.get("enableHttpsTrafficOnly") + if https_only is None: + https_only = props.get("httpsOnly") + + return https_only is False or https_only in ("false", 0) + + +def _resource_identifiers(resource: Any) -> Dict[str, Optional[str]]: + if hasattr(resource, "get"): + return { + "id": resource.get("id"), + "name": resource.get("name"), + "type": resource.get("type"), + } + return {"id": getattr(resource, "id", None), "name": getattr(resource, "name", None), "type": getattr(resource, "type", None)} + + +def scan(cache: Any, resource_or_subscription: Any) -> List[Dict[str, Any]]: + """Support both scan(azure_client, subscription_id) and scan(cache, resource). + + - If resource_or_subscription is a string, treat it as subscription_id and + iterate cache.get_storage_accounts(). + - Otherwise treat resource_or_subscription as a single resource object/dict. """ + findings: List[Dict[str, Any]] = [] - storage_accounts = get_storage_accounts(subscription_id) - findings = [] - - for account in storage_accounts: - props = account.get("properties", {}) - https_only = props.get("supportsHttpsTrafficOnly", True) - - if not https_only: - findings.append({ - "id": "AZ-STORAGE-HTTPS-001", - "resource_id": account.get("id"), - "resource_name": account.get("name"), - "resource_group": account.get("resourceGroup"), - "subscription_id": subscription_id, - "severity": "HIGH", - "category": "Storage", - "description": "Storage Account does not enforce HTTPS-only traffic.", - }) + # Path A: called as scan(azure_client, subscription_id) + if isinstance(resource_or_subscription, str): + azure_client = cache + for account in azure_client.get_storage_accounts(): + props = _extract_properties(account) + if _is_https_disabled(props): + ids = _resource_identifiers(account) + findings.append( + { + "rule_id": RULE_ID, + "rule_name": RULE_NAME, + "severity": SEVERITY, + "category": CATEGORY, + "resource_id": ids.get("id"), + "resource_name": ids.get("name"), + "resource_type": ids.get("type") or "Microsoft.Storage/storageAccounts", + "description": "Storage account does not enforce HTTPS-only traffic (httpsOnly is false).", + "remediation": REMEDIATION, + "playbook": PLAYBOOK, + "frameworks": FRAMEWORKS, + } + ) + return findings + # Path B: called as scan(cache, resource) + resource = resource_or_subscription + props = _extract_properties(resource) + if _is_https_disabled(props): + ids = _resource_identifiers(resource) + findings.append( + { + "rule_id": RULE_ID, + "rule_name": RULE_NAME, + "severity": SEVERITY, + "category": CATEGORY, + "resource_id": ids.get("id"), + "resource_name": ids.get("name"), + "resource_type": ids.get("type") or "Microsoft.Storage/storageAccounts", + "description": "Storage account does not enforce HTTPS-only traffic (httpsOnly is false).", + "remediation": REMEDIATION, + "playbook": PLAYBOOK, + "frameworks": FRAMEWORKS, + } + ) return findings diff --git a/tests/test_az_stor_006.py b/tests/test_az_stor_006.py index ea7ad1a..1f83f14 100644 --- a/tests/test_az_stor_006.py +++ b/tests/test_az_stor_006.py @@ -1,49 +1,48 @@ -import unittest -import scanner.rules.az_stor_006 as rule - -class TestStorageHttpsRule(unittest.TestCase): - - def test_storage_https_disabled(self): - # Mock storage accounts - def mock_list_storage_accounts(subscription_id): - return [ - { - "id": "/subscriptions/test-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/teststorage", - "name": "teststorage", - "resourceGroup": "rg", - "properties": { - "supportsHttpsTrafficOnly": False - } - } - ] - - # Patch the function inside the rule - rule.get_storage_accounts = mock_list_storage_accounts - - findings = rule.scan("test-sub") - - self.assertEqual(len(findings), 1) - self.assertEqual(findings[0]["id"], "AZ-STORAGE-HTTPS-001") - self.assertEqual(findings[0]["severity"], "HIGH") - - def test_storage_https_enabled(self): - def mock_list_storage_accounts(subscription_id): - return [ - { - "id": "/subscriptions/test-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/teststorage", - "name": "teststorage", - "resourceGroup": "rg", - "properties": { - "supportsHttpsTrafficOnly": True - } - } - ] - - rule.get_storage_accounts = mock_list_storage_accounts - - findings = rule.scan("test-sub") - - self.assertEqual(findings, []) - -if __name__ == "__main__": - unittest.main() +"""Rule tests for AZ-STOR-006: HTTPS-only enforcement for storage accounts. + +These tests follow the same style as other storage rule tests in +[tests/test_rules_storage.py](/C:/Users/ACER/openshield/openshield/tests/test_rules_storage.py). +""" + +import scanner.rules.az_stor_006 as az_stor_006 +from tests.helpers.mock_azure import make_resource + +_SUB = "00000000-0000-0000-0000-000000000001" +_RG = "rg-test" + + +def _storage_id(name): + return f"/subscriptions/{_SUB}/resourceGroups/{_RG}/providers/Microsoft.Storage/storageAccounts/{name}" + + +def test_az_stor_006_compliant_returns_no_findings(mock_azure, subscription_id): + """A storage account with HTTPS-only enabled must produce no findings.""" + acct = make_resource( + id=_storage_id("https-only-storage"), + name="https-only-storage", + properties={"supportsHttpsTrafficOnly": True}, + ) + mock_azure.set_storage_accounts([acct]) + findings = az_stor_006.scan(mock_azure, subscription_id) + assert findings == [] + + +def test_az_stor_006_noncompliant_returns_one_finding(mock_azure, subscription_id): + """A storage account that allows HTTP traffic must produce exactly one finding.""" + acct = make_resource( + id=_storage_id("http-allowed-storage"), + name="http-allowed-storage", + properties={"supportsHttpsTrafficOnly": False}, + ) + mock_azure.set_storage_accounts([acct]) + findings = az_stor_006.scan(mock_azure, subscription_id) + assert len(findings) == 1 + finding = findings[0] + # basic schema checks similar to tests/test_rules_storage.py + assert finding["rule_id"] == "AZ-STOR-006" + assert finding["severity"] == "HIGH" + assert finding["category"] == "Storage" + assert finding["resource_name"] == "http-allowed-storage" + # message/description compatibility + text = finding.get("message") or finding.get("description", "") + assert "https" in text.lower() From ed1cc46ceaa644a264f76a5b2d176ab8385bc545 Mon Sep 17 00:00:00 2001 From: Ablaze005 Date: Wed, 12 Aug 2026 22:42:42 +0100 Subject: [PATCH 5/7] Update AZ-STOR-006 rule and remediation playbook Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- playbooks/cli/fix_az_stor_006.sh | 33 +++++ scanner/rules/az_stor_006.py | 208 +++++++++++++------------------ 2 files changed, 120 insertions(+), 121 deletions(-) diff --git a/playbooks/cli/fix_az_stor_006.sh b/playbooks/cli/fix_az_stor_006.sh index e69de29..a3ebfe3 100644 --- a/playbooks/cli/fix_az_stor_006.sh +++ b/playbooks/cli/fix_az_stor_006.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# OpenShield Remediation Playbook +# Rule: AZ-STOR-006 — Storage account HTTPS-only enforcement disabled +# Usage: ./fix_az_stor_006.sh +# Severity: HIGH + +set -euo pipefail + +RESOURCE_GROUP="${1:-}" +STORAGE_ACCOUNT="${2:-}" + +if [ -z "$RESOURCE_GROUP" ] || [ -z "$STORAGE_ACCOUNT" ]; then + echo "Usage: $0 " + exit 1 +fi + +echo "Enabling HTTPS-only enforcement for storage account: ${STORAGE_ACCOUNT} (rg: ${RESOURCE_GROUP})" + +# Azure CLI command to enable HTTPS-only (secure transfer) +az storage account update \ + --name "${STORAGE_ACCOUNT}" \ + --resource-group "${RESOURCE_GROUP}" \ + --https-only true \ + --output json + +echo "Verification: current supportsHttpsTrafficOnly value:" +az storage account show \ + --name "${STORAGE_ACCOUNT}" \ + --resource-group "${RESOURCE_GROUP}" \ + --query "supportsHttpsTrafficOnly" \ + --output tsv + +echo "Remediation complete: HTTPS-only enforcement enabled for ${STORAGE_ACCOUNT}." diff --git a/scanner/rules/az_stor_006.py b/scanner/rules/az_stor_006.py index 032b141..4e3113c 100644 --- a/scanner/rules/az_stor_006.py +++ b/scanner/rules/az_stor_006.py @@ -1,137 +1,103 @@ -"""AZ-STOR-006: Storage account should enforce HTTPS-only traffic. - -This rule implementation is written to be tolerant of both calling -conventions seen in the repository's tests and rule modules: - -1. scan(azure_client, subscription_id) — iterates azure_client.get_storage_accounts() -2. scan(cache, resource) — evaluates a single resource object/dict - -The function will detect which form was called and behave accordingly. +""" +AZ-STOR-006: Storage account HTTPS-only enforcement disabled """ +import logging from typing import Any, Dict, List, Optional +logger = logging.getLogger(__name__) + +# ── Required module-level constants ───────────────────────────────────────── + RULE_ID = "AZ-STOR-006" -RULE_NAME = "Storage accounts should enforce HTTPS-only traffic" +RULE_NAME = "Storage Account HTTPS Only Enforcement Disabled" SEVERITY = "HIGH" CATEGORY = "Storage" FRAMEWORKS = { - "CIS": "CIS-Azure-1.4.0", - "NIST": "AC-17", - "ISO": "A.10.1", + "CIS": "3.2", + "NIST": "SC-8", + "ISO27001": "A.10.1.1", "SOC2": "CC6.1", } -REMEDIATION = "Enable httpsOnly on the storage account: az storage account update --name --resource-group --https-only true" +DESCRIPTION = ( + "The storage account does not enforce HTTPS-only traffic. When " + "`supportsHttpsTrafficOnly` is false, data can be transmitted over " + "unencrypted HTTP, exposing it to interception and downgrade attacks. " + "Enforce HTTPS-only to ensure secure transport for storage account traffic." +) +REMEDIATION = ( + "Enable HTTPS-only enforcement on the storage account by setting " + "`supportsHttpsTrafficOnly` to true. In the Azure portal: Storage Account " + "> Configuration > Secure transfer required > Enabled. Or use the Azure CLI: " + "`az storage account update --name --resource-group --https-only true`." +) PLAYBOOK = "playbooks/cli/fix_az_stor_006.sh" -REFERENCES = ["https://learn.microsoft.com/azure/storage/common/secure-your-storage-account"] - - -def _extract_properties(resource: Any) -> Dict[str, Any]: - """Return a dict-like view of resource properties regardless of input type.""" - # resource may be a dict-like object with .get or a SimpleNamespace/object - if resource is None: - return {} - if hasattr(resource, "get"): - # dict-like - props = resource.get("properties") or {} - if isinstance(props, dict): - return props - # props might be SimpleNamespace - if hasattr(props, "__dict__"): - return vars(props) - return {} - - # object-like - props_obj = getattr(resource, "properties", None) - if props_obj is None: - # maybe flags exist at top-level on the resource - out: Dict[str, Any] = {} - for attr in ("supportsHttpsTrafficOnly", "enableHttpsTrafficOnly", "httpsOnly"): - val = getattr(resource, attr, None) - if val is not None: - out[attr] = val - return out - - # props_obj may be namespace or object - if hasattr(props_obj, "get"): - return props_obj - if hasattr(props_obj, "__dict__"): - return vars(props_obj) - return {} - - -def _is_https_disabled(props: Dict[str, Any]) -> bool: - """Return True if HTTPS-only is explicitly disabled for the resource.""" - https_only = props.get("supportsHttpsTrafficOnly") - if https_only is None: - https_only = props.get("enableHttpsTrafficOnly") - if https_only is None: - https_only = props.get("httpsOnly") - - return https_only is False or https_only in ("false", 0) - - -def _resource_identifiers(resource: Any) -> Dict[str, Optional[str]]: - if hasattr(resource, "get"): - return { - "id": resource.get("id"), - "name": resource.get("name"), - "type": resource.get("type"), - } - return {"id": getattr(resource, "id", None), "name": getattr(resource, "name", None), "type": getattr(resource, "type", None)} - - -def scan(cache: Any, resource_or_subscription: Any) -> List[Dict[str, Any]]: - """Support both scan(azure_client, subscription_id) and scan(cache, resource). - - - If resource_or_subscription is a string, treat it as subscription_id and - iterate cache.get_storage_accounts(). - - Otherwise treat resource_or_subscription as a single resource object/dict. + +# ── Required scan function ─────────────────────────────────────────────────── + + +def scan(azure_client: Any, subscription_id: str) -> List[Dict[str, Any]]: + """Detect storage accounts with HTTPS-only enforcement disabled. + + For each storage account returned by the azure_client, check the + `supportsHttpsTrafficOnly` property. If the property is False, emit a + finding. If the property cannot be determined (permissions or error), + skip and log a warning to avoid false positives. + + Returns: + A list of finding dicts for storage accounts where HTTPS-only is not enforced. """ findings: List[Dict[str, Any]] = [] - # Path A: called as scan(azure_client, subscription_id) - if isinstance(resource_or_subscription, str): - azure_client = cache - for account in azure_client.get_storage_accounts(): - props = _extract_properties(account) - if _is_https_disabled(props): - ids = _resource_identifiers(account) - findings.append( - { - "rule_id": RULE_ID, - "rule_name": RULE_NAME, - "severity": SEVERITY, - "category": CATEGORY, - "resource_id": ids.get("id"), - "resource_name": ids.get("name"), - "resource_type": ids.get("type") or "Microsoft.Storage/storageAccounts", - "description": "Storage account does not enforce HTTPS-only traffic (httpsOnly is false).", - "remediation": REMEDIATION, - "playbook": PLAYBOOK, - "frameworks": FRAMEWORKS, - } - ) - return findings - - # Path B: called as scan(cache, resource) - resource = resource_or_subscription - props = _extract_properties(resource) - if _is_https_disabled(props): - ids = _resource_identifiers(resource) - findings.append( - { - "rule_id": RULE_ID, - "rule_name": RULE_NAME, - "severity": SEVERITY, - "category": CATEGORY, - "resource_id": ids.get("id"), - "resource_name": ids.get("name"), - "resource_type": ids.get("type") or "Microsoft.Storage/storageAccounts", - "description": "Storage account does not enforce HTTPS-only traffic (httpsOnly is false).", - "remediation": REMEDIATION, - "playbook": PLAYBOOK, - "frameworks": FRAMEWORKS, - } + for account in azure_client.get_storage_accounts(): + resource_id = getattr(account, "id", "") + account_name = getattr(account, "name", "") + location = getattr(account, "location", "") + + if not resource_id or not account_name: + continue + + parsed = azure_client.parse_resource_id(resource_id) + resource_group = parsed.get("resource_group", "") + if not resource_group: + continue + + # azure_client.get_storage_account_properties should return: + # True -> supportsHttpsTrafficOnly is True (compliant) + # False -> supportsHttpsTrafficOnly is False (non-compliant) + # None -> could not determine (skip) + https_only: Optional[bool] = azure_client.get_storage_account_https_only( + resource_group, account_name ) + + if https_only is None: + logger.warning( + "AZ-STOR-006: Could not determine HTTPS-only status for %s — skipping. " + "Ensure the service principal has Microsoft.Storage/storageAccounts/read permission.", + account_name, + ) + continue + + if https_only is False: + findings.append( + { + "rule_id": RULE_ID, + "rule_name": RULE_NAME, + "severity": SEVERITY, + "category": CATEGORY, + "resource_id": resource_id, + "resource_name": account_name, + "resource_type": "Microsoft.Storage/storageAccounts", + "description": DESCRIPTION, + "remediation": REMEDIATION, + "playbook": PLAYBOOK, + "frameworks": FRAMEWORKS, + "metadata": { + "resource_group": resource_group, + "location": location, + "supportsHttpsTrafficOnly": False, + }, + } + ) + return findings From 6b15cfd772afcc3de756eebda60f6911d9fcb639 Mon Sep 17 00:00:00 2001 From: Ablaze005 Date: Wed, 12 Aug 2026 22:52:29 +0100 Subject: [PATCH 6/7] Add AZ-STOR-006 regression tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/test_az_stor_006.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_az_stor_006.py b/tests/test_az_stor_006.py index 1f83f14..3d05c8f 100644 --- a/tests/test_az_stor_006.py +++ b/tests/test_az_stor_006.py @@ -46,3 +46,15 @@ def test_az_stor_006_noncompliant_returns_one_finding(mock_azure, subscription_i # message/description compatibility text = finding.get("message") or finding.get("description", "") assert "https" in text.lower() + + +def test_az_stor_006_unknown_status_skips(mock_azure, subscription_id): + """If the HTTPS status cannot be determined, the rule must not flag the resource.""" + acct = make_resource( + id=_storage_id("unknown-storage"), + name="unknown-storage", + properties={"supportsHttpsTrafficOnly": None}, + ) + mock_azure.set_storage_accounts([acct]) + findings = az_stor_006.scan(mock_azure, subscription_id) + assert findings == [] From e59674306805273a0955750790f50229f69dbc25 Mon Sep 17 00:00:00 2001 From: Ablaze005 Date: Wed, 12 Aug 2026 22:53:07 +0100 Subject: [PATCH 7/7] Fix AZ-STOR-006 compatibility with mock resources and add regression tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scanner/rules/az_stor_006.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/scanner/rules/az_stor_006.py b/scanner/rules/az_stor_006.py index 4e3113c..f25ed53 100644 --- a/scanner/rules/az_stor_006.py +++ b/scanner/rules/az_stor_006.py @@ -62,13 +62,19 @@ def scan(azure_client: Any, subscription_id: str) -> List[Dict[str, Any]]: if not resource_group: continue - # azure_client.get_storage_account_properties should return: - # True -> supportsHttpsTrafficOnly is True (compliant) - # False -> supportsHttpsTrafficOnly is False (non-compliant) - # None -> could not determine (skip) - https_only: Optional[bool] = azure_client.get_storage_account_https_only( - resource_group, account_name - ) + # AzureClient implementations vary slightly across integrations: + # - some expose a dedicated get_storage_account_https_only() helper + # - others only surface the value on the resource's properties bag + if hasattr(azure_client, "get_storage_account_https_only"): + https_only: Optional[bool] = azure_client.get_storage_account_https_only( + resource_group, account_name + ) + else: + property_obj = getattr(account, "properties", None) + if property_obj is not None and hasattr(property_obj, "get"): + https_only = property_obj.get("supportsHttpsTrafficOnly") + else: + https_only = getattr(account, "supportsHttpsTrafficOnly", None) if https_only is None: logger.warning(