From 615bacccafde85abeeb222fa986cfa901cb9c6f2 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 21 May 2025 07:59:53 +0200 Subject: [PATCH 01/25] chore: tweak some wording for consistency (#7794) --- ui/components/findings/table/finding-detail.tsx | 4 ++-- ui/components/providers/table/column-providers.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ui/components/findings/table/finding-detail.tsx b/ui/components/findings/table/finding-detail.tsx index facd6a1d9f..e232b2a648 100644 --- a/ui/components/findings/table/finding-detail.tsx +++ b/ui/components/findings/table/finding-detail.tsx @@ -115,13 +115,13 @@ export const FindingDetail = ({ - + - + diff --git a/ui/components/providers/table/column-providers.tsx b/ui/components/providers/table/column-providers.tsx index f31c199920..f16585e34b 100644 --- a/ui/components/providers/table/column-providers.tsx +++ b/ui/components/providers/table/column-providers.tsx @@ -27,7 +27,7 @@ export const ColumnProviders: ColumnDef[] = [ { accessorKey: "account", header: ({ column }) => ( - + ), cell: ({ row }) => { const { @@ -64,7 +64,11 @@ export const ColumnProviders: ColumnDef[] = [ { accessorKey: "uid", header: ({ column }) => ( - + ), cell: ({ row }) => { const { From ad39061e1af52337becc0127f86df620b6a1d140 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Wed, 21 May 2025 08:07:43 +0200 Subject: [PATCH 02/25] fix: retrieve more than 10 providers (#7793) --- ui/CHANGELOG.md | 3 ++- ui/app/(prowler)/findings/page.tsx | 2 +- ui/app/(prowler)/manage-groups/page.tsx | 4 ++-- ui/app/(prowler)/scans/page.tsx | 2 ++ 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 629b18aaec..52b2818551 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -7,7 +7,8 @@ All notable changes to the **Prowler UI** are documented in this file. ### 🐞 Fixes - Added validation to AWS IAM role. [(#7787)](https://github.com/prowler-cloud/prowler/pull/7787) - +- Retrieve more than 10 providers in /scans, /manage-groups and /findings pages. [(#7793)](https://github.com/prowler-cloud/prowler/pull/7793) + --- ## [v1.7.0] (Prowler v5.7.0) diff --git a/ui/app/(prowler)/findings/page.tsx b/ui/app/(prowler)/findings/page.tsx index 3615c77b3b..f137b451cc 100644 --- a/ui/app/(prowler)/findings/page.tsx +++ b/ui/app/(prowler)/findings/page.tsx @@ -43,7 +43,7 @@ export default async function Findings({ sort: encodedSort, filters, }), - getProviders({}), + getProviders({ pageSize: 50 }), getScans({}), ]); diff --git a/ui/app/(prowler)/manage-groups/page.tsx b/ui/app/(prowler)/manage-groups/page.tsx index 178baf320e..c5e5be9932 100644 --- a/ui/app/(prowler)/manage-groups/page.tsx +++ b/ui/app/(prowler)/manage-groups/page.tsx @@ -58,7 +58,7 @@ export default function ManageGroupsPage({ } const SSRAddGroupForm = async () => { - const providersResponse = await getProviders({}); + const providersResponse = await getProviders({ pageSize: 50 }); const rolesResponse = await getRoles({}); const providersData = @@ -95,7 +95,7 @@ const SSRDataEditGroup = async ({ return
Provider group not found
; } - const providersResponse = await getProviders({}); + const providersResponse = await getProviders({ pageSize: 50 }); const rolesResponse = await getRoles({}); const providersList = diff --git a/ui/app/(prowler)/scans/page.tsx b/ui/app/(prowler)/scans/page.tsx index 737d7f284b..0679139770 100644 --- a/ui/app/(prowler)/scans/page.tsx +++ b/ui/app/(prowler)/scans/page.tsx @@ -29,6 +29,7 @@ export default async function Scans({ filters: { "filter[connected]": true, }, + pageSize: 50, }); const providerInfo = @@ -42,6 +43,7 @@ export default async function Scans({ const providersCountConnected = await getProviders({ filters: { "filter[connected]": true }, + pageSize: 50, }); const thereIsNoProviders = !providersCountConnected?.data || providersCountConnected.data.length === 0; From 6f7cd85a18c1c1c57da999ea0c78bccb3b1e82dc Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Wed, 21 May 2025 12:14:30 +0545 Subject: [PATCH 03/25] chore(backport): create label on minor release (#7791) --- .github/workflows/create-backport-label.yml | 67 +++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/create-backport-label.yml diff --git a/.github/workflows/create-backport-label.yml b/.github/workflows/create-backport-label.yml new file mode 100644 index 0000000000..0bc93bd45d --- /dev/null +++ b/.github/workflows/create-backport-label.yml @@ -0,0 +1,67 @@ +name: Create Backport Label + +on: + release: + types: [published] + +jobs: + create_label: + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + steps: + - name: Create backport label + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + OWNER_REPO: ${{ github.repository }} + run: | + VERSION_ONLY=${RELEASE_TAG#v} # Remove 'v' prefix if present (e.g., v3.2.0 -> 3.2.0) + + # Check if it's a minor version (X.Y.0) + if [[ "$VERSION_ONLY" =~ ^[0-9]+\.[0-9]+\.0$ ]]; then + echo "Release ${RELEASE_TAG} (version ${VERSION_ONLY}) is a minor version. Proceeding to create backport label." + + TWO_DIGIT_VERSION=${VERSION_ONLY%.0} # Extract X.Y from X.Y.0 (e.g., 5.6 from 5.6.0) + + FINAL_LABEL_NAME="backport-to-v${TWO_DIGIT_VERSION}" + FINAL_DESCRIPTION="Backport PR to the v${TWO_DIGIT_VERSION} branch" + + echo "Effective label name will be: ${FINAL_LABEL_NAME}" + echo "Effective description will be: ${FINAL_DESCRIPTION}" + + # Check if the label already exists + STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${GITHUB_TOKEN}" "https://api.github.com/repos/${OWNER_REPO}/labels/${FINAL_LABEL_NAME}") + + if [ "${STATUS_CODE}" -eq 200 ]; then + echo "Label '${FINAL_LABEL_NAME}' already exists." + elif [ "${STATUS_CODE}" -eq 404 ]; then + echo "Label '${FINAL_LABEL_NAME}' does not exist. Creating it..." + # Prepare JSON data payload + JSON_DATA=$(printf '{"name":"%s","description":"%s","color":"B60205"}' "${FINAL_LABEL_NAME}" "${FINAL_DESCRIPTION}") + + CREATE_STATUS_CODE=$(curl -s -o /tmp/curl_create_response.json -w "%{http_code}" -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + --data "${JSON_DATA}" \ + "https://api.github.com/repos/${OWNER_REPO}/labels") + + CREATE_RESPONSE_BODY=$(cat /tmp/curl_create_response.json) + rm -f /tmp/curl_create_response.json + + if [ "$CREATE_STATUS_CODE" -eq 201 ]; then + echo "Label '${FINAL_LABEL_NAME}' created successfully." + else + echo "Error creating label '${FINAL_LABEL_NAME}'. Status: $CREATE_STATUS_CODE" + echo "Response: $CREATE_RESPONSE_BODY" + exit 1 + fi + else + echo "Error checking for label '${FINAL_LABEL_NAME}'. HTTP Status: ${STATUS_CODE}" + exit 1 + fi + else + echo "Release ${RELEASE_TAG} (version ${VERSION_ONLY}) is not a minor version. Skipping backport label creation." + exit 0 + fi From 84749df70858c8c96c14c7fd2576584319cecaf7 Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Wed, 21 May 2025 08:48:36 +0200 Subject: [PATCH 04/25] feat(admincenter): add new check `admincenter_organization_customer_lockbox_enabled` (#7732) Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> --- prowler/CHANGELOG.md | 1 + prowler/compliance/m365/cis_4.0_m365.json | 4 +- .../__init__.py | 0 ...ion_customer_lockbox_enabled.metadata.json | 32 +++++ ...r_organization_customer_lockbox_enabled.py | 52 ++++++++ .../admincenter/admincenter_service.py | 30 +++++ ...enter_groups_not_public_visibility_test.py | 9 ++ ...anization_customer_lockbox_enabled_test.py | 120 ++++++++++++++++++ .../admincenter/admincenter_service_test.py | 97 +++++++++++--- ...ter_settings_password_never_expire_test.py | 9 ++ ...s_admins_reduced_license_footprint_test.py | 15 +++ ...between_two_and_four_global_admins_test.py | 12 ++ 12 files changed, 359 insertions(+), 22 deletions(-) create mode 100644 prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/__init__.py create mode 100644 prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.metadata.json create mode 100644 prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.py create mode 100644 tests/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index f0f2f8a57c..f71948dc1d 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update the compliance list supported for each provider from docs. [(#7694)](https://github.com/prowler-cloud/prowler/pull/7694) - Allow setting cluster name in in-cluster mode in Kubernetes. [(#7695)](https://github.com/prowler-cloud/prowler/pull/7695) - Add Prowler ThreatScore for M365 provider. [(#7692)](https://github.com/prowler-cloud/prowler/pull/7692) +- Add new check `admincenter_organization_customer_lockbox_enabled`. [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732) - Add GitHub provider. [(#5787)](https://github.com/prowler-cloud/prowler/pull/5787) - Add `repository_default_branch_requires_multiple_approvals` check for GitHub provider. [(#6160)](https://github.com/prowler-cloud/prowler/pull/6160) - Add `repository_default_branch_protection_enabled` check for GitHub provider. [(#6161)](https://github.com/prowler-cloud/prowler/pull/6161) diff --git a/prowler/compliance/m365/cis_4.0_m365.json b/prowler/compliance/m365/cis_4.0_m365.json index 07f2bc2d29..92a48244ea 100644 --- a/prowler/compliance/m365/cis_4.0_m365.json +++ b/prowler/compliance/m365/cis_4.0_m365.json @@ -248,7 +248,9 @@ { "Id": "1.3.6", "Description": "Customer Lockbox is a security feature that provides an additional layer of control and transparency to customer data in Microsoft 365. It offers an approval process for Microsoft support personnel to access organization data and creates an audited trail to meet compliance requirements.", - "Checks": [], + "Checks": [ + "admincenter_organization_customer_lockbox_enabled" + ], "Attributes": [ { "Section": "1 Microsoft 365 admin center", diff --git a/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/__init__.py b/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.metadata.json b/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.metadata.json new file mode 100644 index 0000000000..2c0ba3ebac --- /dev/null +++ b/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "m365", + "CheckID": "admincenter_organization_customer_lockbox_enabled", + "CheckTitle": "Ensure that customer lockbox is enabled for the organization", + "CheckType": [], + "ServiceName": "admincenter", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Exchange Organization Configuration", + "Description": "Customer Lockbox ensures that Microsoft support engineers cannot access content in your tenant to perform a service operation without explicit approval. This feature provides an additional layer of control and transparency over data access requests.", + "Risk": "If Customer Lockbox is not enabled, Microsoft support personnel can access your organization's data for troubleshooting without explicit approval, potentially increasing the risk of unauthorized access or data exfiltration.", + "RelatedUrl": "https://learn.microsoft.com/en-us/azure/security/fundamentals/customer-lockbox-overview", + "Remediation": { + "Code": { + "CLI": "Set-OrganizationConfig -CustomerLockBoxEnabled $true", + "NativeIaC": "", + "Other": "1. Navigate to Microsoft 365 admin center https://admin.microsoft.com. 2. Click Settings > Org settings. 3. Select the Security & privacy tab. 4. Click Customer lockbox. 5. Check the box 'Require approval for all data access requests'. 6. Click Save.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable the Customer Lockbox feature to ensure explicit approval is required before Microsoft engineers can access your data during support operations.", + "Url": "https://learn.microsoft.com/en-us/azure/security/fundamentals/customer-lockbox-overview" + } + }, + "Categories": [ + "e5" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.py b/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.py new file mode 100644 index 0000000000..532b053b14 --- /dev/null +++ b/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.py @@ -0,0 +1,52 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.admincenter.admincenter_client import ( + admincenter_client, +) + + +class admincenter_organization_customer_lockbox_enabled(Check): + """ + Ensure the customer lockbox feature is enabled. + + Customer Lockbox ensures that Microsoft support engineers cannot access content + in your tenant to perform a service operation without explicit approval. This feature + provides an additional layer of control and transparency over data access requests. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """ + Execute the check for the Customer Lockbox feature in Microsoft 365. + + This method checks if the Customer Lockbox feature is enabled in the organization configuration. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + organization_config = admincenter_client.organization_config + if organization_config: + report = CheckReportM365( + metadata=self.metadata(), + resource=organization_config, + resource_name=organization_config.name, + resource_id=organization_config.guid, + ) + report.status = "FAIL" + report.status_extended = ( + "Customer Lockbox is not enabled at organization level." + ) + + if organization_config.customer_lockbox_enabled: + report.status = "PASS" + report.status_extended = ( + "Customer Lockbox is enabled at organization level." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/admincenter/admincenter_service.py b/prowler/providers/m365/services/admincenter/admincenter_service.py index ed8e4ad4a2..09c534edf6 100644 --- a/prowler/providers/m365/services/admincenter/admincenter_service.py +++ b/prowler/providers/m365/services/admincenter/admincenter_service.py @@ -11,6 +11,11 @@ from prowler.providers.m365.m365_provider import M365Provider class AdminCenter(M365Service): def __init__(self, provider: M365Provider): super().__init__(provider) + self.organization_config = None + if self.powershell: + self.powershell.connect_exchange_online() + self.organization_config = self._get_organization_config() + self.powershell.close() loop = get_event_loop() @@ -29,6 +34,25 @@ class AdminCenter(M365Service): self.groups = attributes[1] self.domains = attributes[2] + def _get_organization_config(self): + logger.info("Microsoft365 - Getting Exchange Organization configuration...") + organization_config = None + try: + organization_configuration = self.powershell.get_organization_config() + if organization_configuration: + organization_config = Organization( + name=organization_configuration.get("Name", ""), + guid=organization_configuration.get("Guid", ""), + customer_lockbox_enabled=organization_configuration.get( + "CustomerLockboxEnabled", False + ), + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return organization_config + async def _get_users(self): logger.info("M365 - Getting users...") users = {} @@ -163,3 +187,9 @@ class Group(BaseModel): class Domain(BaseModel): id: str password_validity_period: int + + +class Organization(BaseModel): + name: str + guid: str + customer_lockbox_enabled: bool diff --git a/tests/providers/m365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py b/tests/providers/m365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py index 9e8ccfbc7d..81209d67a4 100644 --- a/tests/providers/m365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py +++ b/tests/providers/m365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility_test.py @@ -15,6 +15,9 @@ class Test_admincenter_groups_not_public_visibility: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_groups_not_public_visibility.admincenter_groups_not_public_visibility.admincenter_client", new=admincenter_client, @@ -40,6 +43,9 @@ class Test_admincenter_groups_not_public_visibility: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_groups_not_public_visibility.admincenter_groups_not_public_visibility.admincenter_client", new=admincenter_client, @@ -78,6 +84,9 @@ class Test_admincenter_groups_not_public_visibility: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_groups_not_public_visibility.admincenter_groups_not_public_visibility.admincenter_client", new=admincenter_client, diff --git a/tests/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled_test.py b/tests/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled_test.py new file mode 100644 index 0000000000..48d3f1e745 --- /dev/null +++ b/tests/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled_test.py @@ -0,0 +1,120 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_admincenter_organization_customer_lockbox_enabled: + def test_admincenter_no_org_config(self): + admincenter_client = mock.MagicMock() + admincenter_client.audited_tenant = "audited_tenant" + admincenter_client.audited_domain = DOMAIN + admincenter_client.organization_config = None + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled.admincenter_client", + new=admincenter_client, + ), + ): + from prowler.providers.m365.services.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled import ( + admincenter_organization_customer_lockbox_enabled, + ) + + check = admincenter_organization_customer_lockbox_enabled() + result = check.execute() + assert len(result) == 0 + + def test_admincenter_customer_lockbox_enabled(self): + admincenter_client = mock.MagicMock() + admincenter_client.audited_tenant = "audited_tenant" + admincenter_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled.admincenter_client", + new=admincenter_client, + ), + ): + from prowler.providers.m365.services.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled import ( + admincenter_organization_customer_lockbox_enabled, + ) + from prowler.providers.m365.services.admincenter.admincenter_service import ( + Organization, + ) + + admincenter_client.organization_config = Organization( + name="test-org", + guid="org-guid", + customer_lockbox_enabled=True, + ) + + check = admincenter_organization_customer_lockbox_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Customer Lockbox is enabled at organization level." + ) + assert result[0].resource == admincenter_client.organization_config.dict() + assert result[0].resource_name == "test-org" + assert result[0].resource_id == "org-guid" + assert result[0].location == "global" + + def test_admincenter_customer_lockbox_disabled(self): + admincenter_client = mock.MagicMock() + admincenter_client.audited_tenant = "audited_tenant" + admincenter_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled.admincenter_client", + new=admincenter_client, + ), + ): + from prowler.providers.m365.services.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled import ( + admincenter_organization_customer_lockbox_enabled, + ) + from prowler.providers.m365.services.admincenter.admincenter_service import ( + Organization, + ) + + admincenter_client.organization_config = Organization( + name="test-org", + guid="org-guid", + customer_lockbox_enabled=False, + ) + + check = admincenter_organization_customer_lockbox_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Customer Lockbox is not enabled at organization level." + ) + assert result[0].resource == admincenter_client.organization_config.dict() + assert result[0].resource_name == "test-org" + assert result[0].resource_id == "org-guid" + assert result[0].location == "global" diff --git a/tests/providers/m365/services/admincenter/admincenter_service_test.py b/tests/providers/m365/services/admincenter/admincenter_service_test.py index f72c14ce71..a7e50e8528 100644 --- a/tests/providers/m365/services/admincenter/admincenter_service_test.py +++ b/tests/providers/m365/services/admincenter/admincenter_service_test.py @@ -1,3 +1,4 @@ +from unittest import mock from unittest.mock import patch from prowler.providers.m365.models import M365IdentityInfo @@ -5,6 +6,7 @@ from prowler.providers.m365.services.admincenter.admincenter_service import ( AdminCenter, DirectoryRole, Group, + Organization, User, ) from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider @@ -36,6 +38,14 @@ async def mock_admincenter_get_groups(_): } +def mock_admincenter_get_organization(_): + return Organization( + guid="id-1", + name="Test", + customer_lockbox_enabled=False, + ) + + @patch( "prowler.providers.m365.services.admincenter.admincenter_service.AdminCenter._get_users", new=mock_admincenter_get_users, @@ -48,32 +58,77 @@ async def mock_admincenter_get_groups(_): "prowler.providers.m365.services.admincenter.admincenter_service.AdminCenter._get_groups", new=mock_admincenter_get_groups, ) +@patch( + "prowler.providers.m365.services.admincenter.admincenter_service.AdminCenter._get_organization_config", + new=mock_admincenter_get_organization, +) class Test_AdminCenter_Service: def test_get_client(self): - admincenter_client = AdminCenter( - set_mocked_m365_provider(identity=M365IdentityInfo(tenant_domain=DOMAIN)) - ) - assert admincenter_client.client.__class__.__name__ == "GraphServiceClient" + with ( + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + ): + admincenter_client = AdminCenter( + set_mocked_m365_provider( + identity=M365IdentityInfo(tenant_domain=DOMAIN) + ) + ) + assert admincenter_client.client.__class__.__name__ == "GraphServiceClient" def test_get_users(self): - admincenter_client = AdminCenter(set_mocked_m365_provider()) - assert len(admincenter_client.users) == 1 - assert admincenter_client.users["user-1@tenant1.es"].id == "id-1" - assert admincenter_client.users["user-1@tenant1.es"].name == "User 1" + with ( + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + ): + admincenter_client = AdminCenter(set_mocked_m365_provider()) + assert len(admincenter_client.users) == 1 + assert admincenter_client.users["user-1@tenant1.es"].id == "id-1" + assert admincenter_client.users["user-1@tenant1.es"].name == "User 1" def test_get_group_settings(self): - admincenter_client = AdminCenter(set_mocked_m365_provider()) - assert len(admincenter_client.groups) == 1 - assert admincenter_client.groups["id-1"].id == "id-1" - assert admincenter_client.groups["id-1"].name == "Test" - assert admincenter_client.groups["id-1"].visibility == "Public" + with ( + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + ): + admincenter_client = AdminCenter(set_mocked_m365_provider()) + assert len(admincenter_client.groups) == 1 + assert admincenter_client.groups["id-1"].id == "id-1" + assert admincenter_client.groups["id-1"].name == "Test" + assert admincenter_client.groups["id-1"].visibility == "Public" def test_get_directory_roles(self): - admincenter_client = AdminCenter(set_mocked_m365_provider()) - assert ( - admincenter_client.directory_roles["GlobalAdministrator"].id - == "id-directory-role" - ) - assert ( - len(admincenter_client.directory_roles["GlobalAdministrator"].members) == 0 - ) + with ( + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + ): + admincenter_client = AdminCenter(set_mocked_m365_provider()) + assert ( + admincenter_client.directory_roles["GlobalAdministrator"].id + == "id-directory-role" + ) + assert ( + len(admincenter_client.directory_roles["GlobalAdministrator"].members) + == 0 + ) + + def test_get_organization(self): + with ( + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + ): + admincenter_client = AdminCenter( + set_mocked_m365_provider( + identity=M365IdentityInfo(tenant_domain=DOMAIN) + ) + ) + assert admincenter_client.organization_config.guid == "id-1" + assert admincenter_client.organization_config.name == "Test" + assert ( + admincenter_client.organization_config.customer_lockbox_enabled is False + ) + admincenter_client.powershell.close() diff --git a/tests/providers/m365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py b/tests/providers/m365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py index 7c7cb7506b..e0537249a1 100644 --- a/tests/providers/m365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py +++ b/tests/providers/m365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py @@ -15,6 +15,9 @@ class Test_admincenter_settings_password_never_expire: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire.admincenter_client", new=admincenter_client, @@ -40,6 +43,9 @@ class Test_admincenter_settings_password_never_expire: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire.admincenter_client", new=admincenter_client, @@ -81,6 +87,9 @@ class Test_admincenter_settings_password_never_expire: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire.admincenter_client", new=admincenter_client, diff --git a/tests/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py b/tests/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py index 06f807c1c2..6b93429b3d 100644 --- a/tests/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py +++ b/tests/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py @@ -15,6 +15,9 @@ class Test_admincenter_users_admins_reduced_license_footprint: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint.admincenter_client", new=admincenter_client, @@ -40,6 +43,9 @@ class Test_admincenter_users_admins_reduced_license_footprint: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint.admincenter_client", new=admincenter_client, @@ -77,6 +83,9 @@ class Test_admincenter_users_admins_reduced_license_footprint: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint.admincenter_client", new=admincenter_client, @@ -123,6 +132,9 @@ class Test_admincenter_users_admins_reduced_license_footprint: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint.admincenter_client", new=admincenter_client, @@ -169,6 +181,9 @@ class Test_admincenter_users_admins_reduced_license_footprint: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint.admincenter_client", new=admincenter_client, diff --git a/tests/providers/m365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py b/tests/providers/m365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py index 994dbbcf49..98cb224900 100644 --- a/tests/providers/m365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py +++ b/tests/providers/m365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins_test.py @@ -15,6 +15,9 @@ class Test_admincenter_users_between_two_and_four_global_admins: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_users_between_two_and_four_global_admins.admincenter_users_between_two_and_four_global_admins.admincenter_client", new=admincenter_client, @@ -40,6 +43,9 @@ class Test_admincenter_users_between_two_and_four_global_admins: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_users_between_two_and_four_global_admins.admincenter_users_between_two_and_four_global_admins.admincenter_client", new=admincenter_client, @@ -91,6 +97,9 @@ class Test_admincenter_users_between_two_and_four_global_admins: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_users_between_two_and_four_global_admins.admincenter_users_between_two_and_four_global_admins.admincenter_client", new=admincenter_client, @@ -153,6 +162,9 @@ class Test_admincenter_users_between_two_and_four_global_admins: "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_m365_provider(), ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), mock.patch( "prowler.providers.m365.services.admincenter.admincenter_users_between_two_and_four_global_admins.admincenter_users_between_two_and_four_global_admins.admincenter_client", new=admincenter_client, From 1a89d6551619ad6000308f7dcae52e25672c6184 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Wed, 21 May 2025 08:49:04 +0200 Subject: [PATCH 05/25] fix(m365powershell): add `sanitize` to `test_credentials` (#7761) Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> --- prowler/CHANGELOG.md | 1 + prowler/providers/m365/lib/powershell/m365_powershell.py | 4 ++-- tests/providers/m365/lib/powershell/m365_powershell_test.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index f71948dc1d..6ab55a6eb1 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -27,6 +27,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update CIS 4.0 for M365 provider. [(#7699)](https://github.com/prowler-cloud/prowler/pull/7699) - Update and upgrade CIS for all the providers [(#7738)](https://github.com/prowler-cloud/prowler/pull/7738) - Cover policies with conditions with SNS endpoint in `sns_topics_not_publicly_accessible`. [(#7750)](https://github.com/prowler-cloud/prowler/pull/7750) +- Fix `m365_powershell test_credentials` to use sanitized credentials. [(#7761)](https://github.com/prowler-cloud/prowler/pull/7761) - Change severity logic for `ec2_securitygroup_allow_ingress_from_internet_to_all_ports` check. [(#7764)](https://github.com/prowler-cloud/prowler/pull/7764) --- diff --git a/prowler/providers/m365/lib/powershell/m365_powershell.py b/prowler/providers/m365/lib/powershell/m365_powershell.py index 9dbda1c01e..e6a296b57c 100644 --- a/prowler/providers/m365/lib/powershell/m365_powershell.py +++ b/prowler/providers/m365/lib/powershell/m365_powershell.py @@ -87,10 +87,10 @@ class M365PowerShell(PowerShellSession): bool: True if credentials are valid and authentication succeeds, False otherwise. """ self.execute( - f'$securePassword = "{credentials.passwd}" | ConvertTo-SecureString' + f'$securePassword = "{self.sanitize(credentials.passwd)}" | ConvertTo-SecureString' ) self.execute( - f'$credential = New-Object System.Management.Automation.PSCredential("{credentials.user}", $securePassword)\n' + f'$credential = New-Object System.Management.Automation.PSCredential("{self.sanitize(credentials.user)}", $securePassword)' ) decrypted_password = self.execute( 'Write-Output "$($credential.GetNetworkCredential().Password)"' diff --git a/tests/providers/m365/lib/powershell/m365_powershell_test.py b/tests/providers/m365/lib/powershell/m365_powershell_test.py index b841602914..7bc3236433 100644 --- a/tests/providers/m365/lib/powershell/m365_powershell_test.py +++ b/tests/providers/m365/lib/powershell/m365_powershell_test.py @@ -145,7 +145,7 @@ class Testm365PowerShell: f'$securePassword = "{credentials.passwd}" | ConvertTo-SecureString' ) session.execute.assert_any_call( - f'$credential = New-Object System.Management.Automation.PSCredential("{credentials.user}", $securePassword)\n' + f'$credential = New-Object System.Management.Automation.PSCredential("{session.sanitize(credentials.user)}", $securePassword)' ) session.execute.assert_any_call( 'Write-Output "$($credential.GetNetworkCredential().Password)"' From 9b127eba93fb0d016947721f9c998a08c26895de Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Wed, 21 May 2025 09:14:45 +0200 Subject: [PATCH 06/25] feat(admincenter): add new check `admincenter_external_calendar_sharing_disabled` (#7733) Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> --- prowler/CHANGELOG.md | 1 + prowler/compliance/m365/cis_4.0_m365.json | 4 +- .../m365/lib/powershell/m365_powershell.py | 18 +++ .../__init__.py | 0 ...al_calendar_sharing_disabled.metadata.json | 32 +++++ ...nter_external_calendar_sharing_disabled.py | 52 ++++++++ .../admincenter/admincenter_service.py | 25 ++++ ...external_calendar_sharing_disabled_test.py | 120 ++++++++++++++++++ .../admincenter/admincenter_service_test.py | 29 +++++ 9 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/__init__.py create mode 100644 prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.metadata.json create mode 100644 prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.py create mode 100644 tests/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 6ab55a6eb1..9345caccf1 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Allow setting cluster name in in-cluster mode in Kubernetes. [(#7695)](https://github.com/prowler-cloud/prowler/pull/7695) - Add Prowler ThreatScore for M365 provider. [(#7692)](https://github.com/prowler-cloud/prowler/pull/7692) - Add new check `admincenter_organization_customer_lockbox_enabled`. [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732) +- Add new check `admincenter_external_calendar_sharing_disabled`. [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733) - Add GitHub provider. [(#5787)](https://github.com/prowler-cloud/prowler/pull/5787) - Add `repository_default_branch_requires_multiple_approvals` check for GitHub provider. [(#6160)](https://github.com/prowler-cloud/prowler/pull/6160) - Add `repository_default_branch_protection_enabled` check for GitHub provider. [(#6161)](https://github.com/prowler-cloud/prowler/pull/6161) diff --git a/prowler/compliance/m365/cis_4.0_m365.json b/prowler/compliance/m365/cis_4.0_m365.json index 92a48244ea..98ff0e82e1 100644 --- a/prowler/compliance/m365/cis_4.0_m365.json +++ b/prowler/compliance/m365/cis_4.0_m365.json @@ -185,7 +185,9 @@ { "Id": "1.3.3", "Description": "External calendar sharing allows an administrator to enable the ability for users to share calendars with anyone outside of the organization. Outside users will be sent a URL that can be used to view the calendar.", - "Checks": [], + "Checks": [ + "admincenter_external_calendar_sharing_disabled" + ], "Attributes": [ { "Section": "1 Microsoft 365 admin center", diff --git a/prowler/providers/m365/lib/powershell/m365_powershell.py b/prowler/providers/m365/lib/powershell/m365_powershell.py index e6a296b57c..a91e25d52d 100644 --- a/prowler/providers/m365/lib/powershell/m365_powershell.py +++ b/prowler/providers/m365/lib/powershell/m365_powershell.py @@ -685,6 +685,24 @@ class M365PowerShell(PowerShellSession): """ return self.execute("Get-TransportConfig | ConvertTo-Json", json_parse=True) + def get_sharing_policy(self) -> dict: + """ + Get Exchange Online Sharing Policy. + + Retrieves the current sharing policy settings for Exchange Online. + + Returns: + dict: Sharing policy settings in JSON format. + + Example: + >>> get_sharing_policy() + { + "Identity": "Default", + "Enabled": true + } + """ + return self.execute("Get-SharingPolicy | ConvertTo-Json", json_parse=True) + # This function is used to install the required M365 PowerShell modules in Docker containers def initialize_m365_powershell_modules(): diff --git a/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/__init__.py b/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.metadata.json b/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.metadata.json new file mode 100644 index 0000000000..64f9d85c74 --- /dev/null +++ b/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "m365", + "CheckID": "admincenter_external_calendar_sharing_disabled", + "CheckTitle": "Ensure external sharing of calendars is disabled", + "CheckType": [], + "ServiceName": "admincenter", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "Sharing Policy", + "Description": "Restrict the ability for users to share their calendars externally in Microsoft 365. This prevents users from sending calendar sharing links to external recipients, reducing information exposure.", + "Risk": "Allowing calendar sharing outside the organization can help attackers build knowledge of personnel availability, relationships, and activity patterns, aiding social engineering or targeted attacks.", + "RelatedUrl": "https://learn.microsoft.com/en-us/microsoft-365/admin/manage/share-calendars-with-external-users?view=o365-worldwide", + "Remediation": { + "Code": { + "CLI": "Set-SharingPolicy -Identity \"Default Sharing Policy\" -Enabled $False", + "NativeIaC": "", + "Other": "1. Navigate to https://admin.microsoft.com. 2. Click Settings > Org settings. 3. Select Calendar in the Services section. 4. Uncheck 'Let your users share their calendars with people outside of your organization who have Office 365 or Exchange'. 5. Click Save.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Disable external calendar sharing by setting the Default Sharing Policy to disabled.", + "Url": "https://learn.microsoft.com/en-us/microsoft-365/admin/manage/share-calendars-with-external-users?view=o365-worldwide" + } + }, + "Categories": [ + "e5" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.py b/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.py new file mode 100644 index 0000000000..ff2af49aca --- /dev/null +++ b/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.py @@ -0,0 +1,52 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.admincenter.admincenter_client import ( + admincenter_client, +) + + +class admincenter_external_calendar_sharing_disabled(Check): + """ + Ensure that external calendar sharing is disabled for the organization. + + Disabling external calendar sharing restricts the ability for users to share their + calendars externally in Microsoft 365. This prevents users from sending calendar + sharing links to external recipients, reducing information exposure. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """ + Execute the check for external calendar sharing in Microsoft 365. + + This method checks if external calendar sharing is disabled in the organization configuration. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + sharing_policy = admincenter_client.sharing_policy + if sharing_policy: + report = CheckReportM365( + metadata=self.metadata(), + resource=sharing_policy, + resource_name=sharing_policy.name, + resource_id=sharing_policy.guid, + ) + report.status = "FAIL" + report.status_extended = ( + "External calendar sharing is enabled at the organization level." + ) + + if not sharing_policy.enabled: + report.status = "PASS" + report.status_extended = ( + "External calendar sharing is disabled at the organization level." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/admincenter/admincenter_service.py b/prowler/providers/m365/services/admincenter/admincenter_service.py index 09c534edf6..bb1cad48ba 100644 --- a/prowler/providers/m365/services/admincenter/admincenter_service.py +++ b/prowler/providers/m365/services/admincenter/admincenter_service.py @@ -12,9 +12,11 @@ class AdminCenter(M365Service): def __init__(self, provider: M365Provider): super().__init__(provider) self.organization_config = None + self.sharing_policy = None if self.powershell: self.powershell.connect_exchange_online() self.organization_config = self._get_organization_config() + self.sharing_policy = self._get_sharing_policy() self.powershell.close() loop = get_event_loop() @@ -53,6 +55,23 @@ class AdminCenter(M365Service): ) return organization_config + def _get_sharing_policy(self): + logger.info("M365 - Getting sharing policy...") + sharing_policy = None + try: + sharing_policy_data = self.powershell.get_sharing_policy() + if sharing_policy_data: + sharing_policy = SharingPolicy( + name=sharing_policy_data.get("Name", ""), + guid=sharing_policy_data.get("Guid", ""), + enabled=sharing_policy_data.get("Enabled", False), + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return sharing_policy + async def _get_users(self): logger.info("M365 - Getting users...") users = {} @@ -193,3 +212,9 @@ class Organization(BaseModel): name: str guid: str customer_lockbox_enabled: bool + + +class SharingPolicy(BaseModel): + name: str + guid: str + enabled: bool diff --git a/tests/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled_test.py b/tests/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled_test.py new file mode 100644 index 0000000000..0c76f2f696 --- /dev/null +++ b/tests/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled_test.py @@ -0,0 +1,120 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_admincenter_external_calendar_sharing_disabled: + def test_admincenter_no_sharing_policy(self): + admincenter_client = mock.MagicMock() + admincenter_client.audited_tenant = "audited_tenant" + admincenter_client.audited_domain = DOMAIN + admincenter_client.sharing_policy = None + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled.admincenter_client", + new=admincenter_client, + ), + ): + from prowler.providers.m365.services.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled import ( + admincenter_external_calendar_sharing_disabled, + ) + + check = admincenter_external_calendar_sharing_disabled() + result = check.execute() + assert len(result) == 0 + + def test_admincenter_calendar_sharing_disabled(self): + admincenter_client = mock.MagicMock() + admincenter_client.audited_tenant = "audited_tenant" + admincenter_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled.admincenter_client", + new=admincenter_client, + ), + ): + from prowler.providers.m365.services.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled import ( + admincenter_external_calendar_sharing_disabled, + ) + from prowler.providers.m365.services.admincenter.admincenter_service import ( + SharingPolicy, + ) + + admincenter_client.sharing_policy = SharingPolicy( + name="test-org", + guid="org-guid", + enabled=False, + ) + + check = admincenter_external_calendar_sharing_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "External calendar sharing is disabled at the organization level." + ) + assert result[0].resource == admincenter_client.sharing_policy.dict() + assert result[0].resource_name == "test-org" + assert result[0].resource_id == "org-guid" + assert result[0].location == "global" + + def test_admincenter_calendar_sharing_enabled(self): + admincenter_client = mock.MagicMock() + admincenter_client.audited_tenant = "audited_tenant" + admincenter_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + mock.patch( + "prowler.providers.m365.services.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled.admincenter_client", + new=admincenter_client, + ), + ): + from prowler.providers.m365.services.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled import ( + admincenter_external_calendar_sharing_disabled, + ) + from prowler.providers.m365.services.admincenter.admincenter_service import ( + SharingPolicy, + ) + + admincenter_client.sharing_policy = SharingPolicy( + name="test-org", + guid="org-guid", + enabled=True, + ) + + check = admincenter_external_calendar_sharing_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "External calendar sharing is enabled at the organization level." + ) + assert result[0].resource == admincenter_client.sharing_policy.dict() + assert result[0].resource_name == "test-org" + assert result[0].resource_id == "org-guid" + assert result[0].location == "global" diff --git a/tests/providers/m365/services/admincenter/admincenter_service_test.py b/tests/providers/m365/services/admincenter/admincenter_service_test.py index a7e50e8528..287c91d661 100644 --- a/tests/providers/m365/services/admincenter/admincenter_service_test.py +++ b/tests/providers/m365/services/admincenter/admincenter_service_test.py @@ -7,6 +7,7 @@ from prowler.providers.m365.services.admincenter.admincenter_service import ( DirectoryRole, Group, Organization, + SharingPolicy, User, ) from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider @@ -46,6 +47,14 @@ def mock_admincenter_get_organization(_): ) +def mock_admincenter_get_sharing_policy(_): + return SharingPolicy( + guid="id-1", + name="Test", + enabled=False, + ) + + @patch( "prowler.providers.m365.services.admincenter.admincenter_service.AdminCenter._get_users", new=mock_admincenter_get_users, @@ -62,6 +71,10 @@ def mock_admincenter_get_organization(_): "prowler.providers.m365.services.admincenter.admincenter_service.AdminCenter._get_organization_config", new=mock_admincenter_get_organization, ) +@patch( + "prowler.providers.m365.services.admincenter.admincenter_service.AdminCenter._get_sharing_policy", + new=mock_admincenter_get_sharing_policy, +) class Test_AdminCenter_Service: def test_get_client(self): with ( @@ -132,3 +145,19 @@ class Test_AdminCenter_Service: admincenter_client.organization_config.customer_lockbox_enabled is False ) admincenter_client.powershell.close() + + def test_get_sharing_policy(self): + with ( + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + ), + ): + admincenter_client = AdminCenter( + set_mocked_m365_provider( + identity=M365IdentityInfo(tenant_domain=DOMAIN) + ) + ) + assert admincenter_client.sharing_policy.guid == "id-1" + assert admincenter_client.sharing_policy.name == "Test" + assert admincenter_client.sharing_policy.enabled is False + admincenter_client.powershell.close() From 2a61610fecbb823e622ce981d13a1f57e23435a1 Mon Sep 17 00:00:00 2001 From: Prowler Bot Date: Wed, 21 May 2025 10:29:08 +0200 Subject: [PATCH 07/25] chore(regions_update): Changes in regions for AWS services (#7774) Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com> --- .../providers/aws/aws_regions_by_service.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/prowler/providers/aws/aws_regions_by_service.json b/prowler/providers/aws/aws_regions_by_service.json index da9bba3923..43b4bb7b47 100644 --- a/prowler/providers/aws/aws_regions_by_service.json +++ b/prowler/providers/aws/aws_regions_by_service.json @@ -3460,7 +3460,8 @@ "regions": { "aws": [ "us-east-1", - "us-east-2" + "us-east-2", + "us-west-2" ], "aws-cn": [], "aws-us-gov": [] @@ -4207,6 +4208,7 @@ "entityresolution": { "regions": { "aws": [ + "af-south-1", "ap-northeast-1", "ap-northeast-2", "ap-southeast-1", @@ -4737,6 +4739,7 @@ "ap-southeast-3", "ap-southeast-4", "ap-southeast-5", + "ap-southeast-7", "ca-central-1", "ca-west-1", "eu-central-1", @@ -4750,6 +4753,7 @@ "il-central-1", "me-central-1", "me-south-1", + "mx-central-1", "sa-east-1", "us-east-1", "us-east-2", @@ -4781,6 +4785,7 @@ "ap-southeast-3", "ap-southeast-4", "ap-southeast-5", + "ap-southeast-7", "ca-central-1", "ca-west-1", "eu-central-1", @@ -4794,6 +4799,7 @@ "il-central-1", "me-central-1", "me-south-1", + "mx-central-1", "sa-east-1", "us-east-1", "us-east-2", @@ -4824,6 +4830,7 @@ "ap-southeast-2", "ap-southeast-3", "ap-southeast-5", + "ap-southeast-7", "ca-central-1", "ca-west-1", "eu-central-1", @@ -4837,6 +4844,7 @@ "il-central-1", "me-central-1", "me-south-1", + "mx-central-1", "sa-east-1", "us-east-1", "us-east-2", @@ -4868,6 +4876,7 @@ "ap-southeast-3", "ap-southeast-4", "ap-southeast-5", + "ap-southeast-7", "ca-central-1", "ca-west-1", "eu-central-1", @@ -4881,6 +4890,7 @@ "il-central-1", "me-central-1", "me-south-1", + "mx-central-1", "sa-east-1", "us-east-1", "us-east-2", @@ -5882,6 +5892,7 @@ "ap-southeast-3", "ap-southeast-4", "ap-southeast-5", + "ap-southeast-7", "ca-central-1", "ca-west-1", "eu-central-1", @@ -5895,6 +5906,7 @@ "il-central-1", "me-central-1", "me-south-1", + "mx-central-1", "sa-east-1", "us-east-1", "us-east-2", @@ -7814,6 +7826,7 @@ "ap-east-1", "ap-northeast-1", "ap-northeast-2", + "ap-northeast-3", "ap-south-1", "ap-southeast-1", "ap-southeast-2", @@ -9768,6 +9781,7 @@ "eu-west-1", "eu-west-2", "eu-west-3", + "il-central-1", "me-central-1", "me-south-1", "sa-east-1", From 4e845071309ff0e9421d38d56dadf18b819bbe99 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Wed, 21 May 2025 10:31:56 +0200 Subject: [PATCH 08/25] feat(entra): add new check `entra_users_mfa_capable` (#7734) Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> --- docs/getting-started/requirements.md | 1 + prowler/CHANGELOG.md | 1 + prowler/compliance/m365/cis_4.0_m365.json | 4 +- .../m365/services/entra/entra_service.py | 19 +++ .../entra/entra_users_mfa_capable/__init__.py | 0 .../entra_users_mfa_capable.metadata.json | 32 ++++ .../entra_users_mfa_capable.py | 45 ++++++ .../entra_users_mfa_capable_test.py | 139 ++++++++++++++++++ .../entra/microsoft365_entra_service_test.py | 6 + 9 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 prowler/providers/m365/services/entra/entra_users_mfa_capable/__init__.py create mode 100644 prowler/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable.metadata.json create mode 100644 prowler/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable.py create mode 100644 tests/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable_test.py diff --git a/docs/getting-started/requirements.md b/docs/getting-started/requirements.md index a9318228ab..29b9069013 100644 --- a/docs/getting-started/requirements.md +++ b/docs/getting-started/requirements.md @@ -247,6 +247,7 @@ Prowler for M365 requires two types of permission scopes to be set (if you want - `User.Read` (IMPORTANT: this must be set as **delegated**): Required for the sign-in. - `Sites.Read.All`: Required for SharePoint service. - `SharePointTenantSettings.Read.All`: Required for SharePoint service. + - `AuditLog.Read.All`: Required for Entra service. - **Powershell Modules Permissions**: These are set at the `M365_USER` level, so the user used to run Prowler must have one of the following roles: - `Global Reader` (recommended): this allows you to read all roles needed. diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 9345caccf1..5f1416caa8 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update the compliance list supported for each provider from docs. [(#7694)](https://github.com/prowler-cloud/prowler/pull/7694) - Allow setting cluster name in in-cluster mode in Kubernetes. [(#7695)](https://github.com/prowler-cloud/prowler/pull/7695) - Add Prowler ThreatScore for M365 provider. [(#7692)](https://github.com/prowler-cloud/prowler/pull/7692) +- Add new check `entra_users_mfa_capable`. [(#7734)](https://github.com/prowler-cloud/prowler/pull/7734) - Add new check `admincenter_organization_customer_lockbox_enabled`. [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732) - Add new check `admincenter_external_calendar_sharing_disabled`. [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733) - Add GitHub provider. [(#5787)](https://github.com/prowler-cloud/prowler/pull/5787) diff --git a/prowler/compliance/m365/cis_4.0_m365.json b/prowler/compliance/m365/cis_4.0_m365.json index 98ff0e82e1..3a6e11b0bb 100644 --- a/prowler/compliance/m365/cis_4.0_m365.json +++ b/prowler/compliance/m365/cis_4.0_m365.json @@ -1421,7 +1421,9 @@ { "Id": "5.2.3.4", "Description": "Microsoft defines Multifactor authentication capable as being registered and enabled for a strong authentication method. The method must also be allowed by the authentication methods policy.Ensure all member users are `MFA capable`.", - "Checks": [], + "Checks": [ + "entra_users_mfa_capable" + ], "Attributes": [ { "Section": "5 Microsoft Entra admin center", diff --git a/prowler/providers/m365/services/entra/entra_service.py b/prowler/providers/m365/services/entra/entra_service.py index d752562023..3e548afdba 100644 --- a/prowler/providers/m365/services/entra/entra_service.py +++ b/prowler/providers/m365/services/entra/entra_service.py @@ -377,6 +377,19 @@ class Entra(M365Service): for member in members: user_roles_map.setdefault(member.id, []).append(role_template_id) + try: + registration_details_list = ( + await self.client.reports.authentication_methods.user_registration_details.get() + ) + registration_details = { + detail.id: detail for detail in registration_details_list.value + } + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + registration_details = {} + for user in users_list.value: users[user.id] = User( id=user.id, @@ -385,6 +398,11 @@ class Entra(M365Service): True if (user.on_premises_sync_enabled) else False ), directory_roles_ids=user_roles_map.get(user.id, []), + is_mfa_capable=( + registration_details.get(user.id, {}).is_mfa_capable + if registration_details.get(user.id, None) is not None + else False + ), ) except Exception as error: logger.error( @@ -563,6 +581,7 @@ class User(BaseModel): name: str on_premises_sync_enabled: bool directory_roles_ids: List[str] = [] + is_mfa_capable: bool = False class InvitationsFrom(Enum): diff --git a/prowler/providers/m365/services/entra/entra_users_mfa_capable/__init__.py b/prowler/providers/m365/services/entra/entra_users_mfa_capable/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable.metadata.json b/prowler/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable.metadata.json new file mode 100644 index 0000000000..594138cccc --- /dev/null +++ b/prowler/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "m365", + "CheckID": "entra_users_mfa_capable", + "CheckTitle": "Ensure all users are MFA capable", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "critical", + "ResourceType": "Conditional Access Policy", + "Description": "Ensure all users are being registered and enabled for multifactor authentication.", + "Risk": "Users who are not MFA capable are more vulnerable to account compromise, as they may rely solely on single-factor authentication (typically a password), which can be easily phished or cracked.", + "RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/authentication/concept-mfa-howitworks", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "Remediation steps will depend on the status of the personnel in question or configuration of Conditional Access policies. Administrators should review each user identified on a case-by-case basis.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure all member users are MFA capable by registering and enabling a strong authentication method that complies with the organization's authentication policy. Regularly review user status to detect gaps in MFA deployment and correct misconfigurations.", + "Url": "https://learn.microsoft.com/en-us/entra/identity/authentication/concept-mfa-howitworks" + } + }, + "Categories": [ + "e3" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable.py b/prowler/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable.py new file mode 100644 index 0000000000..6196f1e9cc --- /dev/null +++ b/prowler/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable.py @@ -0,0 +1,45 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.entra.entra_client import entra_client + + +class entra_users_mfa_capable(Check): + """ + Ensure all users are MFA capable. + + This check verifies if users are MFA capable. + + The check fails if any user is not MFA capable. + """ + + def execute(self) -> List[CheckReportM365]: + """ + Execute the admin MFA capable check for all users. + + Iterates over the users retrieved from the Entra client and generates a report + indicating if users are MFA capable. + + Returns: + List[CheckReportM365]: A list containing a single report with the result of the check. + """ + findings = [] + + for user in entra_client.users.values(): + report = CheckReportM365( + metadata=self.metadata(), + resource={}, + resource_name="Users", + resource_id="users", + ) + + if not user.is_mfa_capable: + report.status = "FAIL" + report.status_extended = f"User {user.name} is not MFA capable." + else: + report.status = "PASS" + report.status_extended = f"User {user.name} is MFA capable." + + findings.append(report) + + return findings diff --git a/tests/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable_test.py b/tests/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable_test.py new file mode 100644 index 0000000000..7cfbe5de0d --- /dev/null +++ b/tests/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable_test.py @@ -0,0 +1,139 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.m365.services.entra.entra_service import User +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_entra_users_mfa_capable: + def test_user_not_mfa_capable(self): + """User is not MFA capable: expected FAIL.""" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.services.entra.entra_users_mfa_capable.entra_users_mfa_capable.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_users_mfa_capable.entra_users_mfa_capable import ( + entra_users_mfa_capable, + ) + + user_id = str(uuid4()) + entra_client.users = { + user_id: User( + id=user_id, + name="Test User", + on_premises_sync_enabled=False, + directory_roles_ids=[], + is_mfa_capable=False, + ) + } + + check = entra_users_mfa_capable() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == "User Test User is not MFA capable." + assert result[0].resource == {} + assert result[0].resource_name == "Users" + assert result[0].resource_id == "users" + + def test_user_mfa_capable(self): + """User is MFA capable: expected PASS.""" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.services.entra.entra_users_mfa_capable.entra_users_mfa_capable.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_users_mfa_capable.entra_users_mfa_capable import ( + entra_users_mfa_capable, + ) + + user_id = str(uuid4()) + entra_client.users = { + user_id: User( + id=user_id, + name="Test User", + on_premises_sync_enabled=False, + directory_roles_ids=[], + is_mfa_capable=True, + ) + } + + check = entra_users_mfa_capable() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == "User Test User is MFA capable." + assert result[0].resource == {} + assert result[0].resource_name == "Users" + assert result[0].resource_id == "users" + + def test_multiple_users(self): + """Multiple users with different MFA capabilities: expected mixed results.""" + entra_client = mock.MagicMock + entra_client.audited_tenant = "audited_tenant" + entra_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_m365_provider(), + ), + mock.patch( + "prowler.providers.m365.services.entra.entra_users_mfa_capable.entra_users_mfa_capable.entra_client", + new=entra_client, + ), + ): + from prowler.providers.m365.services.entra.entra_users_mfa_capable.entra_users_mfa_capable import ( + entra_users_mfa_capable, + ) + + user1_id = str(uuid4()) + user2_id = str(uuid4()) + entra_client.users = { + user1_id: User( + id=user1_id, + name="Test User 1", + on_premises_sync_enabled=False, + directory_roles_ids=[], + is_mfa_capable=True, + ), + user2_id: User( + id=user2_id, + name="Test User 2", + on_premises_sync_enabled=False, + directory_roles_ids=[], + is_mfa_capable=False, + ), + } + + check = entra_users_mfa_capable() + result = check.execute() + + assert len(result) == 2 + # First user (MFA capable) + assert result[0].status == "PASS" + assert result[0].status_extended == "User Test User 1 is MFA capable." + # Second user (not MFA capable) + assert result[1].status == "FAIL" + assert result[1].status_extended == "User Test User 2 is not MFA capable." diff --git a/tests/providers/m365/services/entra/microsoft365_entra_service_test.py b/tests/providers/m365/services/entra/microsoft365_entra_service_test.py index 98fbed8e35..46e92aa1f1 100644 --- a/tests/providers/m365/services/entra/microsoft365_entra_service_test.py +++ b/tests/providers/m365/services/entra/microsoft365_entra_service_test.py @@ -121,18 +121,21 @@ async def mock_entra_get_users(_): name="User 1", directory_roles_ids=[AdminRoles.GLOBAL_ADMINISTRATOR.value], on_premises_sync_enabled=True, + is_mfa_capable=True, ), "user-2": User( id="user-2", name="User 2", directory_roles_ids=[AdminRoles.GLOBAL_ADMINISTRATOR.value], on_premises_sync_enabled=False, + is_mfa_capable=False, ), "user-3": User( id="user-3", name="User 3", directory_roles_ids=[AdminRoles.GLOBAL_ADMINISTRATOR.value], on_premises_sync_enabled=True, + is_mfa_capable=False, ), } @@ -278,12 +281,14 @@ class Test_Entra_Service: assert entra_client.users["user-1"].directory_roles_ids == [ AdminRoles.GLOBAL_ADMINISTRATOR.value ] + assert entra_client.users["user-1"].is_mfa_capable assert entra_client.users["user-1"].on_premises_sync_enabled assert entra_client.users["user-2"].id == "user-2" assert entra_client.users["user-2"].name == "User 2" assert entra_client.users["user-2"].directory_roles_ids == [ AdminRoles.GLOBAL_ADMINISTRATOR.value ] + assert not entra_client.users["user-2"].is_mfa_capable assert not entra_client.users["user-2"].on_premises_sync_enabled assert entra_client.users["user-3"].id == "user-3" assert entra_client.users["user-3"].name == "User 3" @@ -291,3 +296,4 @@ class Test_Entra_Service: AdminRoles.GLOBAL_ADMINISTRATOR.value ] assert entra_client.users["user-3"].on_premises_sync_enabled + assert not entra_client.users["user-3"].is_mfa_capable From acdf4209419bf6c2e8c34d6a6267e91fe6781286 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Wed, 21 May 2025 10:47:32 +0200 Subject: [PATCH 09/25] feat: profile page (#7780) Co-authored-by: Pablo Lara --- ui/CHANGELOG.md | 6 + ui/actions/roles/roles.ts | 32 +++++ ui/actions/users/tenants.ts | 28 ++++ ui/actions/users/users.ts | 34 ++++- ui/app/(prowler)/profile/page.tsx | 76 +++++++--- .../findings/table/delta-indicator.tsx | 2 +- .../ui/content-layout/content-layout.tsx | 4 +- ui/components/ui/user-nav/user-nav.tsx | 2 +- ui/components/users/profile/index.ts | 7 +- .../users/profile/membership-item.tsx | 27 ++++ .../users/profile/memberships-card.tsx | 44 ++++++ ui/components/users/profile/role-item.tsx | 94 +++++++++++++ ui/components/users/profile/roles-card.tsx | 41 ++++++ .../users/profile/skeleton-user-info.tsx | 132 ++++++++++-------- .../users/profile/user-basic-info-card.tsx | 90 ++++++++++++ ui/components/users/profile/user-info.tsx | 77 ---------- ui/lib/permissions.ts | 44 ++++++ ui/types/users/users.ts | 101 +++++++++++++- 18 files changed, 678 insertions(+), 163 deletions(-) create mode 100644 ui/actions/users/tenants.ts create mode 100644 ui/components/users/profile/membership-item.tsx create mode 100644 ui/components/users/profile/memberships-card.tsx create mode 100644 ui/components/users/profile/role-item.tsx create mode 100644 ui/components/users/profile/roles-card.tsx create mode 100644 ui/components/users/profile/user-basic-info-card.tsx delete mode 100644 ui/components/users/profile/user-info.tsx create mode 100644 ui/lib/permissions.ts diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 52b2818551..39a1fbca22 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to the **Prowler UI** are documented in this file. ## [v1.8.0] (Prowler v5.8.0) – Not released + +### 🚀 Added + +- New profile page with details about the user and their roles. [(#7780)](https://github.com/prowler-cloud/prowler/pull/7780) + ### 🐞 Fixes - Added validation to AWS IAM role. [(#7787)](https://github.com/prowler-cloud/prowler/pull/7787) @@ -13,6 +18,7 @@ All notable changes to the **Prowler UI** are documented in this file. ## [v1.7.0] (Prowler v5.7.0) + ### 🚀 Added - Add a new chart to show the split between passed and failed findings. [(#7680)](https://github.com/prowler-cloud/prowler/pull/7680) diff --git a/ui/actions/roles/roles.ts b/ui/actions/roles/roles.ts index e63558d1b9..d0dd07bd9a 100644 --- a/ui/actions/roles/roles.ts +++ b/ui/actions/roles/roles.ts @@ -73,6 +73,38 @@ export const getRoleInfoById = async (roleId: string) => { } }; +export const getRolesByIds = async (roleIds: string[]) => { + if (!roleIds || roleIds.length === 0) { + return { data: [] }; + } + + const headers = await getAuthHeaders({ contentType: false }); + const url = new URL(`${apiBaseUrl}/roles`); + + // Add filter for role IDs + url.searchParams.append("filter[id__in]", roleIds.join(",")); + // Request all results on a single page with reasonable size + url.searchParams.append("page[size]", "100"); + + try { + const response = await fetch(url.toString(), { + method: "GET", + headers, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch roles: ${response.statusText}`); + } + + const data = await response.json(); + return parseStringify(data); + } catch (error) { + // eslint-disable-next-line no-console + console.error("Error fetching roles by IDs:", error); + return { data: [] }; + } +}; + export const addRole = async (formData: FormData) => { const headers = await getAuthHeaders({ contentType: true }); diff --git a/ui/actions/users/tenants.ts b/ui/actions/users/tenants.ts new file mode 100644 index 0000000000..0901345852 --- /dev/null +++ b/ui/actions/users/tenants.ts @@ -0,0 +1,28 @@ +import { revalidatePath } from "next/cache"; + +import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib/helper"; + +export const getAllTenants = async () => { + const headers = await getAuthHeaders({ contentType: false }); + const url = new URL(`${apiBaseUrl}/tenants`); + + try { + const response = await fetch(url.toString(), { + method: "GET", + headers, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch tenants data: ${response.statusText}`); + } + + const data = await response.json(); + const parsedData = parseStringify(data); + revalidatePath("/profile"); + return parsedData; + } catch (error) { + // eslint-disable-next-line no-console + console.error("Error fetching tenants:", error); + return undefined; + } +}; diff --git a/ui/actions/users/users.ts b/ui/actions/users/users.ts index b826c5fcc7..c47c58ebbf 100644 --- a/ui/actions/users/users.ts +++ b/ui/actions/users/users.ts @@ -184,9 +184,9 @@ export const deleteUser = async (formData: FormData) => { } }; -export const getProfileInfo = async () => { +export const getUserInfo = async () => { const headers = await getAuthHeaders({ contentType: false }); - const url = new URL(`${apiBaseUrl}/users/me`); + const url = new URL(`${apiBaseUrl}/users/me?include=roles`); try { const response = await fetch(url.toString(), { @@ -208,3 +208,33 @@ export const getProfileInfo = async () => { return undefined; } }; + +export const getUserMemberships = async (userId: string) => { + if (!userId) { + return { data: [] }; + } + + const headers = await getAuthHeaders({ contentType: false }); + const url = new URL(`${apiBaseUrl}/users/${userId}/memberships`); + url.searchParams.append("page[size]", "100"); + + try { + const response = await fetch(url.toString(), { + method: "GET", + headers, + }); + + if (!response.ok) { + throw new Error( + `Failed to fetch user memberships: ${response.statusText}`, + ); + } + + const data = await response.json(); + return parseStringify(data); + } catch (error) { + // eslint-disable-next-line no-console + console.error("Error fetching user memberships:", error); + return { data: [] }; + } +}; diff --git a/ui/app/(prowler)/profile/page.tsx b/ui/app/(prowler)/profile/page.tsx index 6ea66ded18..b78d9e8439 100644 --- a/ui/app/(prowler)/profile/page.tsx +++ b/ui/app/(prowler)/profile/page.tsx @@ -1,36 +1,74 @@ import React, { Suspense } from "react"; -import { getProfileInfo } from "@/actions/users/users"; +import { getAllTenants } from "@/actions/users/tenants"; +import { getUserInfo } from "@/actions/users/users"; +import { getUserMemberships } from "@/actions/users/users"; import { ContentLayout } from "@/components/ui"; -import { SkeletonUserInfo } from "@/components/users/profile"; -import { UserInfo } from "@/components/users/profile/user-info"; -import { UserProfileProps } from "@/types"; +import { UserBasicInfoCard } from "@/components/users/profile"; +import { MembershipsCard } from "@/components/users/profile/memberships-card"; +import { RolesCard } from "@/components/users/profile/roles-card"; +import { SkeletonUserInfo } from "@/components/users/profile/skeleton-user-info"; +import { RoleDetail, TenantDetailData } from "@/types/users/users"; export default async function Profile() { return ( -
-
-
-
- }> - - -
-
-
+
+ }> + +
); } const SSRDataUser = async () => { - const userProfile: UserProfileProps = await getProfileInfo(); + const userProfile = await getUserInfo(); + if (!userProfile?.data) { + return null; + } + + const roleDetails = + userProfile.included?.filter((item: any) => item.type === "roles") || []; + + const roleDetailsMap = roleDetails.reduce( + (acc: Record, role: RoleDetail) => { + acc[role.id] = role; + return acc; + }, + {} as Record, + ); + + const memberships = await getUserMemberships(userProfile.data.id); + const tenants = await getAllTenants(); + + const tenantsMap = tenants?.data?.reduce( + (acc: Record, tenant: TenantDetailData) => { + acc[tenant.id] = tenant; + return acc; + }, + {} as Record, + ); + + const userMembershipIds = + userProfile.data.relationships?.memberships?.data?.map( + (membership: { id: string }) => membership.id, + ) || []; + + const userTenant = tenants?.data?.find((tenant: TenantDetailData) => + tenant.relationships?.memberships?.data?.some( + (membership: { id: string }) => userMembershipIds.includes(membership.id), + ), + ); return ( - <> -

User Info

- - +
+ + + +
); }; diff --git a/ui/components/findings/table/delta-indicator.tsx b/ui/components/findings/table/delta-indicator.tsx index 3336c18702..86f89b02a6 100644 --- a/ui/components/findings/table/delta-indicator.tsx +++ b/ui/components/findings/table/delta-indicator.tsx @@ -37,7 +37,7 @@ export const DeltaIndicator = ({ delta }: DeltaIndicatorProps) => { delta === "new" ? "bg-system-severity-high" : delta === "changed" - ? "bg-system-severity-medium" + ? "bg-system-severity-low" : "bg-gray-500", )} /> diff --git a/ui/components/ui/content-layout/content-layout.tsx b/ui/components/ui/content-layout/content-layout.tsx index b073c1c477..f1fdb13a57 100644 --- a/ui/components/ui/content-layout/content-layout.tsx +++ b/ui/components/ui/content-layout/content-layout.tsx @@ -1,6 +1,6 @@ import { Suspense, use } from "react"; -import { getProfileInfo } from "@/actions/users/users"; +import { getUserInfo } from "@/actions/users/users"; import { Navbar } from "../nav-bar/navbar"; import { SkeletonContentLayout } from "./skeleton-content-layout"; @@ -11,7 +11,7 @@ interface ContentLayoutProps { } export function ContentLayout({ title, icon, children }: ContentLayoutProps) { - const user = use(getProfileInfo()); + const user = use(getUserInfo()); return ( <> diff --git a/ui/components/ui/user-nav/user-nav.tsx b/ui/components/ui/user-nav/user-nav.tsx index b5023a9ece..58ff0b9abf 100644 --- a/ui/components/ui/user-nav/user-nav.tsx +++ b/ui/components/ui/user-nav/user-nav.tsx @@ -77,7 +77,7 @@ export const UserNav = ({ user }: { user?: UserProfileProps }) => { - + Account diff --git a/ui/components/users/profile/index.ts b/ui/components/users/profile/index.ts index 07aeca9b89..02cdd56c9d 100644 --- a/ui/components/users/profile/index.ts +++ b/ui/components/users/profile/index.ts @@ -1,2 +1,5 @@ -export * from "./skeleton-user-info"; -export * from "./user-info"; +export * from "./membership-item"; +export * from "./memberships-card"; +export * from "./role-item"; +export * from "./roles-card"; +export * from "./user-basic-info-card"; diff --git a/ui/components/users/profile/membership-item.tsx b/ui/components/users/profile/membership-item.tsx new file mode 100644 index 0000000000..f9d28a9f7d --- /dev/null +++ b/ui/components/users/profile/membership-item.tsx @@ -0,0 +1,27 @@ +import { Chip } from "@nextui-org/react"; + +import { DateWithTime } from "@/components/ui/entities"; +import { MembershipDetailData } from "@/types/users/users"; + +export const MembershipItem = ({ + membership, + tenantName, +}: { + membership: MembershipDetailData; + tenantName: string; +}) => ( +
+
+
+ + {membership.attributes.role} + +

{tenantName}

+
+
+
+ Joined on: + +
+
+); diff --git a/ui/components/users/profile/memberships-card.tsx b/ui/components/users/profile/memberships-card.tsx new file mode 100644 index 0000000000..c752882156 --- /dev/null +++ b/ui/components/users/profile/memberships-card.tsx @@ -0,0 +1,44 @@ +import { Card, CardBody, CardHeader } from "@nextui-org/react"; + +import { MembershipDetailData, TenantDetailData } from "@/types/users/users"; + +import { MembershipItem } from "./membership-item"; + +export const MembershipsCard = ({ + memberships, + tenantsMap, +}: { + memberships: MembershipDetailData[]; + tenantsMap: Record; +}) => { + return ( + + +
+

Organizations

+

+ Organizations this user is associated with +

+
+
+ + {memberships.length === 0 ? ( +
No memberships found.
+ ) : ( +
+ {memberships.map((membership) => ( + + ))} +
+ )} +
+
+ ); +}; diff --git a/ui/components/users/profile/role-item.tsx b/ui/components/users/profile/role-item.tsx new file mode 100644 index 0000000000..27c056843c --- /dev/null +++ b/ui/components/users/profile/role-item.tsx @@ -0,0 +1,94 @@ +"use client"; + +import { Chip } from "@nextui-org/react"; +import { Ban, Check } from "lucide-react"; +import { useState } from "react"; + +import { CustomButton } from "@/components/ui/custom/custom-button"; +import { getRolePermissions } from "@/lib/permissions"; +import { RoleData, RoleDetail } from "@/types/users/users"; + +interface PermissionItemProps { + enabled: boolean; + label: string; +} + +export const PermissionIcon = ({ enabled }: { enabled: boolean }) => ( + + {enabled ? : } + +); + +const PermissionItem = ({ enabled, label }: PermissionItemProps) => ( +
+ + {label} +
+); + +export const RoleItem = ({ + role, + roleDetail, +}: { + role: RoleData; + roleDetail?: RoleDetail; +}) => { + const [isExpanded, setIsExpanded] = useState(false); + + if (!roleDetail) { + return ( + + {role.id} + + ); + } + + const { attributes } = roleDetail; + const roleName = attributes?.name || role.id; + const permissionState = attributes?.permission_state || ""; + const detailsId = `role-details-${role.id}`; + + const permissions = getRolePermissions(attributes); + + return ( +
+
+
+ + {roleName} + + + {permissionState} + +
+ + setIsExpanded(!isExpanded)} + className="text-blue-500" + color="transparent" + size="sm" + > + {isExpanded ? "Hide details" : "Show details"} + +
+ + {isExpanded && ( +
+
+ {permissions.map(({ key, label, enabled }) => ( + + ))} +
+
+ )} +
+ ); +}; diff --git a/ui/components/users/profile/roles-card.tsx b/ui/components/users/profile/roles-card.tsx new file mode 100644 index 0000000000..5135c41528 --- /dev/null +++ b/ui/components/users/profile/roles-card.tsx @@ -0,0 +1,41 @@ +import { Card, CardBody, CardHeader } from "@nextui-org/react"; + +import { RoleData, RoleDetail } from "@/types/users/users"; + +import { RoleItem } from "./role-item"; + +export const RolesCard = ({ + roles, + roleDetails, +}: { + roles: RoleData[]; + roleDetails: Record; +}) => { + return ( + + +
+

Active roles

+

+ Roles assigned to this user account +

+
+
+ + {roles.length === 0 ? ( +
No roles assigned.
+ ) : ( +
+ {roles.map((role) => ( + + ))} +
+ )} +
+
+ ); +}; diff --git a/ui/components/users/profile/skeleton-user-info.tsx b/ui/components/users/profile/skeleton-user-info.tsx index 6bd52c322e..fec5f68f19 100644 --- a/ui/components/users/profile/skeleton-user-info.tsx +++ b/ui/components/users/profile/skeleton-user-info.tsx @@ -1,64 +1,82 @@ -import { Card, CardBody, CardHeader, Skeleton } from "@nextui-org/react"; +import { Card, CardBody, Skeleton } from "@nextui-org/react"; export const SkeletonUserInfo = () => { - const rows = 4; - return ( - - - -
-
-
- -
- {/* Header Skeleton */} -
- -
-
- -
-
- -
-
- -
-
-
- - {/* Row Skeletons */} - {Array.from({ length: rows }).map((_, index) => ( -
- {/* Provider Name */} -
- -
-
- -
-
-
- {/* Percent Passing */} - -
-
- {/* Failing Checks */} - -
-
- {/* Total Resources */} - -
+
+ {/* User Information */} + + +
+ {/* Name */} +
+

Name:

+ +
- ))} -
-
-
+ {/* Email */} +
+

Email:

+ +
+
+
+ {/* Company */} +
+

Company:

+ +
+
+
+ {/* Date Joined */} +
+

+ Date Joined: +

+ +
+
+
+ {/* Tenant ID */} +
+

+ Tenant ID: +

+ +
+
+
+
+ + + + {/* Roles */} + + +

Roles

+
+ {[1, 2, 3].map((i) => ( + +
+
+ ))} +
+
+
+ + {/* Memberships */} + + +

Memberships

+
+ {[1, 2].map((i) => ( + +
+
+ ))} +
+
+
+
); }; diff --git a/ui/components/users/profile/user-basic-info-card.tsx b/ui/components/users/profile/user-basic-info-card.tsx new file mode 100644 index 0000000000..5c4ea0bbbd --- /dev/null +++ b/ui/components/users/profile/user-basic-info-card.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { Card, CardBody, Divider, Tooltip } from "@nextui-org/react"; +import { CircleUserRound } from "lucide-react"; +import { useState } from "react"; + +import { CopyIcon, DoneIcon } from "@/components/icons"; +import { CustomButton } from "@/components/ui/custom/custom-button"; +import { DateWithTime } from "@/components/ui/entities"; +import { UserDataWithRoles } from "@/types/users/users"; + +const TenantIdCopy = ({ id }: { id: string }) => { + const [copied, setCopied] = useState(false); + + const handleCopyTenantId = () => { + navigator.clipboard.writeText(id); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+

+ Active organization ID: +

+
+ + + + {id} + + {copied ? ( + + ) : ( + + )} + + +
+
+ ); +}; + +export const UserBasicInfoCard = ({ + user, + tenantId, +}: { + user: UserDataWithRoles; + tenantId: string; +}) => { + const { name, email, company_name, date_joined } = user.attributes; + + return ( + + +
+ +
+

Name:

+ {name} +
+
+

Email:

+ {email} +
+
+

Company:

+ {company_name} +
+
+

+ Date Joined: +

+ + + +
+ + +
+
+
+ ); +}; diff --git a/ui/components/users/profile/user-info.tsx b/ui/components/users/profile/user-info.tsx deleted file mode 100644 index ac44770a04..0000000000 --- a/ui/components/users/profile/user-info.tsx +++ /dev/null @@ -1,77 +0,0 @@ -"use client"; - -import { Card, CardBody } from "@nextui-org/react"; - -import { DateWithTime } from "@/components/ui/entities"; -import { UserProfileProps } from "@/types"; - -export const UserInfo = ({ - user, -}: { - user: UserProfileProps["data"] | null; -}) => { - if (!user || !user.attributes) { - return ( - - -
-
-

Name:

- - -
-
-

Email:

- - -
-
-

Company:

- - -
-
-

- Date Joined: -

- - -
-
-
- Unable to load user information. -
- Please check your API connection. -
-
-
- ); - } - - const { name, email, company_name, date_joined } = user.attributes; - - return ( - - -
-
-

Name:

- {name} -
-
-

Email:

- {email} -
-
-

Company:

- {company_name} -
-
-

- Date Joined: -

- - - -
-
-
-
- ); -}; diff --git a/ui/lib/permissions.ts b/ui/lib/permissions.ts new file mode 100644 index 0000000000..6e0862637d --- /dev/null +++ b/ui/lib/permissions.ts @@ -0,0 +1,44 @@ +import { RolePermissionAttributes } from "@/types/users/users"; + +/** + * Get the permissions for a user role + * @param attributes - The attributes of the user role + * @returns The permissions for the user role + */ +export const getRolePermissions = (attributes: RolePermissionAttributes) => { + const permissions = [ + { + key: "manage_users", + label: "Manage Users", + enabled: attributes.manage_users, + }, + { + key: "manage_account", + label: "Manage Account", + enabled: attributes.manage_account, + }, + { + key: "manage_providers", + label: "Manage Providers", + enabled: attributes.manage_providers, + }, + { + key: "manage_scans", + label: "Manage Scans", + enabled: attributes.manage_scans, + }, + + { + key: "manage_integrations", + label: "Manage Integrations", + enabled: attributes.manage_integrations, + }, + { + key: "unlimited_visibility", + label: "Unlimited Visibility", + enabled: attributes.unlimited_visibility, + }, + ]; + + return permissions; +}; diff --git a/ui/types/users/users.ts b/ui/types/users/users.ts index 61a92ae70e..993f1bf286 100644 --- a/ui/types/users/users.ts +++ b/ui/types/users/users.ts @@ -5,7 +5,7 @@ export interface UserAttributes { date_joined: string; } -export interface Membership { +export interface MembershipData { type: string; id: string; } @@ -17,7 +17,7 @@ export interface MembershipMeta { export interface UserRelationships { memberships: { meta: MembershipMeta; - data: Membership[]; + data: MembershipData[]; }; } @@ -50,3 +50,100 @@ export interface TokenData { export interface SignInResponse { data: TokenData; } + +export interface RoleData { + type: "roles"; + id: string; +} + +export type PermissionKey = + | "manage_users" + | "manage_account" + | "manage_providers" + | "manage_scans" + | "manage_integrations" + | "unlimited_visibility"; + +export type RolePermissionAttributes = Pick< + RoleDetail["attributes"], + PermissionKey +>; + +export interface RoleDetail { + id: string; + type: "roles"; + attributes: { + name: string; + manage_users: boolean; + manage_account: boolean; + manage_providers: boolean; + manage_scans: boolean; + manage_integrations: boolean; + unlimited_visibility: boolean; + permission_state?: string; + inserted_at?: string; + updated_at?: string; + }; +} + +export interface MembershipDetailData { + id: string; + type: "memberships"; + attributes: { + role: string; + date_joined: string; + [key: string]: any; + }; + relationships: { + tenant: { + data: { + type: string; + id: string; + }; + }; + [key: string]: any; + }; +} + +export interface UserDataWithRoles + extends Omit { + attributes: UserAttributes & { + role?: { + name: string; + }; + }; + relationships: { + memberships: UserRelationships["memberships"]; + roles?: { + meta: { + count: number; + }; + data: RoleData[]; + }; + }; +} + +export interface UserInfoProps { + user: UserDataWithRoles | null; + roleDetails?: RoleDetail[]; + membershipDetails?: MembershipDetailData[]; +} + +export interface TenantDetailData { + type: string; + id: string; + attributes: { + name: string; + }; + relationships: { + memberships: { + meta: { + count: number; + }; + data: Array<{ + type: string; + id: string; + }>; + }; + }; +} From 021e243adafbd3e5b26612bcb5434648fa9997a9 Mon Sep 17 00:00:00 2001 From: Sergio Garcia Date: Wed, 21 May 2025 10:49:18 +0200 Subject: [PATCH 10/25] feat(kubernetes): support `HTTPS_PROXY` and `K8S_SKIP_TLS_VERIFY` (#7720) --- docs/tutorials/kubernetes/misc.md | 20 +++++++ .../kubernetes/kubernetes_provider.py | 31 +++++++++- .../kubernetes/kubernetes_provider_test.py | 58 ++++++++++++++++++- 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/kubernetes/misc.md b/docs/tutorials/kubernetes/misc.md index 2554bc712b..0d1edb936b 100644 --- a/docs/tutorials/kubernetes/misc.md +++ b/docs/tutorials/kubernetes/misc.md @@ -21,3 +21,23 @@ To specify the namespace(s) to be scanned, use the `--namespace` flag followed b ```console prowler --namespace namespace1 namespace2 ``` + +## Proxy and TLS Verification + +If your Kubernetes cluster is only accessible via an internal proxy, Prowler will respect the `HTTPS_PROXY` or `https_proxy` environment variable: + +```console +export HTTPS_PROXY=http://my.internal.proxy:8888 +prowler kubernetes ... +``` + +If you need to skip TLS verification for internal proxies, you can set the `K8S_SKIP_TLS_VERIFY` environment variable: + +```console +export K8S_SKIP_TLS_VERIFY=true +prowler kubernetes ... +``` + +This will allow Prowler to connect to the cluster even if the proxy uses a self-signed certificate. + +These environment variables are supported both when using an external `kubeconfig` and in in-cluster mode. diff --git a/prowler/providers/kubernetes/kubernetes_provider.py b/prowler/providers/kubernetes/kubernetes_provider.py index 160ef4e897..20b019780a 100644 --- a/prowler/providers/kubernetes/kubernetes_provider.py +++ b/prowler/providers/kubernetes/kubernetes_provider.py @@ -2,6 +2,7 @@ import os from typing import Union from colorama import Fore, Style +from kubernetes.client import ApiClient, Configuration from kubernetes.client.exceptions import ApiException from kubernetes.config.config_exception import ConfigException from requests.exceptions import Timeout @@ -286,9 +287,21 @@ class KubernetesProvider(Provider): "user": "service-account-name", }, } - return KubernetesSession( - api_client=client.ApiClient(), context=context + # Ensure proxy settings are respected + configuration = Configuration.get_default_copy() + proxy = os.environ.get("HTTPS_PROXY") or os.environ.get( + "https_proxy" ) + if proxy: + configuration.proxy = proxy + # Prevent SSL verification issues with internal proxies + if os.environ.get("K8S_SKIP_TLS_VERIFY", "false").lower() == "true": + configuration.verify_ssl = False + + return KubernetesSession( + api_client=ApiClient(configuration), context=context + ) + if context: contexts = config.list_kube_config_contexts( config_file=kubeconfig_file @@ -302,7 +315,19 @@ class KubernetesProvider(Provider): context = config.list_kube_config_contexts( config_file=kubeconfig_file )[1] - return KubernetesSession(api_client=client.ApiClient(), context=context) + # Ensure proxy settings are respected + configuration = Configuration.get_default_copy() + proxy = os.environ.get("HTTPS_PROXY") or os.environ.get("https_proxy") + if proxy: + configuration.proxy = proxy + + # Prevent SSL verification issues with internal proxies + if os.environ.get("K8S_SKIP_TLS_VERIFY", "false").lower() == "true": + configuration.verify_ssl = False + + return KubernetesSession( + api_client=ApiClient(configuration), context=context + ) except parser.ParserError as parser_error: logger.critical( diff --git a/tests/providers/kubernetes/kubernetes_provider_test.py b/tests/providers/kubernetes/kubernetes_provider_test.py index 411693117d..d21792aeb0 100644 --- a/tests/providers/kubernetes/kubernetes_provider_test.py +++ b/tests/providers/kubernetes/kubernetes_provider_test.py @@ -1,5 +1,5 @@ from argparse import Namespace -from unittest.mock import patch +from unittest.mock import MagicMock, patch from kubernetes.config.config_exception import ConfigException @@ -353,3 +353,59 @@ class TestKubernetesProvider: ) assert isinstance(session, KubernetesSession) assert session.context["context"]["cluster"] == "cli-cluster-name" + + def test_kubernetes_provider_proxy_from_env(self, monkeypatch): + monkeypatch.setenv("HTTPS_PROXY", "http://my.internal.proxy:8888") + + captured = {} + + def fake_api_client(configuration): + captured["proxy"] = getattr(configuration, "proxy", None) + return MagicMock() + + with ( + patch( + "kubernetes.config.load_kube_config", + side_effect=ConfigException("No kubeconfig"), + ), + patch("kubernetes.config.load_incluster_config", return_value=None), + patch( + "prowler.providers.kubernetes.kubernetes_provider.ApiClient", + side_effect=fake_api_client, + ), + patch( + "prowler.providers.kubernetes.kubernetes_provider.KubernetesProvider.get_all_namespaces", + return_value=["default"], + ), + ): + KubernetesProvider.setup_session() + + assert captured["proxy"] == "http://my.internal.proxy:8888" + + def test_kubernetes_provider_disable_tls_verification(self, monkeypatch): + monkeypatch.setenv("K8S_SKIP_TLS_VERIFY", "true") + + captured = {} + + def fake_api_client(configuration): + captured["verify_ssl"] = getattr(configuration, "verify_ssl", True) + return MagicMock() + + with ( + patch( + "kubernetes.config.load_kube_config", + side_effect=ConfigException("No kubeconfig"), + ), + patch("kubernetes.config.load_incluster_config", return_value=None), + patch( + "prowler.providers.kubernetes.kubernetes_provider.ApiClient", + side_effect=fake_api_client, + ), + patch( + "prowler.providers.kubernetes.kubernetes_provider.KubernetesProvider.get_all_namespaces", + return_value=["default"], + ), + ): + KubernetesProvider.setup_session() + + assert captured["verify_ssl"] is False From c6259b6c75d1c91e693d94013720eb35ad365c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 21 May 2025 11:08:52 +0200 Subject: [PATCH 11/25] fix(dashboard): remove typo from subscribe cards (#7792) --- dashboard/__main__.py | 2 +- dashboard/lib/layouts.py | 2 +- dashboard/pages/overview.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dashboard/__main__.py b/dashboard/__main__.py index 3bfb4c623a..dac536ccdf 100644 --- a/dashboard/__main__.py +++ b/dashboard/__main__.py @@ -161,7 +161,7 @@ def update_nav_bar(pathname): html.Span( [ html.Img(src="assets/favicon.ico", className="w-5"), - "Subscribe to prowler SaaS", + "Subscribe to Prowler Cloud", ], className="flex items-center gap-x-3 text-white", ), diff --git a/dashboard/lib/layouts.py b/dashboard/lib/layouts.py index 73d3ec5bcf..f1b4408c38 100644 --- a/dashboard/lib/layouts.py +++ b/dashboard/lib/layouts.py @@ -132,7 +132,7 @@ def create_layout_compliance( html.A( [ html.Img(src="assets/favicon.ico", className="w-5 mr-3"), - html.Span("Subscribe to prowler SaaS"), + html.Span("Subscribe to Prowler Cloud"), ], href="https://prowler.pro/", target="_blank", diff --git a/dashboard/pages/overview.py b/dashboard/pages/overview.py index 3daccd039b..2688281af2 100644 --- a/dashboard/pages/overview.py +++ b/dashboard/pages/overview.py @@ -1342,13 +1342,13 @@ def filter_data( "m365", m365_provider_logo, "Accounts", full_filtered_data ) - # Subscribe to prowler SaaS card + # Subscribe to Prowler Cloud card subscribe_card = [ html.Div( html.A( [ html.Img(src="assets/favicon.ico", className="w-5 mr-3"), - html.Span("Subscribe to prowler SaaS"), + html.Span("Subscribe to Prowler Cloud"), ], href="https://prowler.pro/", target="_blank", From 4e958fdf39b5f7e45c8376163f398693f1d20d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 21 May 2025 11:09:47 +0200 Subject: [PATCH 12/25] feat(kubernetes): add CIS 1.11 compliance framework (#7790) Co-authored-by: MrCloudSec --- README.md | 2 +- dashboard/compliance/cis_1_11_kubernetes.py | 24 + prowler/CHANGELOG.md | 10 + .../kubernetes/cis_1.11_kubernetes.json | 2980 +++++++++++++++++ 4 files changed, 3015 insertions(+), 1 deletion(-) create mode 100644 dashboard/compliance/cis_1_11_kubernetes.py create mode 100644 prowler/compliance/kubernetes/cis_1.11_kubernetes.json diff --git a/README.md b/README.md index 82db510e66..ccab5e285b 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ prowler dashboard | AWS | 564 | 82 | 34 | 10 | | GCP | 79 | 13 | 7 | 3 | | Azure | 140 | 18 | 8 | 3 | -| Kubernetes | 83 | 7 | 4 | 7 | +| Kubernetes | 83 | 7 | 5 | 7 | | GitHub | 3 | 2 | 1 | 0 | | M365 | 44 | 2 | 2 | 0 | | NHN (Unofficial) | 6 | 2 | 1 | 0 | diff --git a/dashboard/compliance/cis_1_11_kubernetes.py b/dashboard/compliance/cis_1_11_kubernetes.py new file mode 100644 index 0000000000..94558f33ad --- /dev/null +++ b/dashboard/compliance/cis_1_11_kubernetes.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_cis + +warnings.filterwarnings("ignore") + + +def get_table(data): + aux = data[ + [ + "REQUIREMENTS_ID", + "REQUIREMENTS_DESCRIPTION", + "REQUIREMENTS_ATTRIBUTES_SECTION", + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ].copy() + + return get_section_containers_cis( + aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION" + ) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 5f1416caa8..9c0f1cb886 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to the **Prowler SDK** are documented in this file. +## [5.8.0] (Prowler v5.8.0) + +### Added +- Add CIS 1.11 compliance framework for Kubernetes. [(#7790)](https://github.com/prowler-cloud/prowler/pull/7790) +- Support `HTTPS_PROXY` and `K8S_SKIP_TLS_VERIFY` in Kubernetes. [(#7720)](https://github.com/prowler-cloud/prowler/pull/7720) + +### Fixed + +--- + ## [v5.7.0] (Prowler v5.7.0) ### Added diff --git a/prowler/compliance/kubernetes/cis_1.11_kubernetes.json b/prowler/compliance/kubernetes/cis_1.11_kubernetes.json new file mode 100644 index 0000000000..d091e42bae --- /dev/null +++ b/prowler/compliance/kubernetes/cis_1.11_kubernetes.json @@ -0,0 +1,2980 @@ +{ + "Framework": "CIS", + "Version": "1.11.1", + "Provider": "Kubernetes", + "Description": "This CIS Kubernetes Benchmark provides prescriptive guidance for establishing a secure configuration posture for Kubernetes v1.28 - v1.31", + "Requirements": [ + { + "Id": "1.1.1", + "Description": "Ensure that the API server pod specification file permissions are set to 600 or more restrictive", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the API server pod specification file has permissions of `600` or more restrictive.", + "RationaleStatement": "The API server pod specification file controls various parameters that set the behavior of the API server. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/manifests/kube-apiserver.yaml ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/manifests/kube-apiserver.yaml ``` Verify that the permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-apiserver/", + "DefaultValue": "By default, the `kube-apiserver.yaml` file has permissions of `640`." + } + ] + }, + { + "Id": "1.1.2", + "Description": "Ensure that the API server pod specification file ownership is set to root:root", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the API server pod specification file ownership is set to `root:root`.", + "RationaleStatement": "The API server pod specification file controls various parameters that set the behavior of the API server. You should set its file ownership to maintain the integrity of the file. The file should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chown root:root /etc/kubernetes/manifests/kube-apiserver.yaml ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %U:%G /etc/kubernetes/manifests/kube-apiserver.yaml ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-apiserver/", + "DefaultValue": "By default, the `kube-apiserver.yaml` file ownership is set to `root:root`." + } + ] + }, + { + "Id": "1.1.3", + "Description": "Ensure that the controller manager pod specification file permissions are set to 600 or more restrictive", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the controller manager pod specification file has permissions of `600` or more restrictive.", + "RationaleStatement": "The controller manager pod specification file controls various parameters that set the behavior of the Controller Manager on the master node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/manifests/kube-controller-manager.yaml ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/manifests/kube-controller-manager.yaml ``` Verify that the permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-apiserver/", + "DefaultValue": "By default, the `kube-controller-manager.yaml` file has permissions of `640`." + } + ] + }, + { + "Id": "1.1.4", + "Description": "Ensure that the controller manager pod specification file ownership is set to root:root", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the controller manager pod specification file ownership is set to `root:root`.", + "RationaleStatement": "The controller manager pod specification file controls various parameters that set the behavior of various components of the master node. You should set its file ownership to maintain the integrity of the file. The file should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chown root:root /etc/kubernetes/manifests/kube-controller-manager.yaml ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %U:%G /etc/kubernetes/manifests/kube-controller-manager.yaml ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-controller-manager", + "DefaultValue": "By default, `kube-controller-manager.yaml` file ownership is set to `root:root`." + } + ] + }, + { + "Id": "1.1.5", + "Description": "Ensure that the scheduler pod specification file permissions are set to 600 or more restrictive", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the scheduler pod specification file has permissions of `600` or more restrictive.", + "RationaleStatement": "The scheduler pod specification file controls various parameters that set the behavior of the Scheduler service in the master node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/manifests/kube-scheduler.yaml ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/manifests/kube-scheduler.yaml ``` Verify that the permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-scheduler/", + "DefaultValue": "By default, `kube-scheduler.yaml` file has permissions of `640`." + } + ] + }, + { + "Id": "1.1.6", + "Description": "Ensure that the scheduler pod specification file ownership is set to root:root", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the scheduler pod specification file ownership is set to `root:root`.", + "RationaleStatement": "The scheduler pod specification file controls various parameters that set the behavior of the `kube-scheduler` service in the master node. You should set its file ownership to maintain the integrity of the file. The file should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chown root:root /etc/kubernetes/manifests/kube-scheduler.yaml ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %U:%G /etc/kubernetes/manifests/kube-scheduler.yaml ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-scheduler/", + "DefaultValue": "By default, `kube-scheduler.yaml` file ownership is set to `root:root`." + } + ] + }, + { + "Id": "1.1.7", + "Description": "Ensure that the etcd pod specification file permissions are set to 600 or more restrictive", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `/etc/kubernetes/manifests/etcd.yaml` file has permissions of `600` or more restrictive.", + "RationaleStatement": "The etcd pod specification file `/etc/kubernetes/manifests/etcd.yaml` controls various parameters that set the behavior of the `etcd` service in the master node. etcd is a highly-available key-value store which Kubernetes uses for persistent storage of all of its REST API object. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/manifests/etcd.yaml ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/manifests/etcd.yaml ``` Verify that the permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd:https://kubernetes.io/docs/admin/etcd/", + "DefaultValue": "By default, `/etc/kubernetes/manifests/etcd.yaml` file has permissions of `640`." + } + ] + }, + { + "Id": "1.1.8", + "Description": "Ensure that the etcd pod specification file ownership is set to root:root", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `/etc/kubernetes/manifests/etcd.yaml` file ownership is set to `root:root`.", + "RationaleStatement": "The etcd pod specification file `/etc/kubernetes/manifests/etcd.yaml` controls various parameters that set the behavior of the `etcd` service in the master node. etcd is a highly-available key-value store which Kubernetes uses for persistent storage of all of its REST API object. You should set its file ownership to maintain the integrity of the file. The file should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chown root:root /etc/kubernetes/manifests/etcd.yaml ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %U:%G /etc/kubernetes/manifests/etcd.yaml ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd:https://kubernetes.io/docs/admin/etcd/", + "DefaultValue": "By default, `/etc/kubernetes/manifests/etcd.yaml` file ownership is set to `root:root`." + } + ] + }, + { + "Id": "1.1.9", + "Description": "Ensure that the Container Network Interface file permissions are set to 600 or more restrictive", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Ensure that the Container Network Interface files have permissions of `600` or more restrictive.", + "RationaleStatement": "Container Network Interface provides various networking options for overlay networking. You should consult their documentation and restrict their respective file permissions to maintain the integrity of those files. Those files should be writable by only the administrators on the system.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a ``` Verify that the permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/cluster-administration/networking/", + "DefaultValue": "NA" + } + ] + }, + { + "Id": "1.1.10", + "Description": "Ensure that the Container Network Interface file ownership is set to root:root", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Ensure that the Container Network Interface files have ownership set to `root:root`.", + "RationaleStatement": "Container Network Interface provides various networking options for overlay networking. You should consult their documentation and restrict their respective file permissions to maintain the integrity of those files. Those files should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chown root:root ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %U:%G ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/cluster-administration/networking/", + "DefaultValue": "NA" + } + ] + }, + { + "Id": "1.1.11", + "Description": "Ensure that the etcd data directory permissions are set to 700 or more restrictive", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the etcd data directory has permissions of `700` or more restrictive.", + "RationaleStatement": "etcd is a highly-available key-value store used by Kubernetes deployments for persistent storage of all of its REST API objects. This data directory should be protected from any unauthorized reads or writes. It should not be readable or writable by any group members or the world.", + "ImpactStatement": "None", + "RemediationProcedure": "On the etcd server node, get the etcd data directory, passed as an argument `--data-dir`, from the below command: ``` ps -ef | grep etcd ``` Run the below command (based on the etcd data directory found above). For example, ``` chmod 700 /var/lib/etcd ```", + "AuditProcedure": "On the etcd server node, get the etcd data directory, passed as an argument `--data-dir`, from the below command: ``` ps -ef | grep etcd ``` Run the below command (based on the etcd data directory found above). For example, ``` stat -c %a /var/lib/etcd ``` Verify that the permissions are `700` or more restrictive.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd/docs/latest/op-guide/configuration.html#data-dir:https://kubernetes.io/docs/admin/etcd/", + "DefaultValue": "By default, etcd data directory has permissions of `755`." + } + ] + }, + { + "Id": "1.1.12", + "Description": "Ensure that the etcd data directory ownership is set to etcd:etcd", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the etcd data directory ownership is set to `etcd:etcd`.", + "RationaleStatement": "etcd is a highly-available key-value store used by Kubernetes deployments for persistent storage of all of its REST API objects. This data directory should be protected from any unauthorized reads or writes. It should be owned by `etcd:etcd`.", + "ImpactStatement": "None", + "RemediationProcedure": "On the etcd server node, get the etcd data directory, passed as an argument `--data-dir`, from the below command: ``` ps -ef | grep etcd ``` Run the below command (based on the etcd data directory found above). For example, ``` chown etcd:etcd /var/lib/etcd ```", + "AuditProcedure": "On the etcd server node, get the etcd data directory, passed as an argument `--data-dir`, from the below command: ``` ps -ef | grep etcd ``` Run the below command (based on the etcd data directory found above). For example, ``` stat -c %U:%G /var/lib/etcd ``` Verify that the ownership is set to `etcd:etcd`.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd/docs/latest/op-guide/configuration.html#data-dir:https://kubernetes.io/docs/admin/etcd/", + "DefaultValue": "By default, etcd data directory ownership is set to `etcd:etcd`." + } + ] + }, + { + "Id": "1.1.13", + "Description": "Ensure that the default administrative credential file permissions are set to 600", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `admin.conf` file (and `super-admin.conf` file, where it exists) have permissions of `600`.", + "RationaleStatement": "As part of initial cluster setup, default kubeconfig files are created to be used by the administrator of the cluster. These files contain private keys and certificates which allow for privileged access to the cluster. You should restrict their file permissions to maintain the integrity and confidentiality of the file(s). The file(s) should be readable and writable by only the administrators on the system.", + "ImpactStatement": "None.", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/admin.conf ``` On Kubernetes 1.29+ the `super-admin.conf` file should also be modified, if present. For example, ``` chmod 600 /etc/kubernetes/super-admin.conf ```", + "AuditProcedure": "Run the following command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/admin.conf ``` On Kubernetes version 1.29 and higher run the following command as well :- ``` stat -c %a /etc/kubernetes/super-admin.conf ``` Verify that the permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/:https://raesene.github.io/blog/2024/01/06/when-is-admin-not-admin/", + "DefaultValue": "By default, admin.conf and super-admin.conf have permissions of `600`." + } + ] + }, + { + "Id": "1.1.14", + "Description": "Ensure that the default administrative credential file ownership is set to root:root", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `admin.conf` (and `super-admin.conf` file, where it exists) file ownership is set to `root:root`.", + "RationaleStatement": "As part of initial cluster setup, default kubeconfig files are created to be used by the administrator of the cluster. These files contain private keys and certificates which allow for privileged access to the cluster. You should set their file ownership to maintain the integrity and confidentiality of the file. The file(s) should be owned by `root:root`.", + "ImpactStatement": "None.", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chown root:root /etc/kubernetes/admin.conf ``` On Kubernetes 1.29+ the super-admin.conf file should also be modified, if present. For example, ``` chown root:root /etc/kubernetes/super-admin.conf ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %U:%G /etc/kubernetes/admin.conf ``` On Kubernetes version 1.29 and higher run the following command as well :- ``` stat -c %U:%G /etc/kubernetes/super-admin.conf ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubeadm/:https://raesene.github.io/blog/2024/01/06/when-is-admin-not-admin/", + "DefaultValue": "By default, `admin.conf` and `super-admin.conf` file ownership is set to `root:root`." + } + ] + }, + { + "Id": "1.1.15", + "Description": "Ensure that the scheduler.conf file permissions are set to 600 or more restrictive", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `scheduler.conf` file has permissions of `600` or more restrictive.", + "RationaleStatement": "The `scheduler.conf` file is the kubeconfig file for the Scheduler. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/scheduler.conf ```", + "AuditProcedure": "Run the following command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/scheduler.conf ``` Verify that the permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/", + "DefaultValue": "By default, `scheduler.conf` has permissions of `640`." + } + ] + }, + { + "Id": "1.1.16", + "Description": "Ensure that the scheduler.conf file ownership is set to root:root", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `scheduler.conf` file ownership is set to `root:root`.", + "RationaleStatement": "The `scheduler.conf` file is the kubeconfig file for the Scheduler. You should set its file ownership to maintain the integrity of the file. The file should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chown root:root /etc/kubernetes/scheduler.conf ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %U:%G /etc/kubernetes/scheduler.conf ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubeadm/", + "DefaultValue": "By default, `scheduler.conf` file ownership is set to `root:root`." + } + ] + }, + { + "Id": "1.1.17", + "Description": "Ensure that the controller-manager.conf file permissions are set to 600 or more restrictive", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `controller-manager.conf` file has permissions of 600 or more restrictive.", + "RationaleStatement": "The `controller-manager.conf` file is the kubeconfig file for the Controller Manager. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod 600 /etc/kubernetes/controller-manager.conf ```", + "AuditProcedure": "Run the following command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %a /etc/kubernetes/controller-manager.conf ``` Verify that the permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-controller-manager/", + "DefaultValue": "By default, `controller-manager.conf` has permissions of `640`." + } + ] + }, + { + "Id": "1.1.18", + "Description": "Ensure that the controller-manager.conf file ownership is set to root:root", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `controller-manager.conf` file ownership is set to `root:root`.", + "RationaleStatement": "The `controller-manager.conf` file is the kubeconfig file for the Controller Manager. You should set its file ownership to maintain the integrity of the file. The file should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chown root:root /etc/kubernetes/controller-manager.conf ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c %U:%G /etc/kubernetes/controller-manager.conf ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-controller-manager/", + "DefaultValue": "By default, `controller-manager.conf` file ownership is set to `root:root`." + } + ] + }, + { + "Id": "1.1.19", + "Description": "Ensure that the Kubernetes PKI directory and file ownership is set to root:root", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the Kubernetes PKI directory and file ownership is set to `root:root`.", + "RationaleStatement": "Kubernetes makes use of a number of certificates as part of its operation. You should set the ownership of the directory containing the PKI information and all files in that directory to maintain their integrity. The directory and files should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chown -R root:root /etc/kubernetes/pki/ ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` ls -laR /etc/kubernetes/pki/ ``` Verify that the ownership of all files and directories in this hierarchy is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-apiserver/", + "DefaultValue": "By default, the /etc/kubernetes/pki/ directory and all of the files and directories contained within it, are set to be owned by the root user." + } + ] + }, + { + "Id": "1.1.20", + "Description": "Ensure that the Kubernetes PKI certificate file permissions are set to 644 or more restrictive", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Ensure that Kubernetes PKI certificate files have permissions of `644` or more restrictive.", + "RationaleStatement": "Kubernetes makes use of a number of certificate files as part of the operation of its components. The permissions on these files should be set to `644` or more restrictive to protect their integrity and confidentiality.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod -R 644 /etc/kubernetes/pki/*.crt ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c '%a' /etc/kubernetes/pki/*.crt ``` Verify that the permissions are `644` or more restrictive. or ``` ls -l /etc/kubernetes/pki/*.crt ``` Verify -rw------", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-apiserver/", + "DefaultValue": "By default, the certificates used by Kubernetes are set to have permissions of `644`" + } + ] + }, + { + "Id": "1.1.21", + "Description": "Ensure that the Kubernetes PKI key file permissions are set to 600", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.1 Control Plane Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Ensure that Kubernetes PKI key files have permissions of `600`.", + "RationaleStatement": "Kubernetes makes use of a number of key files as part of the operation of its components. The permissions on these files should be set to `600` to protect their integrity and confidentiality.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` chmod -R 600 /etc/kubernetes/pki/*.key ```", + "AuditProcedure": "Run the below command (based on the file location on your system) on the Control Plane node. For example, ``` stat -c '%a' /etc/kubernetes/pki/*.key ``` Verify that the permissions are `600` or more restrictive. or ``` ls -l /etc/kubernetes/pki/*.key ``` Verify that the permissions are `-rw------`", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-apiserver/", + "DefaultValue": "By default, the keys used by Kubernetes are set to have permissions of `600`" + } + ] + }, + { + "Id": "1.2.1", + "Description": "Ensure that the --anonymous-auth argument is set to false", + "Checks": [ + "apiserver_anonymous_requests" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Disable anonymous requests to the API server.", + "RationaleStatement": "When enabled, requests that are not rejected by other configured authentication methods are treated as anonymous requests. These requests are then served by the API server. You should rely on authentication to authorize access and disallow anonymous requests. If you are using RBAC authorization, it is generally considered reasonable to allow anonymous access to the API Server for health checks and discovery purposes, and hence this recommendation is not scored. However, you should consider whether anonymous discovery is an acceptable risk for your purposes.", + "ImpactStatement": "Anonymous requests will be rejected.", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the below parameter. ``` --anonymous-auth=false ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--anonymous-auth` argument is set to `false`. Alternative Audit Method ``` kubectl get pod -nkube-system -lcomponent=kube-apiserver -o=jsonpath='{range .items[*]}{.spec.containers[*].command} {\\}{end}' | grep '--anonymous-auth' | grep -i false ``` If the exit code is '1', then the control isn't present / failed", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-apiserver/:https://kubernetes.io/docs/admin/authentication/#anonymous-requests", + "DefaultValue": "By default, anonymous access is enabled." + } + ] + }, + { + "Id": "1.2.2", + "Description": "Ensure that the --token-auth-file parameter is not set", + "Checks": [ + "apiserver_no_token_auth_file" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Do not use token based authentication.", + "RationaleStatement": "The token-based authentication utilizes static tokens to authenticate requests to the apiserver. The tokens are stored in clear-text in a file on the apiserver, and cannot be revoked or rotated without restarting the apiserver. Hence, do not use static token-based authentication.", + "ImpactStatement": "You will have to configure and use alternate authentication mechanisms such as certificates. Static token based authentication could not be used.", + "RemediationProcedure": "Follow the documentation and configure alternate mechanisms for authentication. Then, edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the master node and remove the `--token-auth-file=` parameter.", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--token-auth-file` argument does not exist. Alternative Audit Method ``` kubectl get pod -nkube-system -lcomponent=kube-apiserver -o=jsonpath='{range .items[*]}{.spec.containers[*].command} {\\}{end}' | grep '--token-auth-file' | grep -i false ``` If the exit code is '1', then the control isn't present / failed", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/authentication/#static-token-file:https://kubernetes.io/docs/admin/kube-apiserver/", + "DefaultValue": "By default, `--token-auth-file` argument is not set." + } + ] + }, + { + "Id": "1.2.3", + "Description": "Ensure that the DenyServiceExternalIPs is set", + "Checks": [ + "apiserver_deny_service_external_ips" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "This admission controller rejects all net-new usage of the Service field externalIPs.", + "RationaleStatement": "Most users do not need the ability to set the `externalIPs` field for a `Service` at all, and cluster admins should consider disabling this functionality by enabling the `DenyServiceExternalIPs` admission controller. Clusters that do need to allow this functionality should consider using some custom policy to manage its usage.", + "ImpactStatement": "When enabled, users of the cluster may not create new Services which use externalIPs and may not add new values to externalIPs on existing Service objects.", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the master node and append the Kubernetes API server flag --enable-admission-plugins with the DenyServiceExternalIPs plugin. Note, the Kubernetes API server flag --enable-admission-plugins takes a comma-delimited list of admission control plugins to be enabled, even if they are in the list of plugins enabled by default. ``` kube-apiserver --enable-admission-plugins=DenyServiceExternalIPs ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `DenyServiceExternalIPs' argument exist as a string value in --enable-admission-plugins.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/:https://kubernetes.io/docs/admin/kube-apiserver/", + "DefaultValue": "By default, --enable-admission-plugins=DenyServiceExternalIP argument is not set, and the use of externalIPs is authorized." + } + ] + }, + { + "Id": "1.2.4", + "Description": "Ensure that the --kubelet-client-certificate and --kubelet-client-key arguments are set as appropriate", + "Checks": [ + "apiserver_kubelet_tls_auth" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Enable certificate based kubelet authentication.", + "RationaleStatement": "The apiserver, by default, does not authenticate itself to the kubelet's HTTPS endpoints. The requests from the apiserver are treated anonymously. You should set up certificate-based kubelet authentication to ensure that the apiserver authenticates itself to kubelets when submitting requests.", + "ImpactStatement": "You require TLS to be configured on apiserver as well as kubelets.", + "RemediationProcedure": "Follow the Kubernetes documentation and set up the TLS connection between the apiserver and kubelets. Then, edit API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the kubelet client certificate and key parameters as below. ``` --kubelet-client-certificate= --kubelet-client-key= ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--kubelet-client-certificate` and `--kubelet-client-key` arguments exist and they are set as appropriate. Alternative Audit ``` kubectl get pod -nkube-system -lcomponent=kube-apiserver -o=jsonpath='{range .items[*]}{.spec.containers[*].command} {\\}{end}' | grep '--kubelet-client-certificate' | grep -i false ``` If the exit code is '1', then the control isn't present / failed", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-apiserver/:https://kubernetes.io/docs/admin/kubelet-authentication-authorization/:https://kubernetes.io/docs/concepts/cluster-administration/master-node-communication/#apiserver---kubelet", + "DefaultValue": "By default, certificate-based kubelet authentication is not set." + } + ] + }, + { + "Id": "1.2.5", + "Description": "Ensure that the --kubelet-certificate-authority argument is set as appropriate", + "Checks": [ + "apiserver_kubelet_cert_auth" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Verify kubelet's certificate before establishing connection.", + "RationaleStatement": "The connections from the apiserver to the kubelet are used for fetching logs for pods, attaching (through kubectl) to running pods, and using the kubelet’s port-forwarding functionality. These connections terminate at the kubelet’s HTTPS endpoint. By default, the apiserver does not verify the kubelet’s serving certificate, which makes the connection subject to man-in-the-middle attacks, and unsafe to run over untrusted and/or public networks.", + "ImpactStatement": "You require TLS to be configured on apiserver as well as kubelets.", + "RemediationProcedure": "Follow the Kubernetes documentation and setup the TLS connection between the apiserver and kubelets. Then, edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the `--kubelet-certificate-authority` parameter to the path to the cert file for the certificate authority. ``` --kubelet-certificate-authority= ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--kubelet-certificate-authority` argument exists and is set as appropriate. Alternative Audit ``` kubectl get pod -nkube-system -lcomponent=kube-apiserver -o=jsonpath='{range .items[]}{.spec.containers[].command} {\\}{end}' | grep '--kubelet-certificate-authority' | grep -i false ``` If the exit code is '1', then the control isn't present / failed", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-apiserver/:https://kubernetes.io/docs/admin/kubelet-authentication-authorization/:https://kubernetes.io/docs/concepts/cluster-administration/master-node-communication/#apiserver---kubelet", + "DefaultValue": "By default, `--kubelet-certificate-authority` argument is not set." + } + ] + }, + { + "Id": "1.2.6", + "Description": "Ensure that the --authorization-mode argument is not set to AlwaysAllow", + "Checks": [ + "apiserver_auth_mode_not_always_allow" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Do not always authorize all requests.", + "RationaleStatement": "The API Server, can be configured to allow all requests. This mode should not be used on any production cluster.", + "ImpactStatement": "Only authorized requests will be served.", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the `--authorization-mode` parameter to values other than `AlwaysAllow`. One such example could be as below. ``` --authorization-mode=RBAC ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--authorization-mode` argument exists and is not set to `AlwaysAllow`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/admin/authorization/", + "DefaultValue": "By default, `AlwaysAllow` is not enabled." + } + ] + }, + { + "Id": "1.2.7", + "Description": "Ensure that the --authorization-mode argument includes Node", + "Checks": [ + "apiserver_auth_mode_include_node" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Restrict kubelet nodes to reading only objects associated with them.", + "RationaleStatement": "The `Node` authorization mode only allows kubelets to read `Secret`, `ConfigMap`, `PersistentVolume`, and `PersistentVolumeClaim` objects associated with their nodes.", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the `--authorization-mode` parameter to a value that includes `Node`. ``` --authorization-mode=Node,RBAC ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--authorization-mode` argument exists and is set to a value to include `Node`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-apiserver/:https://kubernetes.io/docs/admin/authorization/node/:https://github.com/kubernetes/kubernetes/pull/46076:https://acotten.com/post/kube17-security", + "DefaultValue": "By default, `Node` authorization is not enabled." + } + ] + }, + { + "Id": "1.2.8", + "Description": "Ensure that the --authorization-mode argument includes RBAC", + "Checks": [ + "apiserver_auth_mode_include_rbac" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Turn on Role Based Access Control.", + "RationaleStatement": "Role Based Access Control (RBAC) allows fine-grained control over the operations that different entities can perform on different objects in the cluster. It is recommended to use the RBAC authorization mode.", + "ImpactStatement": "When RBAC is enabled you will need to ensure that appropriate RBAC settings (including Roles, RoleBindings and ClusterRoleBindings) are configured to allow appropriate access.", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the `--authorization-mode` parameter to a value that includes `RBAC`, for example: ``` --authorization-mode=Node,RBAC ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--authorization-mode` argument exists and is set to a value to include `RBAC`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/access-authn-authz/rbac/", + "DefaultValue": "By default, `RBAC` authorization is not enabled." + } + ] + }, + { + "Id": "1.2.9", + "Description": "Ensure that the admission control plugin EventRateLimit is set", + "Checks": [ + "apiserver_event_rate_limit" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Limit the rate at which the API server accepts requests.", + "RationaleStatement": "Using `EventRateLimit` admission control enforces a limit on the number of events that the API Server will accept in a given time slice. A misbehaving workload could overwhelm and DoS the API Server, making it unavailable. This particularly applies to a multi-tenant cluster, where there might be a small percentage of misbehaving tenants which could have a significant impact on the performance of the cluster overall. Hence, it is recommended to limit the rate of events that the API server will accept.", + "ImpactStatement": "You need to carefully tune in limits as per your environment.", + "RemediationProcedure": "Follow the Kubernetes documentation and set the desired limits in a configuration file. Then, edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` and set the below parameters. ``` --enable-admission-plugins=...,EventRateLimit,... --admission-control-config-file= ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--enable-admission-plugins` argument is set to a value that includes `EventRateLimit`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#eventratelimit:https://github.com/staebler/community/blob/9873b632f4d99b5d99c38c9b15fe2f8b93d0a746/contributors/design-proposals/admission_control_event_rate_limit.md", + "DefaultValue": "By default, `EventRateLimit` is not set." + } + ] + }, + { + "Id": "1.2.10", + "Description": "Ensure that the admission control plugin AlwaysAdmit is not set", + "Checks": [ + "apiserver_no_always_admit_plugin" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Do not allow all requests.", + "RationaleStatement": "Setting admission control plugin `AlwaysAdmit` allows all requests and do not filter any requests. The `AlwaysAdmit` admission controller was deprecated in Kubernetes v1.13. Its behavior was equivalent to turning off all admission controllers.", + "ImpactStatement": "Only requests explicitly allowed by the admissions control plugins would be served.", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and either remove the `--enable-admission-plugins` parameter, or set it to a value that does not include `AlwaysAdmit`.", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that if the `--enable-admission-plugins` argument is set, its value does not include `AlwaysAdmit`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#alwaysadmit", + "DefaultValue": "`AlwaysAdmit` is not in the list of default admission plugins." + } + ] + }, + { + "Id": "1.2.11", + "Description": "Ensure that the admission control plugin AlwaysPullImages is set", + "Checks": [ + "apiserver_always_pull_images_plugin" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Always pull images.", + "RationaleStatement": "Setting admission control policy to `AlwaysPullImages` forces every new pod to pull the required images every time. In a multi-tenant cluster users can be assured that their private images can only be used by those who have the credentials to pull them. Without this admission control policy, once an image has been pulled to a node, any pod from any user can use it simply by knowing the image’s name, without any authorization check against the image ownership. When this plug-in is enabled, images are always pulled prior to starting containers, which means valid credentials are required.", + "ImpactStatement": "Credentials would be required to pull the private images every time. Also, in trusted environments, this might increases load on network, registry, and decreases speed. This setting could impact offline or isolated clusters, which have images preloaded and do not have access to a registry to pull in-use images. This setting is not appropriate for clusters which use this configuration.", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the `--enable-admission-plugins` parameter to include `AlwaysPullImages`. ``` --enable-admission-plugins=...,AlwaysPullImages,... ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--enable-admission-plugins` argument is set to a value that includes `AlwaysPullImages`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#alwayspullimages", + "DefaultValue": "By default, `AlwaysPullImages` is not set." + } + ] + }, + { + "Id": "1.2.12", + "Description": "Ensure that the admission control plugin ServiceAccount is set", + "Checks": [ + "apiserver_service_account_plugin" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Automate service accounts management.", + "RationaleStatement": "When you create a pod, if you do not specify a service account, it is automatically assigned the `default` service account in the same namespace. You should create your own service account and let the API server manage its security tokens.", + "ImpactStatement": "None.", + "RemediationProcedure": "Follow the documentation and create `ServiceAccount` objects as per your environment. Then, edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the master node and ensure that the `--disable-admission-plugins` parameter is set to a value that does not include `ServiceAccount`.", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--disable-admission-plugins` argument is set to a value that does not includes `ServiceAccount`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#serviceaccount:https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + "DefaultValue": "By default, `ServiceAccount` is set." + } + ] + }, + { + "Id": "1.2.13", + "Description": "Ensure that the admission control plugin NamespaceLifecycle is set", + "Checks": [ + "apiserver_namespace_lifecycle_plugin" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Reject creating objects in a namespace that is undergoing termination.", + "RationaleStatement": "Setting admission control policy to `NamespaceLifecycle` ensures that objects cannot be created in non-existent namespaces, and that namespaces undergoing termination are not used for creating the new objects. This is recommended to enforce the integrity of the namespace termination process and also for the availability of the newer objects.", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the `--disable-admission-plugins` parameter to ensure it does not include `NamespaceLifecycle`.", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--disable-admission-plugins` argument is set to a value that does not include `NamespaceLifecycle`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#namespacelifecycle", + "DefaultValue": "By default, `NamespaceLifecycle` is set." + } + ] + }, + { + "Id": "1.2.14", + "Description": "Ensure that the admission control plugin NodeRestriction is set", + "Checks": [ + "apiserver_node_restriction_plugin" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Limit the `Node` and `Pod` objects that a kubelet could modify.", + "RationaleStatement": "Using the `NodeRestriction` plug-in ensures that the kubelet is restricted to the `Node` and `Pod` objects that it could modify as defined. Such kubelets will only be allowed to modify their own `Node` API object, and only modify `Pod` API objects that are bound to their node.", + "ImpactStatement": "None", + "RemediationProcedure": "Follow the Kubernetes documentation and configure `NodeRestriction` plug-in on kubelets. Then, edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the master node and set the `--enable-admission-plugins` parameter to a value that includes `NodeRestriction`. ``` --enable-admission-plugins=...,NodeRestriction,... ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--enable-admission-plugins` argument is set to a value that includes `NodeRestriction`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#noderestriction:https://kubernetes.io/docs/reference/access-authn-authz/node/", + "DefaultValue": "By default, `NodeRestriction` is not set." + } + ] + }, + { + "Id": "1.2.15", + "Description": "Ensure that the --profiling argument is set to false", + "Checks": [ + "apiserver_disable_profiling" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Disable profiling, if not needed.", + "RationaleStatement": "Profiling allows for the identification of specific performance bottlenecks. It generates a significant amount of program data that could potentially be exploited to uncover system and program details. If you are not experiencing any bottlenecks and do not need the profiler for troubleshooting purposes, it is recommended to turn it off to reduce the potential attack surface.", + "ImpactStatement": "Profiling information would not be available.", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the below parameter. ``` --profiling=false ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--profiling` argument is set to `false`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/", + "DefaultValue": "By default, profiling is enabled." + } + ] + }, + { + "Id": "1.2.16", + "Description": "Ensure that the --audit-log-path argument is set", + "Checks": [ + "apiserver_audit_log_path_set" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Enable auditing on the Kubernetes API Server and set the desired audit log path.", + "RationaleStatement": "Auditing the Kubernetes API Server provides a security-relevant chronological set of records documenting the sequence of activities that have affected system by individual users, administrators or other components of the system. Even though currently, Kubernetes provides only basic audit capabilities, it should be enabled. You can enable it by setting an appropriate audit log path.", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the `--audit-log-path` parameter to a suitable path and file where you would like audit logs to be written, for example: ``` --audit-log-path=/var/log/apiserver/audit.log ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--audit-log-path` argument is set as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/:https://github.com/kubernetes/enhancements/issues/22", + "DefaultValue": "By default, auditing is not enabled." + } + ] + }, + { + "Id": "1.2.17", + "Description": "Ensure that the --audit-log-maxage argument is set to 30 or as appropriate", + "Checks": [ + "apiserver_audit_log_maxage_set" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Retain the logs for at least 30 days or as appropriate.", + "RationaleStatement": "Retaining logs for at least 30 days ensures that you can go back in time and investigate or correlate any events. Set your audit log retention period to 30 days or as per your business requirements.", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the `--audit-log-maxage` parameter to 30 or as an appropriate number of days: ``` --audit-log-maxage=30 ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--audit-log-maxage` argument is set to `30` or as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/:https://github.com/kubernetes/enhancements/issues/22", + "DefaultValue": "By default, auditing is not enabled." + } + ] + }, + { + "Id": "1.2.18", + "Description": "Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate", + "Checks": [ + "apiserver_audit_log_maxbackup_set" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Retain 10 or an appropriate number of old log files.", + "RationaleStatement": "Kubernetes automatically rotates the log files. Retaining old log files ensures that you would have sufficient log data available for carrying out any investigation or correlation. For example, if you have set file size of 100 MB and the number of old log files to keep as 10, you would approximate have 1 GB of log data that you could potentially use for your analysis.", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the `--audit-log-maxbackup` parameter to 10 or to an appropriate value. ``` --audit-log-maxbackup=10 ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--audit-log-maxbackup` argument is set to `10` or as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/:https://github.com/kubernetes/enhancements/issues/22", + "DefaultValue": "By default, auditing is not enabled." + } + ] + }, + { + "Id": "1.2.19", + "Description": "Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate", + "Checks": [ + "apiserver_audit_log_maxsize_set" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Rotate log files on reaching 100 MB or as appropriate.", + "RationaleStatement": "Kubernetes automatically rotates the log files. Retaining old log files ensures that you would have sufficient log data available for carrying out any investigation or correlation. If you have set file size of 100 MB and the number of old log files to keep as 10, you would approximate have 1 GB of log data that you could potentially use for your analysis.", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the `--audit-log-maxsize` parameter to an appropriate size in MB. For example, to set it as 100 MB: ``` --audit-log-maxsize=100 ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--audit-log-maxsize` argument is set to `100` or as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/:https://github.com/kubernetes/enhancements/issues/22", + "DefaultValue": "By default, auditing is not enabled." + } + ] + }, + { + "Id": "1.2.20", + "Description": "Ensure that the --request-timeout argument is set as appropriate", + "Checks": [ + "apiserver_request_timeout_set" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Set global request timeout for API server requests as appropriate.", + "RationaleStatement": "Setting global request timeout allows extending the API server request timeout limit to a duration appropriate to the user's connection speed. By default, it is set to 60 seconds which might be problematic on slower connections making cluster resources inaccessible once the data volume for requests exceeds what can be transmitted in 60 seconds. But, setting this timeout limit to be too large can exhaust the API server resources making it prone to Denial-of-Service attack. Hence, it is recommended to set this limit as appropriate and change the default limit of 60 seconds only if needed.", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` and set the below parameter as appropriate and if needed. For example, ``` --request-timeout=300s ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--request-timeout` argument is either not set or set to an appropriate value.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://github.com/kubernetes/kubernetes/pull/51415", + "DefaultValue": "By default, `--request-timeout` is set to 60 seconds." + } + ] + }, + { + "Id": "1.2.21", + "Description": "Ensure that the --service-account-lookup argument is set to true", + "Checks": [ + "apiserver_service_account_lookup_true" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Validate service account before validating token.", + "RationaleStatement": "If `--service-account-lookup` is not enabled, the apiserver only verifies that the authentication token is valid, and does not validate that the service account token mentioned in the request is actually present in etcd. This allows using a service account token even after the corresponding service account is deleted. This is an example of time of check to time of use security issue.", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the below parameter. ``` --service-account-lookup=true ``` Alternatively, you can delete the `--service-account-lookup` parameter from this file so that the default takes effect.", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that if the `--service-account-lookup` argument exists it is set to `true`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://github.com/kubernetes/kubernetes/issues/24167:https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use", + "DefaultValue": "By default, `--service-account-lookup` argument is set to `true`." + } + ] + }, + { + "Id": "1.2.22", + "Description": "Ensure that the --service-account-key-file argument is set as appropriate", + "Checks": [ + "apiserver_service_account_key_file_set" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Explicitly set a service account public key file for service accounts on the apiserver.", + "RationaleStatement": "By default, if no `--service-account-key-file` is specified to the apiserver, it uses the private key from the TLS serving certificate to verify service account tokens. To ensure that the keys for service account tokens could be rotated as needed, a separate public/private key pair should be used for signing service account tokens. Hence, the public key should be specified to the apiserver with `--service-account-key-file`.", + "ImpactStatement": "The corresponding private key must be provided to the controller manager. You would need to securely maintain the key file and rotate the keys based on your organization's key rotation policy.", + "RemediationProcedure": "Edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the Control Plane node and set the `--service-account-key-file` parameter to the public key file for service accounts: ``` --service-account-key-file= ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--service-account-key-file` argument exists and is set as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://github.com/kubernetes/kubernetes/issues/24167", + "DefaultValue": "By default, `--service-account-key-file` argument is not set." + } + ] + }, + { + "Id": "1.2.23", + "Description": "Ensure that the --etcd-certfile and --etcd-keyfile arguments are set as appropriate", + "Checks": [ + "apiserver_etcd_tls_config" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "etcd should be configured to make use of TLS encryption for client connections.", + "RationaleStatement": "etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be protected by client authentication. This requires the API server to identify itself to the etcd server using a client certificate and key.", + "ImpactStatement": "TLS and client certificate authentication must be configured for etcd.", + "RemediationProcedure": "Follow the Kubernetes documentation and set up the TLS connection between the apiserver and etcd. Then, edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the master node and set the etcd certificate and key file parameters. ``` --etcd-certfile= --etcd-keyfile= ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--etcd-certfile` and `--etcd-keyfile` arguments exist and they are set as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/", + "DefaultValue": "By default, `--etcd-certfile` and `--etcd-keyfile` arguments are not set" + } + ] + }, + { + "Id": "1.2.24", + "Description": "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate", + "Checks": [ + "apiserver_tls_config" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Setup TLS connection on the API server.", + "RationaleStatement": "API server communication contains sensitive parameters that should remain encrypted in transit. Configure the API server to serve only HTTPS traffic.", + "ImpactStatement": "TLS and client certificate authentication must be configured for your Kubernetes cluster deployment.", + "RemediationProcedure": "Follow the Kubernetes documentation and set up the TLS connection on the apiserver. Then, edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the master node and set the TLS certificate and private key file parameters. ``` --tls-cert-file= --tls-private-key-file= ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--tls-cert-file` and `--tls-private-key-file` arguments exist and they are set as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://github.com/kelseyhightower/docker-kubernetes-tls-guide", + "DefaultValue": "By default, `--tls-cert-file` and `--tls-private-key-file` are presented and created for use." + } + ] + }, + { + "Id": "1.2.25", + "Description": "Ensure that the --client-ca-file argument is set as appropriate", + "Checks": [ + "apiserver_client_ca_file_set" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Setup TLS connection on the API server.", + "RationaleStatement": "API server communication contains sensitive parameters that should remain encrypted in transit. Configure the API server to serve only HTTPS traffic. If `--client-ca-file` argument is set, any request presenting a client certificate signed by one of the authorities in the `client-ca-file` is authenticated with an identity corresponding to the CommonName of the client certificate.", + "ImpactStatement": "TLS and client certificate authentication must be configured for your Kubernetes cluster deployment.", + "RemediationProcedure": "Follow the Kubernetes documentation and set up the TLS connection on the apiserver. Then, edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the master node and set the client certificate authority file. ``` --client-ca-file= ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--client-ca-file` argument exists and it is set as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://github.com/kelseyhightower/docker-kubernetes-tls-guide", + "DefaultValue": "By default, `--client-ca-file` argument is not set." + } + ] + }, + { + "Id": "1.2.26", + "Description": "Ensure that the --etcd-cafile argument is set as appropriate", + "Checks": [ + "apiserver_etcd_cafile_set" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "etcd should be configured to make use of TLS encryption for client connections.", + "RationaleStatement": "etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be protected by client authentication. This requires the API server to identify itself to the etcd server using a SSL Certificate Authority file.", + "ImpactStatement": "TLS and client certificate authentication must be configured for etcd.", + "RemediationProcedure": "Follow the Kubernetes documentation and set up the TLS connection between the apiserver and etcd. Then, edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the master node and set the etcd certificate authority file parameter. ``` --etcd-cafile= ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--etcd-cafile` argument exists and it is set as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/", + "DefaultValue": "By default, `--etcd-cafile` is not set." + } + ] + }, + { + "Id": "1.2.27", + "Description": "Ensure that the --encryption-provider-config argument is set as appropriate", + "Checks": [ + "apiserver_encryption_provider_config_set" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Encrypt etcd key-value store.", + "RationaleStatement": "etcd is a highly available key-value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be encrypted at rest to avoid any disclosures.", + "ImpactStatement": "None", + "RemediationProcedure": "Follow the Kubernetes documentation and configure a `EncryptionConfig` file. Then, edit the API server pod specification file `/etc/kubernetes/manifests/kube-apiserver.yaml` on the master node and set the `--encryption-provider-config` parameter to the path of that file: ``` --encryption-provider-config= ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--encryption-provider-config` argument is set to a `EncryptionConfig` file. Additionally, ensure that the `EncryptionConfig` file has all the desired `resources` covered especially any secrets.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/:https://acotten.com/post/kube17-security:https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://github.com/kubernetes/enhancements/issues/92", + "DefaultValue": "By default, `--encryption-provider-config` is not set." + } + ] + }, + { + "Id": "1.2.28", + "Description": "Ensure that encryption providers are appropriately configured", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Where `etcd` encryption is used, appropriate providers should be configured.", + "RationaleStatement": "Where `etcd` encryption is used, it is important to ensure that the appropriate set of encryption providers is used. Currently, the `aescbc`, `kms`, and `secretbox` are likely to be appropriate options.", + "ImpactStatement": "None", + "RemediationProcedure": "Follow the Kubernetes documentation and configure a `EncryptionConfig` file. In this file, choose `aescbc`, `kms`, or `secretbox` as the encryption provider.", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Get the `EncryptionConfig` file set for `--encryption-provider-config` argument. Verify that `aescbc`, `kms`, or `secretbox` is set as the encryption provider for all the desired `resources`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/:https://acotten.com/post/kube17-security:https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://github.com/kubernetes/enhancements/issues/92:https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/#providers", + "DefaultValue": "By default, no encryption provider is set." + } + ] + }, + { + "Id": "1.2.29", + "Description": "Ensure that the API Server only makes use of Strong Cryptographic Ciphers", + "Checks": [ + "apiserver_strong_ciphers_only" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Ensure that the API server is configured to only use strong cryptographic ciphers.", + "RationaleStatement": "TLS ciphers have had a number of known vulnerabilities and weaknesses, which can reduce the protection provided by them. By default Kubernetes supports a number of TLS cipher suites including some that have security concerns, weakening the protection provided.", + "ImpactStatement": "API server clients that cannot support modern cryptographic ciphers will not be able to make connections to the API server.", + "RemediationProcedure": "Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the below parameter. ``` --tls-cipher-suites=TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256. ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the `--tls-cipher-suites` argument is set as outlined in the remediation procedure below.", + "AdditionalInformation": "Insecure values: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_RC4_128_SHA.", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/:https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites", + "DefaultValue": "By default the Kubernetes API server supports a wide range of TLS ciphers" + } + ] + }, + { + "Id": "1.2.30", + "Description": "Ensure that the --service-account-extend-token-expiration parameter is set to false", + "Checks": [], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.2 API Server", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "By default Kubernetes extends service account token lifetimes to one year to aid in transition from the legacy token settings.", + "RationaleStatement": "This default setting is not ideal for security as it ignores other settings related to maximum token lifetime and means that a lost or stolen credential could be valid for an extended period of time.", + "ImpactStatement": "Disabling this setting means that the service account token expiry set in the cluster will be enforced, and service account tokens will expire at the end of that time frame.", + "RemediationProcedure": "Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml on the Control Plane node and set the --service-account-extend-token-expiration parameter to false. ``` --service-account-extend-token-expiration=false ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-apiserver ``` Verify that the --service-account-extend-token-expiration argument is set to false.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/", + "DefaultValue": "By default, this parameter is set to true" + } + ] + }, + { + "Id": "1.3.1", + "Description": "Ensure that the --terminated-pod-gc-threshold argument is set as appropriate", + "Checks": [ + "controllermanager_garbage_collection" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.3 Controller Manager", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Activate garbage collector on pod termination, as appropriate.", + "RationaleStatement": "Garbage collection is important to ensure sufficient resource availability and avoiding degraded performance and availability. In the worst case, the system might crash or just be unusable for a long period of time. The current setting for garbage collection is 12,500 terminated pods which might be too high for your system to sustain. Based on your system resources and tests, choose an appropriate threshold value to activate garbage collection.", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the Controller Manager pod specification file `/etc/kubernetes/manifests/kube-controller-manager.yaml` on the Control Plane node and set the `--terminated-pod-gc-threshold` to an appropriate threshold, for example: ``` --terminated-pod-gc-threshold=10 ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-controller-manager ``` Verify that the `--terminated-pod-gc-threshold` argument is set as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-controller-manager/:https://github.com/kubernetes/kubernetes/issues/28484", + "DefaultValue": "By default, `--terminated-pod-gc-threshold` is set to `12500`." + } + ] + }, + { + "Id": "1.3.2", + "Description": "Ensure that the --profiling argument is set to false", + "Checks": [ + "controllermanager_disable_profiling" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.3 Controller Manager", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Disable profiling, if not needed.", + "RationaleStatement": "Profiling allows for the identification of specific performance bottlenecks. It generates a significant amount of program data that could potentially be exploited to uncover system and program details. If you are not experiencing any bottlenecks and do not need the profiler for troubleshooting purposes, it is recommended to turn it off to reduce the potential attack surface.", + "ImpactStatement": "Profiling information would not be available.", + "RemediationProcedure": "Edit the Controller Manager pod specification file `/etc/kubernetes/manifests/kube-controller-manager.yaml` on the Control Plane node and set the below parameter. ``` --profiling=false ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-controller-manager ``` Verify that the `--profiling` argument is set to `false`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-controller-manager/:https://github.com/kubernetes/community/blob/master/contributors/devel/profiling.md", + "DefaultValue": "By default, profiling is enabled." + } + ] + }, + { + "Id": "1.3.3", + "Description": "Ensure that the --use-service-account-credentials argument is set to true", + "Checks": [ + "controllermanager_service_account_credentials" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.3 Controller Manager", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Use individual service account credentials for each controller.", + "RationaleStatement": "The controller manager creates a service account per controller in the `kube-system` namespace, generates a credential for it, and builds a dedicated API client with that service account credential for each controller loop to use. Setting the `--use-service-account-credentials` to `true` runs each control loop within the controller manager using a separate service account credential. When used in combination with RBAC, this ensures that the control loops run with the minimum permissions required to perform their intended tasks.", + "ImpactStatement": "Whatever authorizer is configured for the cluster, it must grant sufficient permissions to the service accounts to perform their intended tasks. When using the RBAC authorizer, those roles are created and bound to the appropriate service accounts in the `kube-system` namespace automatically with default roles and rolebindings that are auto-reconciled on startup. If using other authorization methods (ABAC, Webhook, etc), the cluster deployer is responsible for granting appropriate permissions to the service accounts (the required permissions can be seen by inspecting the `controller-roles.yaml` and `controller-role-bindings.yaml` files for the RBAC roles.", + "RemediationProcedure": "Edit the Controller Manager pod specification file `/etc/kubernetes/manifests/kube-controller-manager.yaml` on the Control Plane node to set the below parameter. ``` --use-service-account-credentials=true ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-controller-manager ``` Verify that the `--use-service-account-credentials` argument is set to `true`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-controller-manager/:https://kubernetes.io/docs/admin/service-accounts-admin/:https://github.com/kubernetes/kubernetes/blob/release-1.6/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml:https://github.com/kubernetes/kubernetes/blob/release-1.6/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml:https://kubernetes.io/docs/admin/authorization/rbac/#controller-roles", + "DefaultValue": "By default, `--use-service-account-credentials` is set to false." + } + ] + }, + { + "Id": "1.3.4", + "Description": "Ensure that the --service-account-private-key-file argument is set as appropriate", + "Checks": [ + "controllermanager_service_account_private_key_file" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.3 Controller Manager", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Explicitly set a service account private key file for service accounts on the controller manager.", + "RationaleStatement": "To ensure that keys for service account tokens can be rotated as needed, a separate public/private key pair should be used for signing service account tokens. The private key should be specified to the controller manager with `--service-account-private-key-file` as appropriate.", + "ImpactStatement": "You would need to securely maintain the key file and rotate the keys based on your organization's key rotation policy.", + "RemediationProcedure": "Edit the Controller Manager pod specification file `/etc/kubernetes/manifests/kube-controller-manager.yaml` on the Control Plane node and set the `--service-account-private-key-file` parameter to the private key file for service accounts. ``` --service-account-private-key-file= ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-controller-manager ``` Verify that the `--service-account-private-key-file` argument is set as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-controller-manager/", + "DefaultValue": "By default, `--service-account-private-key-file` it not set." + } + ] + }, + { + "Id": "1.3.5", + "Description": "Ensure that the --root-ca-file argument is set as appropriate", + "Checks": [ + "controllermanager_root_ca_file_set" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.3 Controller Manager", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Allow pods to verify the API server's serving certificate before establishing connections.", + "RationaleStatement": "Processes running within pods that need to contact the API server must verify the API server's serving certificate. Failing to do so could be a subject to man-in-the-middle attacks. Providing the root certificate for the API server's serving certificate to the controller manager with the `--root-ca-file` argument allows the controller manager to inject the trusted bundle into pods so that they can verify TLS connections to the API server.", + "ImpactStatement": "You need to setup and maintain root certificate authority file.", + "RemediationProcedure": "Edit the Controller Manager pod specification file `/etc/kubernetes/manifests/kube-controller-manager.yaml` on the Control Plane node and set the `--root-ca-file` parameter to the certificate bundle file`. ``` --root-ca-file= ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-controller-manager ``` Verify that the `--root-ca-file` argument exists and is set to a certificate bundle file containing the root certificate for the API server's serving certificate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-controller-manager/:https://github.com/kubernetes/kubernetes/issues/11000", + "DefaultValue": "By default, `--root-ca-file` is not set." + } + ] + }, + { + "Id": "1.3.6", + "Description": "Ensure that the RotateKubeletServerCertificate argument is set to true", + "Checks": [ + "controllermanager_rotate_kubelet_server_cert" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.3 Controller Manager", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Enable kubelet server certificate rotation on controller-manager.", + "RationaleStatement": "`RotateKubeletServerCertificate` causes the kubelet to both request a serving certificate after bootstrapping its client credentials and rotate the certificate as its existing credentials expire. This automated periodic rotation ensures that the there are no downtimes due to expired certificates and thus addressing availability in the CIA security triad. Note: This recommendation only applies if you let kubelets get their certificates from the API server. In case your kubelet certificates come from an outside authority/tool (e.g. Vault) then you need to take care of rotation yourself.", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the Controller Manager pod specification file `/etc/kubernetes/manifests/kube-controller-manager.yaml` on the Control Plane node and set the `--feature-gates` parameter to include `RotateKubeletServerCertificate=true`. ``` --feature-gates=RotateKubeletServerCertificate=true ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-controller-manager ``` Verify that `RotateKubeletServerCertificate` argument exists and is set to `true`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet-tls-bootstrapping/#approval-controller:https://github.com/kubernetes/features/issues/267:https://github.com/kubernetes/kubernetes/pull/45059:https://kubernetes.io/docs/admin/kube-controller-manager/", + "DefaultValue": "By default, `RotateKubeletServerCertificate` is set to true this recommendation verifies that it has not been disabled." + } + ] + }, + { + "Id": "1.3.7", + "Description": "Ensure that the --bind-address argument is set to 127.0.0.1", + "Checks": [ + "controllermanager_bind_address" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.3 Controller Manager", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Do not bind the Controller Manager service to non-loopback insecure addresses.", + "RationaleStatement": "The Controller Manager API service which runs on port 10252/TCP by default is used for health and metrics information and is available without authentication or encryption. As such it should only be bound to a localhost interface, to minimize the cluster's attack surface", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the Controller Manager pod specification file `/etc/kubernetes/manifests/kube-controller-manager.yaml` on the Control Plane node and ensure the correct value for the `--bind-address` parameter", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-controller-manager ``` Verify that the `--bind-address` argument is set to 127.0.0.1", + "AdditionalInformation": "Although the current Kubernetes documentation site says that `--address` is deprecated in favour of `--bind-address` Kubeadm 1.11 still makes use of `--address`", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/", + "DefaultValue": "By default, the `--bind-address` parameter is set to 0.0.0.0" + } + ] + }, + { + "Id": "1.4.1", + "Description": "Ensure that the --profiling argument is set to false", + "Checks": [ + "scheduler_profiling" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.4 Scheduler", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Disable profiling, if not needed.", + "RationaleStatement": "Profiling allows for the identification of specific performance bottlenecks. It generates a significant amount of program data that could potentially be exploited to uncover system and program details. If you are not experiencing any bottlenecks and do not need the profiler for troubleshooting purposes, it is recommended to turn it off to reduce the potential attack surface.", + "ImpactStatement": "Profiling information would not be available.", + "RemediationProcedure": "Edit the Scheduler pod specification file `/etc/kubernetes/manifests/kube-scheduler.yaml` file on the Control Plane node and set the below parameter. ``` --profiling=false ```", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-scheduler ``` Verify that the `--profiling` argument is set to `false`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-scheduler/:https://github.com/kubernetes/community/blob/master/contributors/devel/profiling.md", + "DefaultValue": "By default, profiling is enabled." + } + ] + }, + { + "Id": "1.4.2", + "Description": "Ensure that the --bind-address argument is set to 127.0.0.1", + "Checks": [ + "scheduler_bind_address" + ], + "Attributes": [ + { + "Section": "1 Control Plane Components", + "SubSection": "1.4 Scheduler", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Do not bind the scheduler service to non-loopback insecure addresses.", + "RationaleStatement": "The Scheduler API service which runs on port 10251/TCP by default is used for health and metrics information and is available without authentication or encryption. As such it should only be bound to a localhost interface, to minimize the cluster's attack surface", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the Scheduler pod specification file `/etc/kubernetes/manifests/kube-scheduler.yaml` on the Control Plane node and ensure the correct value for the `--bind-address` parameter", + "AuditProcedure": "Run the following command on the Control Plane node: ``` ps -ef | grep kube-scheduler ``` Verify that the `--bind-address` argument is set to 127.0.0.1", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-scheduler/", + "DefaultValue": "By default, the `--bind-address` parameter is set to 0.0.0.0" + } + ] + }, + { + "Id": "2.1", + "Description": "Ensure that the --cert-file and --key-file arguments are set as appropriate", + "Checks": [ + "etcd_tls_encryption" + ], + "Attributes": [ + { + "Section": "2 etcd", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Configure TLS encryption for the etcd service.", + "RationaleStatement": "etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be encrypted in transit.", + "ImpactStatement": "Client connections only over TLS would be served.", + "RemediationProcedure": "Follow the etcd service documentation and configure TLS encryption. Then, edit the etcd pod specification file `/etc/kubernetes/manifests/etcd.yaml` on the master node and set the below parameters. ``` --cert-file= --key-file= ```", + "AuditProcedure": "Run the following command on the etcd server node ``` ps -ef | grep etcd ``` Verify that the `--cert-file` and the `--key-file` arguments are set as appropriate.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd/docs/latest/op-guide/security.html:https://kubernetes.io/docs/admin/etcd/", + "DefaultValue": "By default, TLS encryption is not set." + } + ] + }, + { + "Id": "2.2", + "Description": "Ensure that the --cert-file and --key-file arguments are set as appropriate", + "Checks": [ + "etcd_tls_encryption" + ], + "Attributes": [ + { + "Section": "2 etcd", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Configure TLS encryption for the etcd service.", + "RationaleStatement": "etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be encrypted in transit.", + "ImpactStatement": "Client connections only over TLS would be served.", + "RemediationProcedure": "Follow the etcd service documentation and configure TLS encryption. Then, edit the etcd pod specification file `/etc/kubernetes/manifests/etcd.yaml` on the master node and set the below parameters. ``` --cert-file= --key-file= ```", + "AuditProcedure": "Run the following command on the etcd server node ``` ps -ef | grep etcd ``` Verify that the `--cert-file` and the `--key-file` arguments are set as appropriate.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd/docs/latest/op-guide/security.html:https://kubernetes.io/docs/admin/etcd/", + "DefaultValue": "By default, TLS encryption is not set." + } + ] + }, + { + "Id": "2.3", + "Description": "Ensure that the --client-cert-auth argument is set to true", + "Checks": [ + "etcd_client_cert_auth" + ], + "Attributes": [ + { + "Section": "2 etcd", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Enable client authentication on etcd service.", + "RationaleStatement": "etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should not be available to unauthenticated clients. You should enable the client authentication via valid certificates to secure the access to the etcd service.", + "ImpactStatement": "All clients attempting to access the etcd server will require a valid client certificate.", + "RemediationProcedure": "Edit the etcd pod specification file `/etc/kubernetes/manifests/etcd.yaml` on the master node and set the below parameter. ``` --client-cert-auth=true ```", + "AuditProcedure": "Run the following command on the etcd server node: ``` ps -ef | grep etcd ``` Verify that the `--client-cert-auth` argument is set to `true`.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd/docs/latest/op-guide/security.html:https://kubernetes.io/docs/admin/etcd/:https://coreos.com/etcd/docs/latest/op-guide/configuration.html#client-cert-auth", + "DefaultValue": "By default, the etcd service can be queried by unauthenticated clients." + } + ] + }, + { + "Id": "2.4", + "Description": "Ensure that the --auto-tls argument is not set to true", + "Checks": [ + "etcd_no_auto_tls" + ], + "Attributes": [ + { + "Section": "2 etcd", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Do not use self-signed certificates for TLS.", + "RationaleStatement": "etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should not be available to unauthenticated clients. You should enable the client authentication via valid certificates to secure the access to the etcd service.", + "ImpactStatement": "Clients will not be able to use self-signed certificates for TLS.", + "RemediationProcedure": "Edit the etcd pod specification file `/etc/kubernetes/manifests/etcd.yaml` on the master node and either remove the `--auto-tls` parameter or set it to `false`. ``` --auto-tls=false ```", + "AuditProcedure": "Run the following command on the etcd server node: ``` ps -ef | grep etcd ``` Verify that if the `--auto-tls` argument exists, it is not set to `true`.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd/docs/latest/op-guide/security.html:https://kubernetes.io/docs/admin/etcd/:https://coreos.com/etcd/docs/latest/op-guide/configuration.html#auto-tls", + "DefaultValue": "By default, `--auto-tls` is set to `false`." + } + ] + }, + { + "Id": "2.5", + "Description": "Ensure that the --peer-cert-file and --peer-key-file arguments are set as appropriate", + "Checks": [ + "etcd_peer_tls_config" + ], + "Attributes": [ + { + "Section": "2 etcd", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "etcd should be configured to make use of TLS encryption for peer connections.", + "RationaleStatement": "etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be encrypted in transit and also amongst peers in the etcd clusters.", + "ImpactStatement": "etcd cluster peers would need to set up TLS for their communication.", + "RemediationProcedure": "Follow the etcd service documentation and configure peer TLS encryption as appropriate for your etcd cluster. Then, edit the etcd pod specification file `/etc/kubernetes/manifests/etcd.yaml` on the master node and set the below parameters. ``` --peer-client-file= --peer-key-file= ```", + "AuditProcedure": "Run the following command on the etcd server node: ``` ps -ef | grep etcd ``` Verify that the `--peer-cert-file` and `--peer-key-file` arguments are set as appropriate. **Note:** This recommendation is applicable only for etcd clusters. If you are using only one etcd server in your environment then this recommendation is not applicable.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd/docs/latest/op-guide/security.html:https://kubernetes.io/docs/admin/etcd/", + "DefaultValue": "**Note:** This recommendation is applicable only for etcd clusters. If you are using only one etcd server in your environment then this recommendation is not applicable. By default, peer communication over TLS is not configured." + } + ] + }, + { + "Id": "2.6", + "Description": "Ensure that the --peer-client-cert-auth argument is set to true", + "Checks": [ + "etcd_peer_client_cert_auth" + ], + "Attributes": [ + { + "Section": "2 etcd", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "etcd should be configured for peer authentication.", + "RationaleStatement": "etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be accessible only by authenticated etcd peers in the etcd cluster.", + "ImpactStatement": "All peers attempting to communicate with the etcd server will require a valid client certificate for authentication.", + "RemediationProcedure": "Edit the etcd pod specification file `/etc/kubernetes/manifests/etcd.yaml` on the master node and set the below parameter. ``` --peer-client-cert-auth=true ```", + "AuditProcedure": "Run the following command on the etcd server node: ``` ps -ef | grep etcd ``` Verify that the `--peer-client-cert-auth` argument is set to `true`. **Note:** This recommendation is applicable only for etcd clusters. If you are using only one etcd server in your environment then this recommendation is not applicable.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd/docs/latest/op-guide/security.html:https://kubernetes.io/docs/admin/etcd/:https://coreos.com/etcd/docs/latest/op-guide/configuration.html#peer-client-cert-auth", + "DefaultValue": "**Note:** This recommendation is applicable only for etcd clusters. If you are using only one etcd server in your environment then this recommendation is not applicable. By default, `--peer-client-cert-auth` argument is set to `false`." + } + ] + }, + { + "Id": "2.7", + "Description": "Ensure that the --peer-auto-tls argument is not set to true", + "Checks": [ + "etcd_no_peer_auto_tls" + ], + "Attributes": [ + { + "Section": "2 etcd", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Do not use automatically generated self-signed certificates for TLS connections between peers.", + "RationaleStatement": "etcd is a highly-available key value store used by Kubernetes deployments for persistent storage of all of its REST API objects. These objects are sensitive in nature and should be accessible only by authenticated etcd peers in the etcd cluster. Hence, do not use self-signed certificates for authentication.", + "ImpactStatement": "All peers attempting to communicate with the etcd server will require a valid client certificate for authentication.", + "RemediationProcedure": "Edit the etcd pod specification file `/etc/kubernetes/manifests/etcd.yaml` on the master node and either remove the `--peer-auto-tls` parameter or set it to `false`. ``` --peer-auto-tls=false ```", + "AuditProcedure": "Run the following command on the etcd server node: ``` ps -ef | grep etcd ``` Verify that if the `--peer-auto-tls` argument exists, it is not set to `true`. **Note:** This recommendation is applicable only for etcd clusters. If you are using only one etcd server in your environment then this recommendation is not applicable.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd/docs/latest/op-guide/security.html:https://kubernetes.io/docs/admin/etcd/:https://coreos.com/etcd/docs/latest/op-guide/configuration.html#peer-auto-tls", + "DefaultValue": "**Note:** This recommendation is applicable only for etcd clusters. If you are using only one etcd server in your environment then this recommendation is not applicable. By default, `--peer-auto-tls` argument is set to `false`." + } + ] + }, + { + "Id": "2.8", + "Description": "Ensure that a unique Certificate Authority is used for etcd", + "Checks": [ + "etcd_unique_ca" + ], + "Attributes": [ + { + "Section": "2 etcd", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Use a different certificate authority for etcd from the one used for Kubernetes.", + "RationaleStatement": "etcd is a highly available key-value store used by Kubernetes deployments for persistent storage of all of its REST API objects. Its access should be restricted to specifically designated clients and peers only. Authentication to etcd is based on whether the certificate presented was issued by a trusted certificate authority. There is no checking of certificate attributes such as common name or subject alternative name. As such, if any attackers were able to gain access to any certificate issued by the trusted certificate authority, they would be able to gain full access to the etcd database.", + "ImpactStatement": "Additional management of the certificates and keys for the dedicated certificate authority will be required.", + "RemediationProcedure": "Follow the etcd documentation and create a dedicated certificate authority setup for the etcd service. Then, edit the etcd pod specification file `/etc/kubernetes/manifests/etcd.yaml` on the master node and set the below parameter. ``` --trusted-ca-file= ```", + "AuditProcedure": "Review the CA used by the etcd environment and ensure that it does not match the CA certificate file used for the management of the overall Kubernetes cluster. Run the following command on the master node: ``` ps -ef | grep etcd ``` Note the file referenced by the `--trusted-ca-file` argument. Run the following command on the master node: ``` ps -ef | grep apiserver ``` Verify that the file referenced by the `--client-ca-file` for apiserver is different from the `--trusted-ca-file` used by etcd.", + "AdditionalInformation": "", + "References": "https://coreos.com/etcd/docs/latest/op-guide/security.html", + "DefaultValue": "By default, no etcd certificate is created and used." + } + ] + }, + { + "Id": "3.1.1", + "Description": "Client certificate authentication should not be used for users", + "Checks": [], + "Attributes": [ + { + "Section": "3 Control Plane Configuration", + "SubSection": "3.1 Authentication and Authorization", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Kubernetes provides the option to use client certificates for user authentication. However as there is no way to revoke these certificates when a user leaves an organization or loses their credential, they are not suitable for this purpose. It is not possible to fully disable client certificate use within a cluster as it is used for component to component authentication.", + "RationaleStatement": "With any authentication mechanism the ability to revoke credentials if they are compromised or no longer required, is a key control. Kubernetes client certificate authentication does not allow for this due to a lack of support for certificate revocation.", + "ImpactStatement": "External mechanisms for authentication generally require additional software to be deployed.", + "RemediationProcedure": "Alternative mechanisms provided by Kubernetes such as the use of OIDC should be implemented in place of client certificates.", + "AuditProcedure": "Review user access to the cluster and ensure that users are not making use of Kubernetes client certificate authentication.", + "AdditionalInformation": "The lack of certificate revocation was flagged up as a high risk issue in the recent Kubernetes security audit. Without this feature, client certificate authentication is not suitable for end users.", + "References": "", + "DefaultValue": "Client certificate authentication is enabled by default." + } + ] + }, + { + "Id": "3.1.2", + "Description": "Service account token authentication should not be used for users", + "Checks": [], + "Attributes": [ + { + "Section": "3 Control Plane Configuration", + "SubSection": "3.1 Authentication and Authorization", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Kubernetes provides service account tokens which are intended for use by workloads running in the Kubernetes cluster, for authentication to the API server. These tokens are not designed for use by end-users and do not provide for features such as revocation or expiry, making them insecure. A newer version of the feature (Bound service account token volumes) does introduce expiry but still does not allow for specific revocation.", + "RationaleStatement": "With any authentication mechanism the ability to revoke credentials if they are compromised or no longer required, is a key control. Service account token authentication does not allow for this due to the use of JWT tokens as an underlying technology.", + "ImpactStatement": "External mechanisms for authentication generally require additional software to be deployed.", + "RemediationProcedure": "Alternative mechanisms provided by Kubernetes such as the use of OIDC should be implemented in place of service account tokens.", + "AuditProcedure": "Review user access to the cluster and ensure that users are not making use of service account token authentication.", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "Service account token authentication is enabled by default." + } + ] + }, + { + "Id": "3.1.3", + "Description": "Bootstrap token authentication should not be used for users", + "Checks": [], + "Attributes": [ + { + "Section": "3 Control Plane Configuration", + "SubSection": "3.1 Authentication and Authorization", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Kubernetes provides bootstrap tokens which are intended for use by new nodes joining the cluster These tokens are not designed for use by end-users they are specifically designed for the purpose of bootstrapping new nodes and not for general authentication", + "RationaleStatement": "Bootstrap tokens are not intended for use as a general authentication mechanism and impose constraints on user and group naming that do not facilitate good RBAC design. They also cannot be used with MFA resulting in a weak authentication mechanism being available.", + "ImpactStatement": "External mechanisms for authentication generally require additional software to be deployed.", + "RemediationProcedure": "Alternative mechanisms provided by Kubernetes such as the use of OIDC should be implemented in place of bootstrap tokens.", + "AuditProcedure": "Review user access to the cluster and ensure that users are not making use of bootstrap token authentication.", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "Bootstrap token authentication is not enabled by default and requires an API server parameter to be set." + } + ] + }, + { + "Id": "3.2.1", + "Description": "Ensure that a minimal audit policy is created", + "Checks": [], + "Attributes": [ + { + "Section": "3 Control Plane Configuration", + "SubSection": "3.2 Logging", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Kubernetes can audit the details of requests made to the API server. The `--audit-policy-file` flag must be set for this logging to be enabled.", + "RationaleStatement": "Logging is an important detective control for all systems, to detect potential unauthorised access.", + "ImpactStatement": "Audit logs will be created on the master nodes, which will consume disk space. Care should be taken to avoid generating too large volumes of log information as this could impact the available of the cluster nodes.", + "RemediationProcedure": "Create an audit policy file for your cluster.", + "AuditProcedure": "Run the following command on one of the cluster master nodes: ``` ps -ef | grep kube-apiserver ``` Verify that the `--audit-policy-file` is set. Review the contents of the file specified and ensure that it contains a valid audit policy.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/tasks/debug-application-cluster/audit/", + "DefaultValue": "Unless the `--audit-policy-file` flag is specified, no auditing will be carried out." + } + ] + }, + { + "Id": "3.2.2", + "Description": "Ensure that the audit policy covers key security concerns", + "Checks": [], + "Attributes": [ + { + "Section": "3 Control Plane Configuration", + "SubSection": "3.2 Logging", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Ensure that the audit policy created for the cluster covers key security concerns.", + "RationaleStatement": "Security audit logs should cover access and modification of key resources in the cluster, to enable them to form an effective part of a security environment.", + "ImpactStatement": "Increasing audit logging will consume resources on the nodes or other log destination.", + "RemediationProcedure": "Consider modification of the audit policy in use on the cluster to include these items, at a minimum.", + "AuditProcedure": "Review the audit policy provided for the cluster and ensure that it covers at least the following areas :- - Access to Secrets managed by the cluster. Care should be taken to only log Metadata for requests to Secrets, ConfigMaps, and TokenReviews, in order to avoid the risk of logging sensitive data. - Modification of `pod` and `deployment` objects. - Use of `pods/exec`, `pods/portforward`, `pods/proxy` and `services/proxy`. For most requests, minimally logging at the Metadata level is recommended (the most basic level of logging).", + "AdditionalInformation": "", + "References": "https://github.com/k8scop/k8s-security-dashboard/blob/master/configs/kubernetes/adv-audit.yaml:https://kubernetes.io/docs/tasks/debug-application-cluster/audit/#audit-policy:https://github.com/kubernetes/kubernetes/blob/master/cluster/gce/gci/configure-helper.sh#L735", + "DefaultValue": "By default Kubernetes clusters do not log audit information." + } + ] + }, + { + "Id": "4.1.1", + "Description": "Ensure that the kubelet service file permissions are set to 600 or more restrictive", + "Checks": [ + "kubelet_service_file_permissions" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.1 Worker Node Configuration Files", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `kubelet` service file has permissions of `600` or more restrictive.", + "RationaleStatement": "The `kubelet` service file controls various parameters that set the behavior of the `kubelet` service in the worker node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the each worker node. For example, ``` chmod 600 /etc/systemd/system/kubelet.service.d/kubeadm.conf ```", + "AuditProcedure": "Automated AAC auditing has been modified to allow CIS-CAT to input a variable for the / of the kubelet service config file. Please set $kubelet_service_config= based on the file location on your system for example: ``` export kubelet_service_config=/etc/systemd/system/kubelet.service.d/kubeadm.conf ``` To perform the audit manually: Run the below command (based on the file location on your system) on the each worker node. For example, ``` stat -c %a /etc/systemd/system/kubelet.service.d/10-kubeadm.conf ``` Verify that the permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet/:https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#44-joining-your-nodes:https://kubernetes.io/docs/admin/kubeadm/#kubelet-drop-in", + "DefaultValue": "By default, the `kubelet` service file has permissions of `640`." + } + ] + }, + { + "Id": "4.1.2", + "Description": "Ensure that the kubelet service file ownership is set to root:root", + "Checks": [ + "kubelet_service_file_ownership_root" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.1 Worker Node Configuration Files", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `kubelet` service file ownership is set to `root:root`.", + "RationaleStatement": "The `kubelet` service file controls various parameters that set the behavior of the `kubelet` service in the worker node. You should set its file ownership to maintain the integrity of the file. The file should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the each worker node. For example, ``` chown root:root /etc/systemd/system/kubelet.service.d/kubeadm.conf ```", + "AuditProcedure": "Automated AAC auditing has been modified to allow CIS-CAT to input a variable for the / of the kubelet service config file. Please set $kubelet_service_config= based on the file location on your system for example: ``` export kubelet_service_config=/etc/systemd/system/kubelet.service.d/kubeadm.conf ``` To perform the audit manually: Run the below command (based on the file location on your system) on the each worker node. For example, ``` stat -c %U:%G /etc/systemd/system/kubelet.service.d/10-kubeadm.conf ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet/:https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#44-joining-your-nodes:https://kubernetes.io/docs/admin/kubeadm/#kubelet-drop-in", + "DefaultValue": "By default, `kubelet` service file ownership is set to `root:root`." + } + ] + }, + { + "Id": "4.1.3", + "Description": "If proxy kubeconfig file exists ensure permissions are set to 600 or more restrictive", + "Checks": [], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.1 Worker Node Configuration Files", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "If `kube-proxy` is running, and if it is using a file-based kubeconfig file, ensure that the proxy kubeconfig file has permissions of `600` or more restrictive.", + "RationaleStatement": "The `kube-proxy` kubeconfig file controls various parameters of the `kube-proxy` service in the worker node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system. It is possible to run `kube-proxy` with the kubeconfig parameters configured as a Kubernetes ConfigMap instead of a file. In this case, there is no proxy kubeconfig file.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the each worker node. For example, ``` chmod 600 ```", + "AuditProcedure": "Find the kubeconfig file being used by `kube-proxy` by running the following command: ``` ps -ef | grep kube-proxy ``` If `kube-proxy` is running, get the kubeconfig file location from the `--kubeconfig` parameter. To perform the audit: Run the below command (based on the file location on your system) on the each worker node. For example, ``` stat -c %a ``` Verify that a file is specified and it exists with permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-proxy/", + "DefaultValue": "By default, proxy file has permissions of `640`." + } + ] + }, + { + "Id": "4.1.4", + "Description": "If proxy kubeconfig file exists ensure ownership is set to root:root", + "Checks": [], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.1 Worker Node Configuration Files", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "If `kube-proxy` is running, ensure that the file ownership of its kubeconfig file is set to `root:root`.", + "RationaleStatement": "The kubeconfig file for `kube-proxy` controls various parameters for the `kube-proxy` service in the worker node. You should set its file ownership to maintain the integrity of the file. The file should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the each worker node. For example, ``` chown root:root ```", + "AuditProcedure": "Find the kubeconfig file being used by `kube-proxy` by running the following command: ``` ps -ef | grep kube-proxy ``` If `kube-proxy` is running, get the kubeconfig file location from the `--kubeconfig` parameter. To perform the audit: Run the below command (based on the file location on your system) on the each worker node. For example, ``` stat -c %U:%G ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kube-proxy/", + "DefaultValue": "By default, `proxy` file ownership is set to `root:root`." + } + ] + }, + { + "Id": "4.1.5", + "Description": "Ensure that the --kubeconfig kubelet.conf file permissions are set to 600 or more restrictive", + "Checks": [ + "kubelet_conf_file_permissions" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.1 Worker Node Configuration Files", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `kubelet.conf` file has permissions of `600` or more restrictive.", + "RationaleStatement": "The `kubelet.conf` file is the kubeconfig file for the node, and controls various parameters that set the behavior and identity of the worker node. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the each worker node. For example, ``` chmod 600 /etc/kubernetes/kubelet.conf ```", + "AuditProcedure": "Automated AAC auditing has been modified to allow CIS-CAT to input a variable for the / of the kubelet config file. Please set $kubelet_config= based on the file location on your system for example: ``` export kubelet_config=/etc/kubernetes/kubelet.conf ``` To perform the audit manually: Run the below command (based on the file location on your system) on the each worker node. For example, ``` stat -c %a /etc/kubernetes/kubelet.conf ``` Verify that the ownership is set to `root:root`.Verify that the permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet/", + "DefaultValue": "By default, `kubelet.conf` file has permissions of `600`." + } + ] + }, + { + "Id": "4.1.6", + "Description": "Ensure that the --kubeconfig kubelet.conf file ownership is set to root:root", + "Checks": [ + "kubelet_conf_file_ownership" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.1 Worker Node Configuration Files", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that the `kubelet.conf` file ownership is set to `root:root`.", + "RationaleStatement": "The `kubelet.conf` file is the kubeconfig file for the node, and controls various parameters that set the behavior and identity of the worker node. You should set its file ownership to maintain the integrity of the file. The file should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the below command (based on the file location on your system) on the each worker node. For example, ``` chown root:root /etc/kubernetes/kubelet.conf ```", + "AuditProcedure": "Automated AAC auditing has been modified to allow CIS-CAT to input a variable for the / of the kubelet config file. Please set $kubelet_config= based on the file location on your system for example: ``` export kubelet_config=/etc/kubernetes/kubelet.conf ``` To perform the audit manually: Run the below command (based on the file location on your system) on the each worker node. For example, ``` stat -c %U:%G /etc/kubernetes/kubelet.conf ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet/", + "DefaultValue": "By default, `kubelet.conf` file ownership is set to `root:root`." + } + ] + }, + { + "Id": "4.1.7", + "Description": "Ensure that the certificate authorities file permissions are set to 644 or more restrictive", + "Checks": [], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.1 Worker Node Configuration Files", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Ensure that the certificate authorities file has permissions of `644` or more restrictive.", + "RationaleStatement": "The certificate authorities file controls the authorities used to validate API requests. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the following command to modify the file permissions of the `--client-ca-file` ``` chmod 644 ```", + "AuditProcedure": "Run the following command: ``` ps -ef | grep kubelet ``` Find the file specified by the `--client-ca-file` argument. Run the following command: ``` stat -c %a ``` Verify that the permissions are `644` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/authentication/#x509-client-certs", + "DefaultValue": "By default no `--client-ca-file` is specified." + } + ] + }, + { + "Id": "4.1.8", + "Description": "Ensure that the client certificate authorities file ownership is set to root:root", + "Checks": [], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.1 Worker Node Configuration Files", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Ensure that the certificate authorities file ownership is set to `root:root`.", + "RationaleStatement": "The certificate authorities file controls the authorities used to validate API requests. You should set its file ownership to maintain the integrity of the file. The file should be owned by `root:root`.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the following command to modify the ownership of the `--client-ca-file`. ``` chown root:root ```", + "AuditProcedure": "Run the following command: ``` ps -ef | grep kubelet ``` Find the file specified by the `--client-ca-file` argument. Run the following command: ``` stat -c %U:%G ``` Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/authentication/#x509-client-certs", + "DefaultValue": "By default no `--client-ca-file` is specified." + } + ] + }, + { + "Id": "4.1.9", + "Description": "If the kubelet config.yaml configuration file is being used validate permissions set to 600 or more restrictive", + "Checks": [ + "kubelet_config_yaml_permissions" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.1 Worker Node Configuration Files", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that if the kubelet refers to a configuration file with the `--config` argument, that file has permissions of 600 or more restrictive.", + "RationaleStatement": "The kubelet reads various parameters, including security settings, from a config file specified by the `--config` argument. If this file is specified you should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the following command (using the config file location identified in the Audit step) ``` chmod 600 /var/lib/kubelet/config.yaml ```", + "AuditProcedure": "Automated AAC auditing has been modified to allow CIS-CAT to input a variable for the / of the kubelet config yaml file. Please set $kubelet_config_yaml= based on the file location on your system for example: ``` export kubelet_config_yaml=/var/lib/kubelet/config.yaml ``` To perform the audit manually: Run the below command (based on the file location on your system) on the each worker node. For example, ``` stat -c %a /var/lib/kubelet/config.yaml ``` Verify that the permissions are `600` or more restrictive.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/", + "DefaultValue": "By default, the /var/lib/kubelet/config.yaml file as set up by `kubeadm` has permissions of 600." + } + ] + }, + { + "Id": "4.1.10", + "Description": "If the kubelet config.yaml configuration file is being used validate file ownership is set to root:root", + "Checks": [ + "kubelet_config_yaml_ownership" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.1 Worker Node Configuration Files", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Automated", + "Description": "Ensure that if the kubelet refers to a configuration file with the `--config` argument, that file is owned by root:root.", + "RationaleStatement": "The kubelet reads various parameters, including security settings, from a config file specified by the `--config` argument. If this file is specified you should restrict its file permissions to maintain the integrity of the file. The file should be owned by root:root.", + "ImpactStatement": "None", + "RemediationProcedure": "Run the following command (using the config file location identied in the Audit step) ``` chown root:root /etc/kubernetes/kubelet.conf ```", + "AuditProcedure": "Automated AAC auditing has been modified to allow CIS-CAT to input a variable for the / of the kubelet config yaml file. Please set $kubelet_config_yaml= based on the file location on your system for example: ``` export kubelet_config_yaml=/var/lib/kubelet/config.yaml ``` To perform the audit manually: Run the below command (based on the file location on your system) on the each worker node. For example, ``` stat -c %U:%G /var/lib/kubelet/config.yaml ```Verify that the ownership is set to `root:root`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/", + "DefaultValue": "By default, `/var/lib/kubelet/config.yaml` file as set up by `kubeadm` is owned by `root:root`." + } + ] + }, + { + "Id": "4.2.1", + "Description": "Ensure that the --anonymous-auth argument is set to false", + "Checks": [ + "kubelet_disable_anonymous_auth" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Automated", + "Description": "Disable anonymous requests to the Kubelet server.", + "RationaleStatement": "When enabled, requests that are not rejected by other configured authentication methods are treated as anonymous requests. These requests are then served by the Kubelet server. You should rely on authentication to authorize access and disallow anonymous requests.", + "ImpactStatement": "Anonymous requests will be rejected.", + "RemediationProcedure": "If using a Kubelet config file, edit the file to set `authentication: anonymous: enabled` to `false`. If using executable arguments, edit the kubelet service file `/etc/kubernetes/kubelet.conf` on each worker node and set the below parameter in `KUBELET_SYSTEM_PODS_ARGS` variable. ``` --anonymous-auth=false ``` Based on your system, restart the `kubelet` service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "If using a Kubelet configuration file, check that there is an entry for `authentication: anonymous: enabled` set to `false`. Run the following command on each node: ``` ps -ef | grep kubelet ``` Verify that the `--anonymous-auth` argument is set to `false`. This executable argument may be omitted, provided there is a corresponding entry set to `false` in the Kubelet config file.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet/:https://kubernetes.io/docs/admin/kubelet-authentication-authorization/#kubelet-authentication", + "DefaultValue": "By default, anonymous access is enabled." + } + ] + }, + { + "Id": "4.2.2", + "Description": "Ensure that the --authorization-mode argument is not set to AlwaysAllow", + "Checks": [ + "kubelet_authorization_mode" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Automated", + "Description": "Do not allow all requests. Enable explicit authorization.", + "RationaleStatement": "Kubelets, by default, allow all authenticated requests (even anonymous ones) without needing explicit authorization checks from the apiserver. You should restrict this behavior and only allow explicitly authorized requests.", + "ImpactStatement": "Unauthorized requests will be denied.", + "RemediationProcedure": "If using a Kubelet config file, edit the file to set `authorization: mode` to `Webhook`. If using executable arguments, edit the kubelet service file `/etc/kubernetes/kubelet.conf` on each worker node and set the below parameter in `KUBELET_AUTHZ_ARGS` variable. ``` --authorization-mode=Webhook ``` Based on your system, restart the `kubelet` service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "Run the following command on each node: ``` ps -ef | grep kubelet ``` If the `--authorization-mode` argument is present check that it is not set to `AlwaysAllow`. If it is not present check that there is a Kubelet config file specified by `--config`, and that file sets `authorization: mode` to something other than `AlwaysAllow`. It is also possible to review the running configuration of a Kubelet via the `/configz` endpoint on the Kubelet API port (typically `10250/TCP`). Accessing these with appropriate credentials will provide details of the Kubelet's configuration.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet/:https://kubernetes.io/docs/admin/kubelet-authentication-authorization/#kubelet-authentication", + "DefaultValue": "By default, `--authorization-mode` argument is set to `AlwaysAllow`." + } + ] + }, + { + "Id": "4.2.3", + "Description": "Ensure that the --client-ca-file argument is set as appropriate", + "Checks": [ + "kubelet_client_ca_file_set" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Automated", + "Description": "Enable Kubelet authentication using certificates.", + "RationaleStatement": "The connections from the apiserver to the kubelet are used for fetching logs for pods, attaching (through kubectl) to running pods, and using the kubelet’s port-forwarding functionality. These connections terminate at the kubelet’s HTTPS endpoint. By default, the apiserver does not verify the kubelet’s serving certificate, which makes the connection subject to man-in-the-middle attacks, and unsafe to run over untrusted and/or public networks. Enabling Kubelet certificate authentication ensures that the apiserver could authenticate the Kubelet before submitting any requests.", + "ImpactStatement": "You require TLS to be configured on apiserver as well as kubelets.", + "RemediationProcedure": "If using a Kubelet config file, edit the file to set `authentication: x509: clientCAFile` to the location of the client CA file. If using command line arguments, edit the kubelet service file `/etc/kubernetes/kubelet.conf` on each worker node and set the below parameter in `KUBELET_AUTHZ_ARGS` variable. ``` --client-ca-file= ``` Based on your system, restart the `kubelet` service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "Run the following command on each node: ``` ps -ef | grep kubelet ``` Verify that the `--client-ca-file` argument exists and is set to the location of the client certificate authority file. If the `--client-ca-file` argument is not present, check that there is a Kubelet config file specified by `--config`, and that the file sets `authentication: x509: clientCAFile` to the location of the client certificate authority file.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet/:https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet-authentication-authorization/", + "DefaultValue": "By default, `--client-ca-file` argument is not set." + } + ] + }, + { + "Id": "4.2.4", + "Description": "Verify that if defined, readOnlyPort is set to 0", + "Checks": [ + "kubelet_disable_read_only_port" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Disable the read-only port.", + "RationaleStatement": "The Kubelet process provides a read-only API in addition to the main Kubelet API. Unauthenticated access is provided to this read-only API which could possibly retrieve potentially sensitive information about the cluster.", + "ImpactStatement": "Removal of the read-only port will require that any service which made use of it will need to be re-configured to use the main Kubelet API.", + "RemediationProcedure": "If using a Kubelet config file, edit the file to set `readOnlyPort` to `0`. If using command line arguments, edit the kubelet service file `/etc/kubernetes/kubelet.conf` on each worker node and set the below parameter in `KUBELET_SYSTEM_PODS_ARGS` variable. ``` --read-only-port=0 ``` Based on your system, restart the `kubelet` service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "Run the following command on each node: ``` ps -ef | grep kubelet ``` Verify that the `--read-only-port` argument exists and is set to `0`. If the `--read-only-port` argument is not present, check that there is a Kubelet config file specified by `--config`. Check that if there is a `readOnlyPort` entry in the file, it is set to `0`.", + "AdditionalInformation": "https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/", + "References": "https://kubernetes.io/docs/admin/kubelet/:https://github.com/kubernetes/kubernetes/blob/6cedc0853faa118df0ba3d41b48b993422ad3df6/staging/src/k8s.io/kubelet/config/v1beta1/types.go#L142", + "DefaultValue": "" + } + ] + }, + { + "Id": "4.2.5", + "Description": "Ensure that the --streaming-connection-idle-timeout argument is not set to 0", + "Checks": [ + "kubelet_streaming_connection_timeout" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Do not disable timeouts on streaming connections.", + "RationaleStatement": "Setting idle timeouts ensures that you are protected against Denial-of-Service attacks, inactive connections and running out of ephemeral ports. **Note:** By default, `--streaming-connection-idle-timeout` is set to 4 hours which might be too high for your environment. Setting this as appropriate would additionally ensure that such streaming connections are timed out after serving legitimate use cases.", + "ImpactStatement": "Long-lived connections could be interrupted.", + "RemediationProcedure": "If using a Kubelet config file, edit the file to set `streamingConnectionIdleTimeout` to a value other than 0. If using command line arguments, edit the kubelet service file `/etc/kubernetes/kubelet.conf` on each worker node and set the below parameter in `KUBELET_SYSTEM_PODS_ARGS` variable. ``` --streaming-connection-idle-timeout=5m ``` Based on your system, restart the `kubelet` service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "Run the following command on each node: ``` ps -ef | grep kubelet ``` Verify that the `--streaming-connection-idle-timeout` argument is not set to `0`. If the argument is not present, and there is a Kubelet config file specified by `--config`, check that it does not set `streamingConnectionIdleTimeout` to 0.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet/:https://github.com/kubernetes/kubernetes/pull/18552", + "DefaultValue": "By default, `--streaming-connection-idle-timeout` is set to 4 hours." + } + ] + }, + { + "Id": "4.2.6", + "Description": "Ensure that the --make-iptables-util-chains argument is set to true", + "Checks": [ + "kubelet_manage_iptables" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Automated", + "Description": "Allow Kubelet to manage iptables.", + "RationaleStatement": "Kubelets can automatically manage the required changes to iptables based on how you choose your networking options for the pods. It is recommended to let kubelets manage the changes to iptables. This ensures that the iptables configuration remains in sync with pods networking configuration. Manually configuring iptables with dynamic pod network configuration changes might hamper the communication between pods/containers and to the outside world. You might have iptables rules too restrictive or too open.", + "ImpactStatement": "Kubelet would manage the iptables on the system and keep it in sync. If you are using any other iptables management solution, then there might be some conflicts.", + "RemediationProcedure": "If using a Kubelet config file, edit the file to set `makeIPTablesUtilChains: true`. If using command line arguments, edit the kubelet service file `/etc/kubernetes/kubelet.conf` on each worker node and remove the `--make-iptables-util-chains` argument from the `KUBELET_SYSTEM_PODS_ARGS` variable. Based on your system, restart the `kubelet` service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "Run the following command on each node: ``` ps -ef | grep kubelet ``` Verify that if the `--make-iptables-util-chains` argument exists then it is set to `true`. If the `--make-iptables-util-chains` argument does not exist, and there is a Kubelet config file specified by `--config`, verify that the file does not set `makeIPTablesUtilChains` to `false`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet/", + "DefaultValue": "By default, `--make-iptables-util-chains` argument is set to `true`." + } + ] + }, + { + "Id": "4.2.7", + "Description": "Ensure that the --hostname-override argument is not set", + "Checks": [], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Do not override node hostnames.", + "RationaleStatement": "Overriding hostnames could potentially break TLS setup between the kubelet and the apiserver. Additionally, with overridden hostnames, it becomes increasingly difficult to associate logs with a particular node and process them for security analytics. Hence, you should setup your kubelet nodes with resolvable FQDNs and avoid overriding the hostnames with IPs.", + "ImpactStatement": "Some cloud providers may require this flag to ensure that hostname matches names issued by the cloud provider. In these environments, this recommendation should not apply.", + "RemediationProcedure": "Edit the kubelet service file `/etc/systemd/system/kubelet.service.d/10-kubeadm.conf` on each worker node and remove the `--hostname-override` argument from the `KUBELET_SYSTEM_PODS_ARGS` variable. Based on your system, restart the `kubelet` service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "Run the following command on each node: ``` ps -ef | grep kubelet ``` Verify that `--hostname-override` argument does not exist. **Note** This setting is not configurable via the Kubelet config file.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet/:https://github.com/kubernetes/kubernetes/issues/22063", + "DefaultValue": "By default, `--hostname-override` argument is not set." + } + ] + }, + { + "Id": "4.2.8", + "Description": "Ensure that the eventRecordQPS argument is set to a level which ensures appropriate event capture", + "Checks": [ + "kubelet_event_record_qps" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 2 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Security relevant information should be captured. The eventRecordQPS on the Kubelet configuration can be used to limit the rate at which events are gathered and sets the maximum event creations per second. Setting this too low could result in relevant events not being logged, however the unlimited setting of `0` could result in a denial of service on the kubelet.", + "RationaleStatement": "It is important to capture all events and not restrict event creation. Events are an important source of security information and analytics that ensure that your environment is consistently monitored using the event data.", + "ImpactStatement": "Setting this parameter to `0` could result in a denial of service condition due to excessive events being created. The cluster's event processing and storage systems should be scaled to handle expected event loads.", + "RemediationProcedure": "If using a Kubelet config file, edit the file to set `eventRecordQPS:` to an appropriate level. If using command line arguments, edit the kubelet service file `/etc/systemd/system/kubelet.service.d/10-kubeadm.conf` on each worker node and set the below parameter in `KUBELET_ARGS` variable. Based on your system, restart the `kubelet` service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "Run the following command on each node: ``` sudo grep eventRecordQPS /etc/systemd/system/kubelet.service.d/10-kubeadm.conf ``` or If using command line arguments, kubelet service file is located /etc/systemd/system/kubelet.service.d/10-kubelet-args.conf ``` sudo grep eventRecordQPS /etc/systemd/system/kubelet.service.d/10-kubelet-args.conf ``` Review the value set for the argument and determine whether this has been set to an appropriate level for the cluster. If the argument does not exist, check that there is a Kubelet config file specified by `--config` and review the value in this location. If using command line arguments", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/kubelet/:https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/apis/kubeletconfig/v1beta1/types.go", + "DefaultValue": "By default, eventRecordQPS argument is set to `5`." + } + ] + }, + { + "Id": "4.2.9", + "Description": "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate", + "Checks": [ + "kubelet_tls_cert_and_key" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Setup TLS connection on the Kubelets.", + "RationaleStatement": "The connections from the apiserver to the kubelet are used for fetching logs for pods, attaching (through kubectl) to running pods, and using the kubelet’s port-forwarding functionality. These connections terminate at the kubelet’s HTTPS endpoint. By default, the apiserver does not verify the kubelet’s serving certificate, which makes the connection subject to man-in-the-middle attacks, and unsafe to run over untrusted and/or public networks.", + "ImpactStatement": "", + "RemediationProcedure": "If using a Kubelet config file, edit the file to set tlsCertFile to the location of the certificate file to use to identify this Kubelet, and tlsPrivateKeyFile to the location of the corresponding private key file. If using command line arguments, edit the kubelet service file /etc/kubernetes/kubelet.conf on each worker node and set the below parameters in KUBELET_CERTIFICATE_ARGS variable. --tls-cert-file= --tls-private-key-file= Based on your system, restart the kubelet service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "Run the following command on each node: ``` ps -ef | grep kubelet ``` Verify that the --tls-cert-file and --tls-private-key-file arguments exist and they are set as appropriate. If these arguments are not present, check that there is a Kubelet config specified by --config and that it contains appropriate settings for tlsCertFile and tlsPrivateKeyFile.", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "" + } + ] + }, + { + "Id": "4.2.10", + "Description": "Ensure that the --rotate-certificates argument is not set to false", + "Checks": [ + "kubelet_rotate_certificates" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Automated", + "Description": "Enable kubelet client certificate rotation.", + "RationaleStatement": "The `--rotate-certificates` setting causes the kubelet to rotate its client certificates by creating new CSRs as its existing credentials expire. This automated periodic rotation ensures that the there is no downtime due to expired certificates and thus addressing availability in the CIA security triad. **Note:** This recommendation only applies if you let kubelets get their certificates from the API server. In case your kubelet certificates come from an outside authority/tool (e.g. Vault) then you need to take care of rotation yourself. **Note:** This feature also require the `RotateKubeletClientCertificate` feature gate to be enabled (which is the default since Kubernetes v1.7)", + "ImpactStatement": "None", + "RemediationProcedure": "If using a Kubelet config file, edit the file to add the line `rotateCertificates: true` or remove it altogether to use the default value. If using command line arguments, edit the kubelet service file `/etc/kubernetes/kubelet.conf` on each worker node and remove `--rotate-certificates=false` argument from the `KUBELET_CERTIFICATE_ARGS` variable or set --rotate-certificates=true . Based on your system, restart the `kubelet` service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "Run the following command on each node: ``` ps -ef | grep kubelet ``` Verify that the `RotateKubeletServerCertificate` argument is not present, or is set to `true`. If the RotateKubeletServerCertificate argument is not present, verify that if there is a Kubelet config file specified by `--config`, that file does not contain `RotateKubeletServerCertificate: false`.", + "AdditionalInformation": "", + "References": "https://github.com/kubernetes/kubernetes/pull/41912:https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet-tls-bootstrapping/#kubelet-configuration:https://kubernetes.io/docs/imported/release/notes/:https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/", + "DefaultValue": "By default, kubelet client certificate rotation is enabled." + } + ] + }, + { + "Id": "4.2.11", + "Description": "Verify that the RotateKubeletServerCertificate argument is set to true", + "Checks": [], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Enable kubelet server certificate rotation.", + "RationaleStatement": "`RotateKubeletServerCertificate` causes the kubelet to both request a serving certificate after bootstrapping its client credentials and rotate the certificate as its existing credentials expire. This automated periodic rotation ensures that the there are no downtimes due to expired certificates and thus addressing availability in the CIA security triad. Note: This recommendation only applies if you let kubelets get their certificates from the API server. In case your kubelet certificates come from an outside authority/tool (e.g. Vault) then you need to take care of rotation yourself.", + "ImpactStatement": "None", + "RemediationProcedure": "Edit the kubelet service file `/etc/kubernetes/kubelet.conf` on each worker node and set the below parameter in `KUBELET_CERTIFICATE_ARGS` variable. ``` --feature-gates=RotateKubeletServerCertificate=true ``` Based on your system, restart the `kubelet` service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "Ignore this check if serverTLSBootstrap is true in the kubelet config file or if the --rotate-server-certificates parameter is set on kubelet Run the following command on each node: ``` ps -ef | grep kubelet ``` Verify that `RotateKubeletServerCertificate` argument exists and is set to `true`.", + "AdditionalInformation": "", + "References": "https://github.com/kubernetes/kubernetes/pull/45059:https://kubernetes.io/docs/admin/kubelet-tls-bootstrapping/#kubelet-configuration", + "DefaultValue": "By default, kubelet server certificate rotation is enabled." + } + ] + }, + { + "Id": "4.2.12", + "Description": "Ensure that the Kubelet only makes use of Strong Cryptographic Ciphers", + "Checks": [ + "kubelet_strong_ciphers_only" + ], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Ensure that the Kubelet is configured to only use strong cryptographic ciphers.", + "RationaleStatement": "TLS ciphers have had a number of known vulnerabilities and weaknesses, which can reduce the protection provided by them. By default Kubernetes supports a number of TLS ciphersuites including some that have security concerns, weakening the protection provided.", + "ImpactStatement": "Kubelet clients that cannot support modern cryptographic ciphers will not be able to make connections to the Kubelet API.", + "RemediationProcedure": "If using a Kubelet config file, edit the file to set `tlsCipherSuites:` to `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256` or to a subset of these values. If using executable arguments, edit the kubelet service file `/etc/kubernetes/kubelet.conf` on each worker node and set the `--tls-cipher-suites` parameter as follows, or to a subset of these values. ``` --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256 ``` Based on your system, restart the `kubelet` service. For example: ``` systemctl daemon-reload systemctl restart kubelet.service ```", + "AuditProcedure": "The set of cryptographic ciphers currently considered secure is the following: - `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256` - `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256` - `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305` - `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384` - `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305` - `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384` - `TLS_RSA_WITH_AES_256_GCM_SHA384` - `TLS_RSA_WITH_AES_128_GCM_SHA256` Run the following command on each node: ``` ps -ef | grep kubelet ``` If the `--tls-cipher-suites` argument is present, ensure it only contains values included in this set. If it is not present check that there is a Kubelet config file specified by `--config`, and that file sets `tlsCipherSuites:` to only include values from this set.", + "AdditionalInformation": "The list chosen above should be fine for modern clients. It's essentially the list from the Mozilla Modern cipher option with the ciphersuites supporting CBC mode removed, as CBC has traditionally had a lot of issues", + "References": "", + "DefaultValue": "By default the Kubernetes API server supports a wide range of TLS ciphers" + } + ] + }, + { + "Id": "4.2.13", + "Description": "Ensure that a limit is set on pod PIDs", + "Checks": [], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Ensure that the Kubelet sets limits on the number of PIDs that can be created by pods running on the node.", + "RationaleStatement": "By default pods running in a cluster can consume any number of PIDs, potentially exhausting the resources available on the node. Setting an appropriate limit reduces the risk of a denial of service attack on cluster nodes.", + "ImpactStatement": "Setting this value will restrict the number of processes per pod. If this limit is lower than the number of PIDs required by a pod it will not operate.", + "RemediationProcedure": "Decide on an appropriate level for this parameter and set it, either via the `--pod-max-pids` command line parameter or the `PodPidsLimit` configuration file setting.", + "AuditProcedure": "Review the Kubelet's start-up parameters for the value of `--pod-max-pids`, and check the Kubelet configuration file for the `PodPidsLimit` . If neither of these values is set, then there is no limit in place.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/policy/pid-limiting/#pod-pid-limits", + "DefaultValue": "By default the number of PIDs is not limited." + } + ] + }, + { + "Id": "4.2.14", + "Description": "Ensure that the --seccomp-default parameter is set to true", + "Checks": [], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Ensure that the Kubelet enforces the use of the RuntimeDefault seccomp profile", + "RationaleStatement": "By default, Kubernetes disables the seccomp profile which ships with most container runtimes. Setting this parameter will ensure workloads running on the node are protected by the runtime's seccomp profile.", + "ImpactStatement": "Setting this will remove some rights from pods running on the node.", + "RemediationProcedure": "Set the parameter, either via the `--seccomp-default` command line parameter or the `seccompDefault` configuration file setting.", + "AuditProcedure": "Review the Kubelet's start-up parameters for the value of `--seccomp-default`, and check the Kubelet configuration file for the `seccompDefault` . If neither of these values is set, then the seccomp profile is not in use.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/tutorials/security/seccomp/#enable-the-use-of-runtimedefault-as-the-default-seccomp-profile-for-all-workloads", + "DefaultValue": "By default the seccomp profile is not enabled." + } + ] + }, + { + "Id": "4.2.15", + "Description": "Ensure that the --IPAddressDeny is set to any", + "Checks": [], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.2 Kubelet", + "Profile": "Level 2 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Ensuring that `--IPAddressDeny` is set to Any will facilitate allowlisting of only IP addresses that are explicitly set with the `--IPAddressAllow` parameter which will block unspecified IP addresses from communicating with the **kubelet** component.", + "RationaleStatement": "By default, Kubernetes allows any IP address to communicate with the **kubelet** component IP restrictions and IP whitelisting are security best practices and reduce the attack surface of the **kubelet**.", + "ImpactStatement": "Configuring the setting `IPAddressDeny=any` will deny service to any IP address not specified in the complimentary setting `IPAddressDeny=any` configuration parameter. Applying `IPAddressDeny=any` alone will completely disable communication with the component.", + "RemediationProcedure": "`IPAddressDeny=any` `IPAddressAllow={{ kubelet_secure_addresses }}` *Note kubelet_secure_addresses: localhost link-local {{ kube_pods_subnets | regex_replace(',', ' ') }} {{ kube_node_addresses }} {{ loadbalancer_apiserver.address | default('')", + "AuditProcedure": "Review the Kubelet's start-up parameters for the value of `--IPAddressDeny`, and check the Kubelet configuration file for `IPAddressDeny=any`. If this entry is present it should be accompanied by `IPAddressAllow={{ kubelet_secure_addresses }}` to allow the control plane to communicate with the component.", + "AdditionalInformation": "", + "References": "https://github.com/kubernetes-sigs/kubespray/pull/9194/files:https://kubernetes.io/docs/concepts/services-networking/network-policies/", + "DefaultValue": "By default IPAddressDeny is not enabled." + } + ] + }, + { + "Id": "4.3.1", + "Description": "Ensure that the kube-proxy metrics service is bound to localhost", + "Checks": [], + "Attributes": [ + { + "Section": "4 Worker Nodes", + "SubSection": "4.3 kube-proxy", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Do not bind the kube-proxy metrics port to non-loopback addresses.", + "RationaleStatement": "kube-proxy has two APIs which provided access to information about the service and can be bound to network ports. The metrics API service includes endpoints (`/metrics` and `/configz`) which disclose information about the configuration and operation of kube-proxy. These endpoints should not be exposed to untrusted networks as they do not support encryption or authentication to restrict access to the data they provide.", + "ImpactStatement": "3rd party services which try to access metrics or configuration information related to kube-proxy will require access to the localhost interface of the node.", + "RemediationProcedure": "Modify or remove any values which bind the metrics service to a non-localhost address", + "AuditProcedure": "review the start-up flags provided to kube proxy Run the following command on each node: ``` ps -ef | grep -i kube-proxy ``` Ensure that the `--metrics-bind-address` parameter is not set to a value other than 127.0.0.1. From the output of this command gather the location specified in the `--config` parameter. Review any file stored at that location and ensure that it does not specify a value other than 127.0.0.1 for `metricsBindAddress`.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/", + "DefaultValue": "The default value is `127.0.0.1:10249`" + } + ] + }, + { + "Id": "5.1.1", + "Description": "Ensure that the cluster-admin role is only used where required", + "Checks": [ + "rbac_cluster_admin_usage" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "The RBAC role `cluster-admin` provides wide-ranging powers over the environment and should be used only where and when needed.", + "RationaleStatement": "Kubernetes provides a set of default roles where RBAC is used. Some of these roles such as `cluster-admin` provide wide-ranging privileges which should only be applied where absolutely necessary. Roles such as `cluster-admin` allow super-user access to perform any action on any resource. When used in a `ClusterRoleBinding`, it gives full control over every resource in the cluster and in all namespaces. When used in a `RoleBinding`, it gives full control over every resource in the rolebinding's namespace, including the namespace itself.", + "ImpactStatement": "Care should be taken before removing any `clusterrolebindings` from the environment to ensure they were not required for operation of the cluster. Specifically, modifications should not be made to `clusterrolebindings` with the `system:` prefix as they are required for the operation of system components.", + "RemediationProcedure": "Identify all clusterrolebindings to the cluster-admin role. Check if they are used and if they need this role or if they could use a role with fewer privileges. Where possible, first bind users to a lower privileged role and then remove the clusterrolebinding to the cluster-admin role : ``` kubectl delete clusterrolebinding [name] ```", + "AuditProcedure": "Obtain a list of the principals who have access to the `cluster-admin` role by reviewing the `clusterrolebinding` output for each role binding that has access to the `cluster-admin` role. ``` kubectl get clusterrolebindings -o=custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT:.subjects[*].name ``` Review each principal listed and ensure that `cluster-admin` privilege is required for it.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/authorization/rbac/#user-facing-roles", + "DefaultValue": "By default a single `clusterrolebinding` called `cluster-admin` is provided with the `system:masters` group as its principal." + } + ] + }, + { + "Id": "5.1.2", + "Description": "Minimize access to secrets", + "Checks": [ + "rbac_minimize_secret_access" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "The Kubernetes API stores secrets, which may be service account tokens for the Kubernetes API or credentials used by workloads in the cluster. Access to these secrets should be restricted to the smallest possible group of users to reduce the risk of privilege escalation.", + "RationaleStatement": "Inappropriate access to secrets stored within the Kubernetes cluster can allow for an attacker to gain additional access to the Kubernetes cluster or external resources whose credentials are stored as secrets.", + "ImpactStatement": "Care should be taken not to remove access to secrets to system components which require this for their operation", + "RemediationProcedure": "Where possible, restrict access to secret objects in the cluster by removing get, list, and watch permissions.", + "AuditProcedure": "Review the users who have `get`, `list`, or `watch` access to `secrets` objects in the Kubernetes API.", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "By default in a kubeadm cluster the following list of principals have `get` privileges on `secret` objects ``` CLUSTERROLEBINDING SUBJECT TYPE SA-NAMESPACE cluster-admin system:masters Group system:controller:clusterrole-aggregation-controller clusterrole-aggregation-controller ServiceAccount kube-system system:controller:expand-controller expand-controller ServiceAccount kube-system system:controller:generic-garbage-collector generic-garbage-collector ServiceAccount kube-system system:controller:namespace-controller namespace-controller ServiceAccount kube-system system:controller:persistent-volume-binder persistent-volume-binder ServiceAccount kube-system system:kube-controller-manager system:kube-controller-manager User ```" + } + ] + }, + { + "Id": "5.1.3", + "Description": "Minimize wildcard use in Roles and ClusterRoles", + "Checks": [ + "rbac_minimize_wildcard_use_roles" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Worker Node", + "AssessmentStatus": "Manual", + "Description": "Kubernetes Roles and ClusterRoles provide access to resources based on sets of objects and actions that can be taken on those objects. It is possible to set either of these to be the wildcard * which matches all items. Use of wildcards is not optimal from a security perspective as it may allow for inadvertent access to be granted when new resources are added to the Kubernetes API either as CRDs or in later versions of the product.", + "RationaleStatement": "The principle of least privilege recommends that users are provided only the access required for their role and nothing more. The use of wildcard rights grants is likely to provide excessive rights to the Kubernetes API.", + "ImpactStatement": "", + "RemediationProcedure": "Where possible replace any use of wildcards in ClusterRoles and Roles with specific objects or actions.", + "AuditProcedure": "Retrieve the roles defined across each namespaces in the cluster and review for wildcards ``` kubectl get roles --all-namespaces -o yaml ``` Retrieve the cluster roles defined in the cluster and review for wildcards ``` kubectl get clusterroles -o yaml ```", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "" + } + ] + }, + { + "Id": "5.1.4", + "Description": "Minimize access to create pods", + "Checks": [ + "rbac_minimize_pod_creation_access" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "The ability to create pods in a namespace can provide a number of opportunities for privilege escalation, such as assigning privileged service accounts to these pods or mounting hostPaths with access to sensitive data (unless Pod Security Policies are implemented to restrict this access) As such, access to create new pods should be restricted to the smallest possible group of users.", + "RationaleStatement": "The ability to create pods in a cluster opens up possibilities for privilege escalation and should be restricted, where possible.", + "ImpactStatement": "Care should be taken not to remove access to pods to system components which require this for their operation", + "RemediationProcedure": "Where possible, remove `create` access to `pod` objects in the cluster.", + "AuditProcedure": "Review the users who have create access to pod objects in the Kubernetes API.", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "By default in a kubeadm cluster the following list of principals have `create` privileges on `pod` objects ``` CLUSTERROLEBINDING SUBJECT TYPE SA-NAMESPACE cluster-admin system:masters Group system:controller:clusterrole-aggregation-controller clusterrole-aggregation-controller ServiceAccount kube-system system:controller:daemon-set-controller daemon-set-controller ServiceAccount kube-system system:controller:job-controller job-controller ServiceAccount kube-system system:controller:persistent-volume-binder persistent-volume-binder ServiceAccount kube-system system:controller:replicaset-controller replicaset-controller ServiceAccount kube-system system:controller:replication-controller replication-controller ServiceAccount kube-system system:controller:statefulset-controller statefulset-controller ServiceAccount kube-system ```" + } + ] + }, + { + "Id": "5.1.5", + "Description": "Ensure that default service accounts are not actively used.", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "The `default` service account should not be used to ensure that rights granted to applications can be more easily audited and reviewed.", + "RationaleStatement": "Kubernetes provides a default service account which is used by cluster workloads where no specific service account is assigned to the pod. Where access to the Kubernetes API from a pod is required, a specific service account should be created for that pod, and rights granted to that service account. The default service account should be configured to ensure that it does not automatically provide a service account token, and it must not have any non-default role bindings or custom role assignments", + "ImpactStatement": "All workloads which require access to the Kubernetes API will require an explicit service account to be created.", + "RemediationProcedure": "Create explicit service accounts wherever a Kubernetes workload requires specific access to the Kubernetes API server. Modify the configuration of each default service account to include this value ``` automountServiceAccountToken: false ```", + "AuditProcedure": "For each namespace in the cluster, review the rights assigned to the default service account and ensure that it has no roles or cluster roles bound to it apart from the defaults. Additionally ensure that the `automountServiceAccountToken: false` setting is in place for each default service account.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + "DefaultValue": "By default the `default` service account allows for its service account token to be mounted in pods in its namespace." + } + ] + }, + { + "Id": "5.1.6", + "Description": "Ensure that Service Account Tokens are only mounted where necessary", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Service accounts tokens should not be mounted in pods except where the workload running in the pod explicitly needs to communicate with the API server", + "RationaleStatement": "Mounting service account tokens inside pods can provide an avenue for privilege escalation attacks where an attacker is able to compromise a single pod in the cluster. Avoiding mounting these tokens removes this attack avenue.", + "ImpactStatement": "Pods mounted without service account tokens will not be able to communicate with the API server, except where the resource is available to unauthenticated principals.", + "RemediationProcedure": "Modify the definition of pods and service accounts which do not need to mount service account tokens to disable it.", + "AuditProcedure": "Review pod and service account objects in the cluster and ensure that the option below is set, unless the resource explicitly requires this access. ``` automountServiceAccountToken: false ```", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + "DefaultValue": "By default, all pods get a service account token mounted in them." + } + ] + }, + { + "Id": "5.1.7", + "Description": "Avoid use of system:masters group", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "The special group `system:masters` should not be used to grant permissions to any user or service account, except where strictly necessary (e.g. bootstrapping access prior to RBAC being fully available)", + "RationaleStatement": "The `system:masters` group has unrestricted access to the Kubernetes API hard-coded into the API server source code. An authenticated user who is a member of this group cannot have their access reduced, even if all bindings and cluster role bindings which mention it, are removed. When combined with client certificate authentication, use of this group can allow for irrevocable cluster-admin level credentials to exist for a cluster.", + "ImpactStatement": "Once the RBAC system is operational in a cluster `system:masters` should not be specifically required, as ordinary bindings from principals to the `cluster-admin` cluster role can be made where unrestricted access is required.", + "RemediationProcedure": "Remove the `system:masters` group from all users in the cluster.", + "AuditProcedure": "Review a list of all credentials which have access to the cluster and ensure that the group `system:masters` is not used.", + "AdditionalInformation": "", + "References": "https://github.com/kubernetes/kubernetes/blob/master/pkg/registry/rbac/escalation_check.go#L38", + "DefaultValue": "By default some clusters will create a break glass client certificate which is a member of this group. Access to this client certificate should be carefully controlled and it should not be used for general cluster operations." + } + ] + }, + { + "Id": "5.1.8", + "Description": "Limit use of the Bind, Impersonate and Escalate permissions in the Kubernetes cluster", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Cluster roles and roles with the impersonate, bind or escalate permissions should not be granted unless strictly required. Each of these permissions allow a particular subject to escalate their privileges beyond those explicitly granted by cluster administrators", + "RationaleStatement": "The impersonate privilege allows a subject to impersonate other users gaining their rights to the cluster. The bind privilege allows the subject to add a binding to a cluster role or role which escalates their effective permissions in the cluster. The escalate privilege allows a subject to modify cluster roles to which they are bound, increasing their rights to that level. Each of these permissions has the potential to allow for privilege escalation to cluster-admin level.", + "ImpactStatement": "There are some cases where these permissions are required for cluster service operation, and care should be taken before removing these permissions from system service accounts.", + "RemediationProcedure": "Where possible, remove the impersonate, bind, and escalate rights from subjects.", + "AuditProcedure": "Review the users who have access to cluster roles or roles which provide the impersonate, bind, or escalate privileges.", + "AdditionalInformation": "", + "References": "https://raesene.github.io/blog/2020/12/12/Escalating_Away/:https://raesene.github.io/blog/2021/01/16/Getting-Into-A-Bind-with-Kubernetes/", + "DefaultValue": "In a default kubeadm cluster, the system:masters group and clusterrole-aggregation-controller service account have access to the escalate privilege. The system:masters group also has access to bind and impersonate." + } + ] + }, + { + "Id": "5.1.9", + "Description": "Minimize access to create persistent volumes", + "Checks": [ + "rbac_minimize_pv_creation_access" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "The ability to create persistent volumes in a cluster can provide an opportunity for privilege escalation, via the creation of `hostPath` volumes. As persistent volumes are not covered by Pod Security Admission, a user with access to create persistent volumes may be able to get access to sensitive files from the underlying host even where restrictive Pod Security Admission policies are in place.", + "RationaleStatement": "The ability to create persistent volumes in a cluster opens up possibilities for privilege escalation and should be restricted, where possible.", + "ImpactStatement": "", + "RemediationProcedure": "Where possible, remove `create` access to `PersistentVolume` objects in the cluster.", + "AuditProcedure": "Review the users who have create access to `PersistentVolume` objects in the Kubernetes API.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/rbac-good-practices/#persistent-volume-creation", + "DefaultValue": "" + } + ] + }, + { + "Id": "5.1.10", + "Description": "Minimize access to the proxy sub-resource of nodes", + "Checks": [ + "rbac_minimize_node_proxy_subresource_access" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Users with access to the `Proxy` sub-resource of `Node` objects automatically have permissions to use the kubelet API, which may allow for privilege escalation or bypass cluster security controls such as audit logs. The kubelet provides an API which includes rights to execute commands in any container running on the node. Access to this API is covered by permissions to the main Kubernetes API via the `node` object. The proxy sub-resource specifically allows wide ranging access to the kubelet API. Direct access to the kubelet API bypasses controls like audit logging (there is no audit log of kubelet API access) and admission control.", + "RationaleStatement": "The ability to use the `proxy` sub-resource of `node` objects opens up possibilities for privilege escalation and should be restricted, where possible.", + "ImpactStatement": "", + "RemediationProcedure": "Where possible, remove access to the `proxy` sub-resource of `node` objects.", + "AuditProcedure": "Review the users who have access to the `proxy` sub-resource of `node` objects in the Kubernetes API.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/rbac-good-practices/#access-to-proxy-subresource-of-nodes:https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/#kubelet-authorization", + "DefaultValue": "" + } + ] + }, + { + "Id": "5.1.11", + "Description": "Minimize access to the approval sub-resource of certificatesigningrequests objects", + "Checks": [ + "rbac_minimize_csr_approval_access" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Users with access to the update the `approval` sub-resource of `CertificateSigningRequests` objects can approve new client certificates for the Kubernetes API effectively allowing them to create new high-privileged user accounts. This can allow for privilege escalation to full cluster administrator, depending on users configured in the cluster", + "RationaleStatement": "The ability to update certificate signing requests should be limited.", + "ImpactStatement": "", + "RemediationProcedure": "Where possible, remove access to the `approval` sub-resource of `CertificateSigningRequests` objects.", + "AuditProcedure": "Review the users who have access to update the `approval` sub-resource of `CertificateSigningRequests` objects in the Kubernetes API.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/rbac-good-practices/#csrs-and-certificate-issuing", + "DefaultValue": "" + } + ] + }, + { + "Id": "5.1.12", + "Description": "Minimize access to webhook configuration objects", + "Checks": [ + "rbac_minimize_webhook_config_access" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Users with rights to create/modify/delete `validatingwebhookconfigurations` or `mutatingwebhookconfigurations` can control webhooks that can read any object admitted to the cluster, and in the case of mutating webhooks, also mutate admitted objects. This could allow for privilege escalation or disruption of the operation of the cluster.", + "RationaleStatement": "The ability to manage webhook configuration should be limited", + "ImpactStatement": "", + "RemediationProcedure": "Where possible, remove access to the `validatingwebhookconfigurations` or `mutatingwebhookconfigurations` objects", + "AuditProcedure": "Review the users who have access to `validatingwebhookconfigurations` or `mutatingwebhookconfigurations` objects in the Kubernetes API.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/rbac-good-practices/#control-admission-webhooks", + "DefaultValue": "" + } + ] + }, + { + "Id": "5.1.13", + "Description": "Minimize access to the service account token creation", + "Checks": [ + "rbac_minimize_service_account_token_creation" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.1 RBAC and Service Accounts", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Users with rights to create new service account tokens at a cluster level, can create long-lived privileged credentials in the cluster. This could allow for privilege escalation and persistent access to the cluster, even if the users account has been revoked.", + "RationaleStatement": "The ability to create service account tokens should be limited.", + "ImpactStatement": "", + "RemediationProcedure": "Where possible, remove access to the `token` sub-resource of `serviceaccount` objects.", + "AuditProcedure": "Review the users who have access to create the `token` sub-resource of `serviceaccount` objects in the Kubernetes API.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/rbac-good-practices/#token-request", + "DefaultValue": "" + } + ] + }, + { + "Id": "5.2.1", + "Description": "Ensure that the cluster has at least one active policy control mechanism in place", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Every Kubernetes cluster should have at least one policy control mechanism in place to enforce the other requirements in this section. This could be the in-built Pod Security Admission controller, or a third party policy control system.", + "RationaleStatement": "Without an active policy control mechanism, it is not possible to limit the use of containers with access to underlying cluster nodes, via mechanisms like privileged containers, or the use of hostPath volume mounts.", + "ImpactStatement": "Where policy control systems are in place, there is a risk that workloads required for the operation of the cluster may be stopped from running. Care is required when implementing admission control policies to ensure that this does not occur.", + "RemediationProcedure": "Ensure that either Pod Security Admission or an external policy control system is in place for every namespace which contains user workloads.", + "AuditProcedure": "Review the workloads deployed to the cluster to understand if Pod Security Admission or external admission control systems are in place.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-admission", + "DefaultValue": "By default, Pod Security Admission is enabled but no policies are in place." + } + ] + }, + { + "Id": "5.2.2", + "Description": "Minimize the admission of privileged containers", + "Checks": [ + "core_minimize_privileged_containers" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally permit containers to be run with the `securityContext.privileged` flag set to `true`.", + "RationaleStatement": "Privileged containers have access to all Linux Kernel capabilities and devices. A container running with full privileges can do almost everything that the host can do. This flag exists to allow special use-cases, like manipulating the network stack and accessing devices. There should be at least one admission control policy defined which does not permit privileged containers. If you need to run privileged containers, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.", + "ImpactStatement": "Pods defined with `spec.containers[].securityContext.privileged: true`, `spec.initContainers[].securityContext.privileged: true` and `spec.ephemeralContainers[].securityContext.privileged: true` will not be permitted.", + "RemediationProcedure": "Add policies to each namespace in the cluster which has user workloads to restrict the admission of privileged containers.", + "AuditProcedure": "Run the following command: `kubectl get pods -A -o=jsonpath='{range .items[*]}{@.metadata.name}: {@..securityContext}{end}'`It will produce an inventory of all the privileged use on the cluster, if any (please, refer to a sample below). Further grepping can be done to automate each specific violation detection. calico-kube-controllers-57b57c56f-jtmk4: {} << No Elevated Privileges calico-node-c4xv4: {} {privileged:true} {privileged:true} {privileged:true} {privileged:true} << Violates 5.2.2 dashboard-metrics-scraper-7bc864c59-2m2xw: {seccompProfile:{type:RuntimeDefault}} {allowPrivilegeEscalation:false,readOnlyRootFilesystem:true,runAsGroup:2001,runAsUser:1001}", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "DefaultValue": "By default, there are no restrictions on the creation of privileged containers." + } + ] + }, + { + "Id": "5.2.3", + "Description": "Minimize the admission of containers wishing to share the host process ID namespace", + "Checks": [ + "core_minimize_hostPID_containers" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally permit containers to be run with the `hostPID` flag set to true.", + "RationaleStatement": "A container running in the host's PID namespace can inspect processes running outside the container. If the container also has access to ptrace capabilities this can be used to escalate privileges outside of the container. There should be at least one admission control policy defined which does not permit containers to share the host PID namespace. If you need to run containers which require hostPID, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.", + "ImpactStatement": "Pods defined with `spec.hostPID: true` will not be permitted unless they are run under a specific policy.", + "RemediationProcedure": "Configure the Admission Controller to restrict the admission of `hostPID` containers.", + "AuditProcedure": "Fetch hostPID from each pod with `get pods -A -o=jsonpath='{range .items[*]}{@.metadata.name}: {@.spec.hostPID}{end}'`", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "DefaultValue": "By default, there are no restrictions on the creation of `hostPID` containers." + } + ] + }, + { + "Id": "5.2.4", + "Description": "Minimize the admission of containers wishing to share the host IPC namespace", + "Checks": [ + "core_minimize_hostIPC_containers" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally permit containers to be run with the `hostIPC` flag set to true.", + "RationaleStatement": "A container running in the host's IPC namespace can use IPC to interact with processes outside the container. There should be at least one admission control policy defined which does not permit containers to share the host IPC namespace. If you need to run containers which require hostIPC, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.", + "ImpactStatement": "Pods defined with `spec.hostIPC: true` will not be permitted unless they are run under a specific policy.", + "RemediationProcedure": "Add policies to each namespace in the cluster which has user workloads to restrict the admission of `hostIPC` containers.", + "AuditProcedure": "Fetch hostIPC from each pod with `get pods -A -o=jsonpath='{range .items[*]}{@.metadata.name}: {@.spec.hostIPC}{end}'`", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "DefaultValue": "By default, there are no restrictions on the creation of `hostIPC` containers." + } + ] + }, + { + "Id": "5.2.5", + "Description": "Minimize the admission of containers wishing to share the host network namespace", + "Checks": [ + "core_minimize_hostNetwork_containers" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally permit containers to be run with the `hostNetwork` flag set to true.", + "RationaleStatement": "A container running in the host's network namespace could access the local loopback device, and could access network traffic to and from other pods. There should be at least one admission control policy defined which does not permit containers to share the host network namespace. If you need to run containers which require access to the host's network namespaces, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.", + "ImpactStatement": "Pods defined with `spec.hostNetwork: true` will not be permitted unless they are run under a specific policy.", + "RemediationProcedure": "Add policies to each namespace in the cluster which has user workloads to restrict the admission of `hostNetwork` containers.", + "AuditProcedure": "Fetch hostNetwork from each pod with `get pods -A -o=jsonpath='{range .items[*]}{@.metadata.name}: {@.spec.hostNetwork}{end}'`", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "DefaultValue": "By default, there are no restrictions on the creation of `hostNetwork` containers." + } + ] + }, + { + "Id": "5.2.6", + "Description": "Minimize the admission of containers with allowPrivilegeEscalation", + "Checks": [ + "core_minimize_allowPrivilegeEscalation_containers" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally permit containers to be run with the `allowPrivilegeEscalation` flag set to true. Allowing this right can lead to a process running a container getting more rights than it started with. It's important to note that these rights are still constrained by the overall container sandbox, and this setting does not relate to the use of privileged containers.", + "RationaleStatement": "A container running with the `allowPrivilegeEscalation` flag set to `true` may have processes that can gain more privileges than their parent. There should be at least one admission control policy defined which does not permit containers to allow privilege escalation. The option exists (and is defaulted to true) to permit setuid binaries to run. If you have need to run containers which use setuid binaries or require privilege escalation, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.", + "ImpactStatement": "Pods defined with `securityContext: allowPrivilegeEscalation: true` will not be permitted unless they are run under a specific policy.", + "RemediationProcedure": "Add policies to each namespace in the cluster which has user workloads to restrict the admission of containers with `securityContext: allowPrivilegeEscalation: true`", + "AuditProcedure": "List the policies in use for each namespace in the cluster, ensure that each policy disallows the admission of containers which allow privilege escalation. To fetch a list of pods which `allowPrivilegeEscalation` run this command: `get pods -A -o=jsonpath='{range .items[*]}{@.metadata.name}: {@..securityContext}{end}'`", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "DefaultValue": "By default, there are no restrictions on contained process ability to escalate privileges, within the context of the container." + } + ] + }, + { + "Id": "5.2.7", + "Description": "Minimize the admission of root containers", + "Checks": [ + "core_minimize_root_containers_admission" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally permit containers to be run as the root user.", + "RationaleStatement": "Containers may run as any Linux user. Containers which run as the root user, whilst constrained by Container Runtime security features still have a escalated likelihood of container breakout. Ideally, all containers should run as a defined non-UID 0 user. There should be at least one admission control policy defined which does not permit root containers. If you need to run root containers, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.", + "ImpactStatement": "Pods with containers which run as the root user will not be permitted.", + "RemediationProcedure": "Create a policy for each namespace in the cluster, ensuring that either `MustRunAsNonRoot` or `MustRunAs` with the range of UIDs not including 0, is set.", + "AuditProcedure": "List the policies in use for each namespace in the cluster, ensure that each policy restricts the use of root containers by setting `MustRunAsNonRoot` or `MustRunAs` with the range of UIDs not including 0.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "DefaultValue": "By default, there are no restrictions on the use of root containers and if a User is not specified in the image, the container will run as root." + } + ] + }, + { + "Id": "5.2.8", + "Description": "Minimize the admission of containers with the NET_RAW capability", + "Checks": [ + "core_minimize_net_raw_capability_admission" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally permit containers with the potentially dangerous NET_RAW capability.", + "RationaleStatement": "Containers run with a default set of capabilities as assigned by the Container Runtime. By default this can include potentially dangerous capabilities. With Docker as the container runtime the NET_RAW capability is enabled which may be misused by malicious containers. Ideally, all containers should drop this capability. There should be at least one admission control policy defined which does not permit containers with the NET_RAW capability. If you need to run containers with this capability, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.", + "ImpactStatement": "Pods with containers which run with the NET_RAW capability will not be permitted.", + "RemediationProcedure": "Add policies to each namespace in the cluster which has user workloads to restrict the admission of containers with the `NET_RAW` capability.", + "AuditProcedure": "List the policies in use for each namespace in the cluster, ensure that at least one policy disallows the admission of containers with the `NET_RAW` capability.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-standards/:https://www.nccgroup.trust/uk/our-research/abusing-privileged-and-unprivileged-linux-containers/", + "DefaultValue": "By default, there are no restrictions on the creation of containers with the `NET_RAW` capability." + } + ] + }, + { + "Id": "5.2.9", + "Description": "Minimize the admission of containers with added capabilities", + "Checks": [ + "core_minimize_containers_added_capabilities" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally permit containers with capabilities assigned beyond the default set.", + "RationaleStatement": "Containers run with a default set of capabilities as assigned by the Container Runtime. Capabilities outside this set can be added to containers which could expose them to risks of container breakout attacks. There should be at least one policy defined which prevents containers with capabilities beyond the default set from launching. If you need to run containers with additional capabilities, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.", + "ImpactStatement": "Pods with containers which require capabilities outside the default set will not be permitted.", + "RemediationProcedure": "Ensure that `allowedCapabilities` is not present in policies for the cluster unless it is set to an empty array.", + "AuditProcedure": "Ensure that allowedCapabilities is not present in policies for the cluster unless it is set to an empty array. Run: kubectl get pods -A -o=jsonpath='{range .items[*]}{@.metadata.name}: {@..securityContext}{end}'", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-standards/:https://www.nccgroup.trust/uk/our-research/abusing-privileged-and-unprivileged-linux-containers/", + "DefaultValue": "By default, there are no restrictions on adding capabilities to containers." + } + ] + }, + { + "Id": "5.2.10", + "Description": "Minimize the admission of containers with capabilities assigned", + "Checks": [ + "core_minimize_containers_capabilities_assigned" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally permit containers with capabilities", + "RationaleStatement": "Containers run with a default set of capabilities as assigned by the Container Runtime. Capabilities are parts of the rights generally granted on a Linux system to the root user. In many cases applications running in containers do not require any capabilities to operate, so from the perspective of the principal of least privilege use of capabilities should be minimized.", + "ImpactStatement": "Pods with containers require capabilities to operate will not be permitted.", + "RemediationProcedure": "Review the use of capabilities in applications running on your cluster. Where a namespace contains applications which do not require any Linux capabilities to operate consider adding a policy which forbids the admission of containers which do not drop all capabilities.", + "AuditProcedure": "List the policies in use for each namespace in the cluster, ensure that at least one policy requires that capabilities are dropped by all containers.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-standards/:https://www.nccgroup.trust/uk/our-research/abusing-privileged-and-unprivileged-linux-containers/", + "DefaultValue": "By default, there are no restrictions on the creation of containers with additional capabilities" + } + ] + }, + { + "Id": "5.2.11", + "Description": "Minimize the admission of Windows HostProcess Containers", + "Checks": [ + "core_minimize_admission_windows_hostprocess_containers" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally permit Windows containers to be run with the `hostProcess` flag set to true.", + "RationaleStatement": "A Windows container making use of the `hostProcess` flag can interact with the underlying Windows cluster node. As per the Kubernetes documentation, this provides privileged access to the Windows node. Where Windows containers are used inside a Kubernetes cluster, there should be at least one admission control policy which does not permit `hostProcess` Windows containers. If you need to run Windows containers which require `hostProcess`, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.", + "ImpactStatement": "Pods defined with `securityContext.windowsOptions.hostProcess: true` will not be permitted unless they are run under a specific policy.", + "RemediationProcedure": "Add policies to each namespace in the cluster which has user workloads to restrict the admission of `hostProcess` containers.", + "AuditProcedure": "List the policies in use for each namespace in the cluster, ensure that each policy disallows the admission of `hostProcess` containers", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/tasks/configure-pod-container/create-hostprocess-pod/:https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "DefaultValue": "By default, there are no restrictions on the creation of `hostProcess` containers." + } + ] + }, + { + "Id": "5.2.12", + "Description": "Minimize the admission of HostPath volumes", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally admit containers which make use of `hostPath` volumes.", + "RationaleStatement": "A container which mounts a `hostPath` volume as part of its specification will have access to the filesystem of the underlying cluster node. The use of `hostPath` volumes may allow containers access to privileged areas of the node filesystem. There should be at least one admission control policy defined which does not permit containers to mount `hostPath` volumes. If you need to run containers which require `hostPath` volumes, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.", + "ImpactStatement": "Pods defined which make use of `hostPath` volumes will not be permitted unless they are run under a specific policy.", + "RemediationProcedure": "Add policies to each namespace in the cluster which has user workloads to restrict the admission of containers which use `hostPath` volumes.", + "AuditProcedure": "List the policies in use for each namespace in the cluster, ensure that each policy disallows the admission of containers with `hostPath` volumes.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "DefaultValue": "By default, there are no restrictions on the creation of `hostPath` volumes." + } + ] + }, + { + "Id": "5.2.13", + "Description": "Minimize the admission of containers which use HostPorts", + "Checks": [ + "core_minimize_admission_hostport_containers" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.2 Pod Security Standards", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Do not generally permit containers which require the use of HostPorts.", + "RationaleStatement": "Host ports connect containers directly to the host's network. This can bypass controls such as network policy. There should be at least one admission control policy defined which does not permit containers which require the use of HostPorts. If you need to run containers which require HostPorts, this should be defined in a separate policy and you should carefully check to ensure that only limited service accounts and users are given permission to use that policy.", + "ImpactStatement": "Pods defined with `hostPort` settings in either the container, initContainer or ephemeralContainer sections will not be permitted unless they are run under a specific policy.", + "RemediationProcedure": "Add policies to each namespace in the cluster which has user workloads to restrict the admission of containers which use `hostPort` sections.", + "AuditProcedure": "List the policies in use for each namespace in the cluster, ensure that each policy disallows the admission of containers which have `hostPort` sections.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/security/pod-security-standards/", + "DefaultValue": "By default, there are no restrictions on the use of HostPorts." + } + ] + }, + { + "Id": "5.3.1", + "Description": "Ensure that the CNI in use supports Network Policies", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.3 Network Policies and CNI", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "There are a variety of CNI plugins available for Kubernetes. If the CNI in use does not support Network Policies it may not be possible to effectively restrict traffic in the cluster.", + "RationaleStatement": "Kubernetes network policies are enforced by the CNI plugin in use. As such it is important to ensure that the CNI plugin supports both Ingress and Egress network policies.", + "ImpactStatement": "None", + "RemediationProcedure": "If the CNI plugin in use does not support network policies, consideration should be given to making use of a different plugin, or finding an alternate mechanism for restricting traffic in the Kubernetes cluster.", + "AuditProcedure": "Review the documentation of CNI plugin in use by the cluster, and confirm that it supports Ingress and Egress network policies.", + "AdditionalInformation": "One example here is Flannel (https://github.com/coreos/flannel) which does not support Network policy unless Calico is also in use.", + "References": "https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/", + "DefaultValue": "This will depend on the CNI plugin in use." + } + ] + }, + { + "Id": "5.3.2", + "Description": "Ensure that all Namespaces have Network Policies defined", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.3 Network Policies and CNI", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Use network policies to isolate traffic in your cluster network.", + "RationaleStatement": "Running different applications on the same Kubernetes cluster creates a risk of one compromised application attacking a neighboring application. Network segmentation is important to ensure that containers can communicate only with those they are supposed to. A network policy is a specification of how selections of pods are allowed to communicate with each other and other network endpoints. Network Policies are namespace scoped. When a network policy is introduced to a given namespace, all traffic not allowed by the policy is denied. However, if there are no network policies in a namespace all traffic will be allowed into and out of the pods in that namespace.", + "ImpactStatement": "Once network policies are in use within a given namespace, traffic not explicitly allowed by a network policy will be denied. As such it is important to ensure that, when introducing network policies, legitimate traffic is not blocked.", + "RemediationProcedure": "Follow the documentation and create `NetworkPolicy` objects as you need them.", + "AuditProcedure": "Run the below command and review the `NetworkPolicy` objects created in the cluster. ``` kubectl get networkpolicy --all-namespaces ``` Ensure that each namespace defined in the cluster has at least one Network Policy.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/services-networking/networkpolicies/:https://octetz.com/posts/k8s-network-policy-apis:https://kubernetes.io/docs/tasks/configure-pod-container/declare-network-policy/", + "DefaultValue": "By default, network policies are not created." + } + ] + }, + { + "Id": "5.4.1", + "Description": "Prefer using secrets as files over secrets as environment variables", + "Checks": [ + "core_no_secrets_envs" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.4 Secrets Management", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Kubernetes supports mounting secrets as data volumes or as environment variables. Minimize the use of environment variable secrets.", + "RationaleStatement": "It is reasonably common for application code to log out its environment (particularly in the event of an error). This will include any secret values passed in as environment variables, so secrets can easily be exposed to any user or entity who has access to the logs.", + "ImpactStatement": "Application code which expects to read secrets in the form of environment variables would need modification", + "RemediationProcedure": "If possible, rewrite application code to read secrets from mounted secret files, rather than from environment variables.", + "AuditProcedure": "Run the following command to find references to objects which use environment variables defined from secrets. ``` kubectl get all -o jsonpath='{range .items[?(@..secretKeyRef)]}{.kind}{@.metadata.name}{end}' -A ```", + "AdditionalInformation": "Mounting secrets as volumes has the additional benefit that secret values can be updated without restarting the pod", + "References": "https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets", + "DefaultValue": "By default, secrets are not defined" + } + ] + }, + { + "Id": "5.4.2", + "Description": "Consider external secret storage", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.4 Secrets Management", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Consider the use of an external secrets storage and management system, instead of using Kubernetes Secrets directly, if you have more complex secret management needs. Ensure the solution requires authentication to access secrets, has auditing of access to and use of secrets, and encrypts secrets. Some solutions also make it easier to rotate secrets.", + "RationaleStatement": "Kubernetes supports secrets as first-class objects, but care needs to be taken to ensure that access to secrets is carefully limited. Using an external secrets provider can ease the management of access to secrets, especially where secrests are used across both Kubernetes and non-Kubernetes environments.", + "ImpactStatement": "None", + "RemediationProcedure": "Refer to the secrets management options offered by your cloud provider or a third-party secrets management solution.", + "AuditProcedure": "Review your secrets management implementation.", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "By default, no external secret management is configured." + } + ] + }, + { + "Id": "5.5.1", + "Description": "Configure Image Provenance using ImagePolicyWebhook admission controller", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.5 Extensible Admission Control", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Configure Image Provenance for your deployment.", + "RationaleStatement": "Kubernetes supports plugging in provenance rules to accept or reject the images in your deployments. You could configure such rules to ensure that only approved images are deployed in the cluster.", + "ImpactStatement": "You need to regularly maintain your provenance configuration based on container image updates.", + "RemediationProcedure": "Follow the Kubernetes documentation and setup image provenance.", + "AuditProcedure": "Review the pod definitions in your cluster and verify that image provenance is configured as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/admin/admission-controllers/#imagepolicywebhook:https://github.com/kubernetes/community/blob/master/contributors/design-proposals/image-provenance.md:https://hub.docker.com/r/dnurmi/anchore-toolbox/:https://github.com/kubernetes/kubernetes/issues/22888", + "DefaultValue": "By default, image provenance is not set." + } + ] + }, + { + "Id": "5.6.1", + "Description": "Create administrative boundaries between resources using namespaces", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.6 General Policies", + "Profile": "Level 1 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Use namespaces to isolate your Kubernetes objects.", + "RationaleStatement": "Limiting the scope of user permissions can reduce the impact of mistakes or malicious activities. A Kubernetes namespace allows you to partition created resources into logically named groups. Resources created in one namespace can be hidden from other namespaces. By default, each resource created by a user in Kubernetes cluster runs in a default namespace, called `default`. You can create additional namespaces and attach resources and users to them. You can use Kubernetes Authorization plugins to create policies that segregate access to namespace resources between different users.", + "ImpactStatement": "You need to switch between namespaces for administration.", + "RemediationProcedure": "Follow the documentation and create namespaces for objects in your deployment as you need them.", + "AuditProcedure": "Run the below command and review the namespaces created in the cluster. ``` kubectl get namespaces ``` Ensure that these namespaces are the ones you need and are adequately administered as per your requirements.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#viewing-namespaces:http://blog.kubernetes.io/2016/08/security-best-practices-kubernetes-deployment.html:https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/589-efficient-node-heartbeats", + "DefaultValue": "By default, Kubernetes starts with 4 initial namespaces: 1. `default` - The default namespace for objects with no other namespace 1. `kube-system` - The namespace for objects created by the Kubernetes system 1. `kube-node-lease` - Namespace used for node heartbeats 1. `kube-public` - Namespace used for public information in a cluster" + } + ] + }, + { + "Id": "5.6.2", + "Description": "Ensure that the seccomp profile is set to docker/default in your pod definitions", + "Checks": [ + "core_seccomp_profile_docker_default" + ], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.6 General Policies", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Enable `docker/default` seccomp profile in your pod definitions.", + "RationaleStatement": "Seccomp (secure computing mode) is used to restrict the set of system calls applications can make, allowing cluster administrators greater control over the security of workloads running in the cluster. Kubernetes disables seccomp profiles by default for historical reasons. You should enable it to ensure that the workloads have restricted actions available within the container.", + "ImpactStatement": "If the `docker/default` seccomp profile is too restrictive for you, you would have to create/manage your own seccomp profiles.", + "RemediationProcedure": "Use security context to enable the `docker/default` seccomp profile in your pod definitions. An example is as below: ``` securityContext: seccompProfile: type: RuntimeDefault ```", + "AuditProcedure": "Review the pod definitions in your cluster. It should create a line as below: ``` securityContext: seccompProfile: type: RuntimeDefault ```", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/tutorials/clusters/seccomp/:https://docs.docker.com/engine/security/seccomp/", + "DefaultValue": "By default, seccomp profile is set to `unconfined` which means that no seccomp profiles are enabled." + } + ] + }, + { + "Id": "5.6.3", + "Description": "Apply Security Context to Your Pods and Containers", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.6 General Policies", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Apply Security Context to Your Pods and Containers", + "RationaleStatement": "A security context defines the operating system security settings (uid, gid, capabilities, SELinux role, etc..) applied to a container. When designing your containers and pods, make sure that you configure the security context for your pods, containers, and volumes. A security context is a property defined in the deployment yaml. It controls the security parameters that will be assigned to the pod/container/volume. There are two levels of security context: pod level security context, and container level security context.", + "ImpactStatement": "If you incorrectly apply security contexts, you may have trouble running the pods.", + "RemediationProcedure": "Follow the Kubernetes documentation and apply security contexts to your pods. For a suggested list of security contexts, you may refer to the CIS Security Benchmark for Docker Containers.", + "AuditProcedure": "Review the pod definitions in your cluster and verify that you have security contexts defined as appropriate.", + "AdditionalInformation": "", + "References": "https://kubernetes.io/docs/concepts/policy/security-context/:https://learn.cisecurity.org/benchmarks", + "DefaultValue": "By default, no security contexts are automatically applied to pods." + } + ] + }, + { + "Id": "5.6.4", + "Description": "The default namespace should not be used", + "Checks": [], + "Attributes": [ + { + "Section": "5 Policies", + "SubSection": "5.6 General Policies", + "Profile": "Level 2 - Master Node", + "AssessmentStatus": "Manual", + "Description": "Kubernetes provides a default namespace, where objects are placed if no namespace is specified for them. Placing objects in this namespace makes application of RBAC and other controls more difficult.", + "RationaleStatement": "Resources in a Kubernetes cluster should be segregated by namespace, to allow for security controls to be applied at that level and to make it easier to manage resources.", + "ImpactStatement": "None", + "RemediationProcedure": "Ensure that namespaces are created to allow for appropriate segregation of Kubernetes resources and that all new resources are created in a specific namespace.", + "AuditProcedure": "Run this command to list objects in default namespace ``` kubectl get $(kubectl api-resources --verbs=list --namespaced=true -o name | paste -sd, -) --ignore-not-found -n default ``` The only entries there should be system managed resources such as the `kubernetes` service", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "Unless a namespace is specific on object creation, the `default` namespace will be used" + } + ] + } + ] +} From 7016779b8ee87c98049362229ecb606d721072de Mon Sep 17 00:00:00 2001 From: Toni de la Fuente Date: Wed, 21 May 2025 11:31:23 +0200 Subject: [PATCH 13/25] chore(README): update `README.md` (#7799) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ccab5e285b..9064d36891 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

- Prowler Open Source is as dynamic and adaptable as the environment it secures. It is trusted by the industry leaders to uphold the highest standards in security. + Prowler is the Open Cloud Security platform trusted by thousands to automate security and compliance in any cloud environment. With hundreds of ready-to-use checks and compliance frameworks, Prowler delivers real-time, customizable monitoring and seamless integrations, making cloud security simple, scalable, and cost-effective for organizations of any size.

Learn more at prowler.com From 31ea672c613d013c018d8ab0decbe6c08f6e24ae Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Wed, 21 May 2025 11:45:54 +0200 Subject: [PATCH 14/25] fix: move changes to release 5.8 (#7801) --- prowler/CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 9c0f1cb886..bfe2c91df2 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -7,8 +7,12 @@ All notable changes to the **Prowler SDK** are documented in this file. ### Added - Add CIS 1.11 compliance framework for Kubernetes. [(#7790)](https://github.com/prowler-cloud/prowler/pull/7790) - Support `HTTPS_PROXY` and `K8S_SKIP_TLS_VERIFY` in Kubernetes. [(#7720)](https://github.com/prowler-cloud/prowler/pull/7720) +- Add new check `entra_users_mfa_capable`. [(#7734)](https://github.com/prowler-cloud/prowler/pull/7734) +- Add new check `admincenter_organization_customer_lockbox_enabled`. [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732) +- Add new check `admincenter_external_calendar_sharing_disabled`. [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733) ### Fixed +- Fix `m365_powershell test_credentials` to use sanitized credentials. [(#7761)](https://github.com/prowler-cloud/prowler/pull/7761) --- @@ -18,9 +22,6 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update the compliance list supported for each provider from docs. [(#7694)](https://github.com/prowler-cloud/prowler/pull/7694) - Allow setting cluster name in in-cluster mode in Kubernetes. [(#7695)](https://github.com/prowler-cloud/prowler/pull/7695) - Add Prowler ThreatScore for M365 provider. [(#7692)](https://github.com/prowler-cloud/prowler/pull/7692) -- Add new check `entra_users_mfa_capable`. [(#7734)](https://github.com/prowler-cloud/prowler/pull/7734) -- Add new check `admincenter_organization_customer_lockbox_enabled`. [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732) -- Add new check `admincenter_external_calendar_sharing_disabled`. [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733) - Add GitHub provider. [(#5787)](https://github.com/prowler-cloud/prowler/pull/5787) - Add `repository_default_branch_requires_multiple_approvals` check for GitHub provider. [(#6160)](https://github.com/prowler-cloud/prowler/pull/6160) - Add `repository_default_branch_protection_enabled` check for GitHub provider. [(#6161)](https://github.com/prowler-cloud/prowler/pull/6161) @@ -39,7 +40,6 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update CIS 4.0 for M365 provider. [(#7699)](https://github.com/prowler-cloud/prowler/pull/7699) - Update and upgrade CIS for all the providers [(#7738)](https://github.com/prowler-cloud/prowler/pull/7738) - Cover policies with conditions with SNS endpoint in `sns_topics_not_publicly_accessible`. [(#7750)](https://github.com/prowler-cloud/prowler/pull/7750) -- Fix `m365_powershell test_credentials` to use sanitized credentials. [(#7761)](https://github.com/prowler-cloud/prowler/pull/7761) - Change severity logic for `ec2_securitygroup_allow_ingress_from_internet_to_all_ports` check. [(#7764)](https://github.com/prowler-cloud/prowler/pull/7764) --- From 1c1c58c97585c3d1bf3a246f840d3a3ae48deeea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20Poyatos?= Date: Wed, 21 May 2025 11:56:54 +0200 Subject: [PATCH 15/25] feat(findings): Add new index for finding UID lookup (#7800) --- api/CHANGELOG.md | 7 +++++ api/pyproject.toml | 2 +- .../0024_findings_uid_index_partitions.py | 29 +++++++++++++++++++ .../0025_findings_uid_index_parent.py | 17 +++++++++++ api/src/backend/api/models.py | 4 +++ api/src/backend/api/specs/v1.yaml | 2 +- api/src/backend/api/v1/views.py | 2 +- 7 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 api/src/backend/api/migrations/0024_findings_uid_index_partitions.py create mode 100644 api/src/backend/api/migrations/0025_findings_uid_index_parent.py diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 96dc979d1e..2f0ba3cce0 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to the **Prowler API** are documented in this file. +## [v1.8.1] (Prowler v5.7.1) + +### Fixed +- Added database index to improve performance on finding lookup. [(#7800)](https://github.com/prowler-cloud/prowler/pull/7800) + +--- + ## [v1.8.0] (Prowler v5.7.0) ### Added diff --git a/api/pyproject.toml b/api/pyproject.toml index ce79dcbc7a..19190f8893 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -35,7 +35,7 @@ name = "prowler-api" package-mode = false # Needed for the SDK compatibility requires-python = ">=3.11,<3.13" -version = "1.8.0" +version = "1.8.1" [project.scripts] celery = "src.backend.config.settings.celery" diff --git a/api/src/backend/api/migrations/0024_findings_uid_index_partitions.py b/api/src/backend/api/migrations/0024_findings_uid_index_partitions.py new file mode 100644 index 0000000000..d0e237453e --- /dev/null +++ b/api/src/backend/api/migrations/0024_findings_uid_index_partitions.py @@ -0,0 +1,29 @@ +from functools import partial + +from django.db import migrations + +from api.db_utils import create_index_on_partitions, drop_index_on_partitions + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ("api", "0023_resources_lookup_optimization"), + ] + + operations = [ + migrations.RunPython( + partial( + create_index_on_partitions, + parent_table="findings", + index_name="find_tenant_uid_inserted_idx", + columns="tenant_id, uid, inserted_at DESC", + ), + reverse_code=partial( + drop_index_on_partitions, + parent_table="findings", + index_name="find_tenant_uid_inserted_idx", + ), + ) + ] diff --git a/api/src/backend/api/migrations/0025_findings_uid_index_parent.py b/api/src/backend/api/migrations/0025_findings_uid_index_parent.py new file mode 100644 index 0000000000..fa64ae925b --- /dev/null +++ b/api/src/backend/api/migrations/0025_findings_uid_index_parent.py @@ -0,0 +1,17 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("api", "0024_findings_uid_index_partitions"), + ] + + operations = [ + migrations.AddIndex( + model_name="finding", + index=models.Index( + fields=["tenant_id", "uid", "-inserted_at"], + name="find_tenant_uid_inserted_idx", + ), + ), + ] diff --git a/api/src/backend/api/models.py b/api/src/backend/api/models.py index 3b85d2208f..a427bcf9c9 100644 --- a/api/src/backend/api/models.py +++ b/api/src/backend/api/models.py @@ -755,6 +755,10 @@ class Finding(PostgresPartitionedModel, RowLevelSecurityProtectedModel): condition=Q(delta="new"), name="find_delta_new_idx", ), + models.Index( + fields=["tenant_id", "uid", "-inserted_at"], + name="find_tenant_uid_inserted_idx", + ), GinIndex(fields=["resource_services"], name="gin_find_service_idx"), GinIndex(fields=["resource_regions"], name="gin_find_region_idx"), GinIndex(fields=["resource_types"], name="gin_find_rtype_idx"), diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml index 4a5bdb4662..3f90c28cb1 100644 --- a/api/src/backend/api/specs/v1.yaml +++ b/api/src/backend/api/specs/v1.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: Prowler API - version: 1.8.0 + version: 1.8.1 description: |- Prowler API specification. diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index 04810f28df..b3e39fef50 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -260,7 +260,7 @@ class SchemaView(SpectacularAPIView): def get(self, request, *args, **kwargs): spectacular_settings.TITLE = "Prowler API" - spectacular_settings.VERSION = "1.8.0" + spectacular_settings.VERSION = "1.8.1" spectacular_settings.DESCRIPTION = ( "Prowler API specification.\n\nThis file is auto-generated." ) From 7d84d679354a1f04a3206b048e533a1e10aa5205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 21 May 2025 12:38:34 +0200 Subject: [PATCH 16/25] feat(gcp): add CIS 4.0 compliance framework (#7785) --- README.md | 2 +- dashboard/compliance/cis_4_0_gcp.py | 24 + prowler/CHANGELOG.md | 1 + prowler/compliance/gcp/cis_4.0_gcp.json | 1846 +++++++++++++++++ ...rate_compliance_json_from_csv_for_cis15.py | 44 +- 5 files changed, 1903 insertions(+), 14 deletions(-) create mode 100644 dashboard/compliance/cis_4_0_gcp.py create mode 100644 prowler/compliance/gcp/cis_4.0_gcp.json diff --git a/README.md b/README.md index 9064d36891..24f6201f46 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ prowler dashboard | Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/compliance/) | [Categories](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/misc/#categories) | |---|---|---|---|---| | AWS | 564 | 82 | 34 | 10 | -| GCP | 79 | 13 | 7 | 3 | +| GCP | 79 | 13 | 9 | 3 | | Azure | 140 | 18 | 8 | 3 | | Kubernetes | 83 | 7 | 5 | 7 | | GitHub | 3 | 2 | 1 | 0 | diff --git a/dashboard/compliance/cis_4_0_gcp.py b/dashboard/compliance/cis_4_0_gcp.py new file mode 100644 index 0000000000..94558f33ad --- /dev/null +++ b/dashboard/compliance/cis_4_0_gcp.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_cis + +warnings.filterwarnings("ignore") + + +def get_table(data): + aux = data[ + [ + "REQUIREMENTS_ID", + "REQUIREMENTS_DESCRIPTION", + "REQUIREMENTS_ATTRIBUTES_SECTION", + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ].copy() + + return get_section_containers_cis( + aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION" + ) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index bfe2c91df2..75a7f1ec42 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add new check `entra_users_mfa_capable`. [(#7734)](https://github.com/prowler-cloud/prowler/pull/7734) - Add new check `admincenter_organization_customer_lockbox_enabled`. [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732) - Add new check `admincenter_external_calendar_sharing_disabled`. [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733) +- Add CIS 4.0 compliance framework for GCP. [(7785)](https://github.com/prowler-cloud/prowler/pull/7785) ### Fixed - Fix `m365_powershell test_credentials` to use sanitized credentials. [(#7761)](https://github.com/prowler-cloud/prowler/pull/7761) diff --git a/prowler/compliance/gcp/cis_4.0_gcp.json b/prowler/compliance/gcp/cis_4.0_gcp.json new file mode 100644 index 0000000000..827d894789 --- /dev/null +++ b/prowler/compliance/gcp/cis_4.0_gcp.json @@ -0,0 +1,1846 @@ +{ + "Framework": "CIS", + "Version": "4.0", + "Provider": "GCP", + "Description": "The CIS Google Cloud Platform Foundations Benchmark provides prescriptive guidance for configuring security options for a subset of GCP with an emphasis on foundational, testable, and architecture agnostic settings.", + "Requirements": [ + { + "Id": "1.1", + "Description": "Ensure that Corporate Login Credentials are Used", + "Checks": [], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Use corporate login credentials instead of consumer accounts, such as Gmail accounts.", + "RationaleStatement": "It is recommended fully-managed corporate Google accounts be used for increased visibility, auditing, and controlling access to Cloud Platform resources. Email accounts based outside of the user's organization, such as consumer accounts, should not be used for business purposes.", + "ImpactStatement": "There will be increased overhead as maintaining accounts will now be required. For smaller organizations, this will not be an issue, but will balloon with size.", + "RemediationProcedure": "Remove all consumer Google accounts from IAM policies. Follow the documentation and setup corporate login accounts. **Prevention:** To ensure that no email addresses outside the organization can be granted IAM permissions to its Google Cloud projects, folders or organization, turn on the Organization Policy for `Domain Restricted Sharing`. Learn more at: [https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains](https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains)", + "AuditProcedure": "For each Google Cloud Platform project, list the accounts that have been granted access to that project: **From Google Cloud CLI** gcloud projects get-iam-policy PROJECT_ID Also list the accounts added on each folder: gcloud resource-manager folders get-iam-policy FOLDER_ID And list your organization's IAM policy: gcloud organizations get-iam-policy ORGANIZATION_ID No email accounts outside the organization domain should be granted permissions in the IAM policies. This excludes Google-owned service accounts.", + "AdditionalInformation": "", + "References": "https://support.google.com/work/android/answer/6371476:https://cloud.google.com/sdk/gcloud/reference/projects/get-iam-policy:https://cloud.google.com/sdk/gcloud/reference/resource-manager/folders/get-iam-policy:https://cloud.google.com/sdk/gcloud/reference/organizations/get-iam-policy:https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains:https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints", + "DefaultValue": "By default, no email addresses outside the organization's domain have access to its Google Cloud deployments, but any user email account can be added to the IAM policy for Google Cloud Platform projects, folders, or organizations." + } + ] + }, + { + "Id": "1.2", + "Description": "Ensure that Multi-Factor Authentication is 'Enabled' for All Non-Service Accounts", + "Checks": [], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Setup multi-factor authentication for Google Cloud Platform accounts.", + "RationaleStatement": "Multi-factor authentication requires more than one mechanism to authenticate a user. This secures user logins from attackers exploiting stolen or weak credentials.", + "ImpactStatement": "", + "RemediationProcedure": "**From Google Cloud Console** For each Google Cloud Platform project: 1. Identify non-service accounts. 1. Setup multi-factor authentication for each account.", + "AuditProcedure": "**From Google Cloud Console** For each Google Cloud Platform project, folder, or organization: 1. Identify non-service accounts. 1. Manually verify that multi-factor authentication for each account is set.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/solutions/securing-gcp-account-u2f:https://support.google.com/accounts/answer/185839", + "DefaultValue": "By default, multi-factor authentication is not set." + } + ] + }, + { + "Id": "1.3", + "Description": "Ensure that Security Key Enforcement is Enabled for All Admin Accounts", + "Checks": [], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Setup Security Key Enforcement for Google Cloud Platform admin accounts.", + "RationaleStatement": "Google Cloud Platform users with Organization Administrator roles have the highest level of privilege in the organization. These accounts should be protected with the strongest form of two-factor authentication: Security Key Enforcement. Ensure that admins use Security Keys to log in instead of weaker second factors like SMS or one-time passwords (OTP). Security Keys are actual physical keys used to access Google Organization Administrator Accounts. They send an encrypted signature rather than a code, ensuring that logins cannot be phished.", + "ImpactStatement": "If an organization administrator loses access to their security key, the user could lose access to their account. For this reason, it is important to set up backup security keys.", + "RemediationProcedure": "1. Identify users with the Organization Administrator role. 2. Setup Security Key Enforcement for each account. Learn more at: [https://cloud.google.com/security-key/](https://cloud.google.com/security-key/)", + "AuditProcedure": "1. Identify users with Organization Administrator privileges: gcloud organizations get-iam-policy ORGANIZATION_ID Look for members granted the role roles/resourcemanager.organizationAdmin. 2. Manually verify that Security Key Enforcement has been enabled for each account.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/security-key/:https://gsuite.google.com/learn-more/key_for_working_smarter_faster_and_more_securely.html", + "DefaultValue": "By default, Security Key Enforcement is not enabled for Organization Administrators." + } + ] + }, + { + "Id": "1.4", + "Description": "Ensure That There Are Only GCP-Managed Service Account Keys for Each Service Account", + "Checks": [ + "iam_sa_no_user_managed_keys" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "User-managed service accounts should not have user-managed keys.", + "RationaleStatement": "Anyone who has access to the keys will be able to access resources through the service account. GCP-managed keys are used by Cloud Platform services such as App Engine and Compute Engine. These keys cannot be downloaded. Google will keep the keys and automatically rotate them on an approximately weekly basis. User-managed keys are created, downloadable, and managed by users. They expire 10 years from creation. For user-managed keys, the user has to take ownership of key management activities which include: - Key storage - Key distribution - Key revocation - Key rotation - Protecting the keys from unauthorized users - Key recovery Even with key owner precautions, keys can be easily leaked by common development malpractices like checking keys into the source code or leaving them in the Downloads directory, or accidentally leaving them on support blogs/channels. It is recommended to prevent user-managed service account keys.", + "ImpactStatement": "Deleting user-managed service account keys may break communication with the applications using the corresponding keys.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the IAM page in the GCP Console using `https://console.cloud.google.com/iam-admin/iam` 2. In the left navigation pane, click `Service accounts`. All service accounts and their corresponding keys are listed. 3. Click the service account. 4. Click the `edit` and delete the keys. **From Google Cloud CLI** To delete a user managed Service Account Key, gcloud iam service-accounts keys delete --iam-account= **Prevention:** You can disable service account key creation through the `Disable service account key creation` Organization policy by visiting [https://console.cloud.google.com/iam-admin/orgpolicies/iam-disableServiceAccountKeyCreation](https://console.cloud.google.com/iam-admin/orgpolicies/iam-disableServiceAccountKeyCreation). Learn more at: [https://cloud.google.com/resource-manager/docs/organization-policy/restricting-service-accounts](https://cloud.google.com/resource-manager/docs/organization-policy/restricting-service-accounts) In addition, if you do not need to have service accounts in your project, you can also prevent the creation of service accounts through the `Disable service account creation` Organization policy: [https://console.cloud.google.com/iam-admin/orgpolicies/iam-disableServiceAccountCreation](https://console.cloud.google.com/iam-admin/orgpolicies/iam-disableServiceAccountCreation).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the IAM page in the GCP Console using `https://console.cloud.google.com/iam-admin/iam` 2. In the left navigation pane, click `Service accounts`. All service accounts and their corresponding keys are listed. 3. Click the service accounts and check if keys exist. **From Google Cloud CLI** List All the service accounts: gcloud iam service-accounts list Identify user-managed service accounts which have an account `EMAIL` ending with `iam.gserviceaccount.com` For each user-managed service account, list the keys managed by the user: gcloud iam service-accounts keys list --iam-account= --managed-by=user No keys should be listed.", + "AdditionalInformation": "A user-managed key cannot be created on GCP-Managed Service Accounts.", + "References": "https://cloud.google.com/iam/docs/understanding-service-accounts#managing_service_account_keys:https://cloud.google.com/resource-manager/docs/organization-policy/restricting-service-accounts", + "DefaultValue": "By default, there are no user-managed keys created for user-managed service accounts." + } + ] + }, + { + "Id": "1.5", + "Description": "Ensure That Service Account Has No Admin Privileges", + "Checks": [ + "iam_sa_no_administrative_privileges" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "A service account is a special Google account that belongs to an application or a VM, instead of to an individual end-user. The application uses the service account to call the service's Google API so that users aren't directly involved. It's recommended not to use admin access for ServiceAccount.", + "RationaleStatement": "Service accounts represent service-level security of the Resources (application or a VM) which can be determined by the roles assigned to it. Enrolling ServiceAccount with Admin rights gives full access to an assigned application or a VM. A ServiceAccount Access holder can perform critical actions like delete, update change settings, etc. without user intervention. For this reason, it's recommended that service accounts not have Admin rights.", + "ImpactStatement": "Removing `*Admin` or `*admin` or `Editor` or `Owner` role assignments from service accounts may break functionality that uses impacted service accounts. Required role(s) should be assigned to impacted service accounts in order to restore broken functionalities.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `IAM & admin/IAM` using `https://console.cloud.google.com/iam-admin/iam` 2. Under the `IAM` Tab look for `VIEW BY PRINCIPALS` 3. Filter `PRINCIPALS` using `type : Service account` 4. Look for the Service Account with the Principal nomenclature: `SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com` 5. Identify `User-Managed user created` service account with roles containing `*Admin` or `*admin` or role matching `Editor` or role matching `Owner` under `Role` Column. 6. Click on `Edit (Pencil Icon)` for the Service Account, it will open all the roles which are assigned to the Service Account. 7. Click the `Delete bin` icon to remove the role from the Principal (service account in this case) **From Google Cloud CLI** gcloud projects get-iam-policy PROJECT_ID --format json > iam.json 1. Using a text editor, Remove `Role` which contains `roles/*Admin` or `roles/*admin` or matched `roles/editor` or matches 'roles/owner`. Add a role to the bindings array that defines the group members and the role for those members. For example, to grant the role roles/appengine.appViewer to the `ServiceAccount` which is roles/editor, you would change the example shown below as follows: { bindings: [ { members: [ serviceAccount:our-project-123@appspot.gserviceaccount.com, ], role: roles/appengine.appViewer }, { members: [ user:email1@gmail.com ], role: roles/owner }, { members: [ serviceAccount:our-project-123@appspot.gserviceaccount.com, serviceAccount:123456789012-compute@developer.gserviceaccount.com ], role: roles/editor } ], etag: BwUjMhCsNvY= } 2. Update the project's IAM policy: gcloud projects set-iam-policy PROJECT_ID iam.json ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `IAM & admin/IAM` using `https://console.cloud.google.com/iam-admin/iam` 2. Under the `IAM` Tab look for `VIEW BY PRINCIPALS` 3. Filter `PRINCIPALS` using `type : Service account` 4. Look for the Service Account with the nomenclature: `SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com` 5. Ensure that there are no such Service Accounts with roles containing `*Admin` or `*admin` or role matching `Editor` or role matching `Owner` under `Role` column. **From Google Cloud CLI** 1. Get the policy that you want to modify, and write it to a JSON file: gcloud projects get-iam-policy PROJECT_ID --format json > iam.json 2. The contents of the JSON file will look similar to the following. Note that `role` of members group associated with each `serviceaccount` does not contain `*Admin` or `*admin` or does not match `roles/editor` or does not match `roles/owner`. This recommendation is only applicable to `User-Managed user-created` service accounts. These accounts have the nomenclature: `SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com`. Note that some Google-managed, Google-created service accounts have the same naming format, and should be excluded (e.g., `appsdev-apps-dev-script-auth@system.gserviceaccount.com` which needs the Owner role). **Sample Json output:** { bindings: [ { members: [ serviceAccount:our-project-123@appspot.gserviceaccount.com, ], role: roles/appengine.appAdmin }, { members: [ user:email1@gmail.com ], role: roles/owner }, { members: [ serviceAccount:our-project-123@appspot.gserviceaccount.com, serviceAccount:123456789012-compute@developer.gserviceaccount.com ], role: roles/editor } ], etag: BwUjMhCsNvY=, version: 1 }", + "AdditionalInformation": "Default (user-managed but not user-created) service accounts have the `Editor (roles/editor)` role assigned to them to support GCP services they offer. Such Service accounts are: `PROJECT_NUMBER-compute@developer.gserviceaccount.com`, `PROJECT_ID@appspot.gserviceaccount.com`.", + "References": "https://cloud.google.com/sdk/gcloud/reference/iam/service-accounts/:https://cloud.google.com/iam/docs/understanding-roles:https://cloud.google.com/iam/docs/understanding-service-accounts", + "DefaultValue": "User Managed (and not user-created) default service accounts have the `Editor (roles/editor)` role assigned to them to support GCP services they offer. By default, there are no roles assigned to `User Managed User created` service accounts." + } + ] + }, + { + "Id": "1.6", + "Description": "Ensure That IAM Users Are Not Assigned the Service Account User or Service Account Token Creator Roles at Project Level", + "Checks": [ + "iam_no_service_roles_at_project_level" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to assign the `Service Account User (iam.serviceAccountUser)` and `Service Account Token Creator (iam.serviceAccountTokenCreator)` roles to a user for a specific service account rather than assigning the role to a user at project level.", + "RationaleStatement": "A service account is a special Google account that belongs to an application or a virtual machine (VM), instead of to an individual end-user. Application/VM-Instance uses the service account to call the service's Google API so that users aren't directly involved. In addition to being an identity, a service account is a resource that has IAM policies attached to it. These policies determine who can use the service account. Users with IAM roles to update the App Engine and Compute Engine instances (such as App Engine Deployer or Compute Instance Admin) can effectively run code as the service accounts used to run these instances, and indirectly gain access to all the resources for which the service accounts have access. Similarly, SSH access to a Compute Engine instance may also provide the ability to execute code as that instance/Service account. Based on business needs, there could be multiple user-managed service accounts configured for a project. Granting the `iam.serviceAccountUser` or `iam.serviceAccountTokenCreator` roles to a user for a project gives the user access to all service accounts in the project, including service accounts that may be created in the future. This can result in elevation of privileges by using service accounts and corresponding `Compute Engine instances`. In order to implement `least privileges` best practices, IAM users should not be assigned the `Service Account User` or `Service Account Token Creator` roles at the project level. Instead, these roles should be assigned to a user for a specific service account, giving that user access to the service account. The `Service Account User` allows a user to bind a service account to a long-running job service, whereas the `Service Account Token Creator` role allows a user to directly impersonate (or assert) the identity of a service account.", + "ImpactStatement": "After revoking `Service Account User` or `Service Account Token Creator` roles at the project level from all impacted user account(s), these roles should be assigned to a user(s) for specific service account(s) according to business needs.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the IAM page in the GCP Console by visiting: [https://console.cloud.google.com/iam-admin/iam](https://console.cloud.google.com/iam-admin/iam). 2. Click on the filter table text bar. Type `Role: Service Account User` 3. Click the `Delete Bin` icon in front of the role `Service Account User` for every user listed as a result of a filter. 4. Click on the filter table text bar. Type `Role: Service Account Token Creator` 5. Click the `Delete Bin` icon in front of the role `Service Account Token Creator` for every user listed as a result of a filter. **From Google Cloud CLI** 1. Using a text editor, remove the bindings with the `roles/iam.serviceAccountUser` or `roles/iam.serviceAccountTokenCreator`. For example, you can use the iam.json file shown below as follows: { bindings: [ { members: [ serviceAccount:our-project-123@appspot.gserviceaccount.com, ], role: roles/appengine.appViewer }, { members: [ user:email1@gmail.com ], role: roles/owner }, { members: [ serviceAccount:our-project-123@appspot.gserviceaccount.com, serviceAccount:123456789012-compute@developer.gserviceaccount.com ], role: roles/editor } ], etag: BwUjMhCsNvY= } 2. Update the project's IAM policy: gcloud projects set-iam-policy PROJECT_ID iam.json ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the IAM page in the GCP Console by visiting [https://console.cloud.google.com/iam-admin/iam](https://console.cloud.google.com/iam-admin/iam) 2. Click on the filter table text bar, Type `Role: Service Account User`. 3. Ensure no user is listed as a result of the filter. 4. Click on the filter table text bar, Type `Role: Service Account Token Creator`. 3. Ensure no user is listed as a result of the filter. **From Google Cloud CLI** To ensure IAM users are not assigned Service Account User role at the project level: gcloud projects get-iam-policy PROJECT_ID --format json | jq '.bindings[].role' | grep roles/iam.serviceAccountUser gcloud projects get-iam-policy PROJECT_ID --format json | jq '.bindings[].role' | grep roles/iam.serviceAccountTokenCreator These commands should not return any output.", + "AdditionalInformation": "To assign the role `roles/iam.serviceAccountUser` or `roles/iam.serviceAccountTokenCreator` to a user role on a service account instead of a project: 1. Go to [https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts) 2. Select ` Target Project` 3. Select `target service account`. Click `Permissions` on the top bar. It will open permission pane on right side of the page 4. Add desired members with `Service Account User` or `Service Account Token Creator` role.", + "References": "https://cloud.google.com/iam/docs/service-accounts:https://cloud.google.com/iam/docs/granting-roles-to-service-accounts:https://cloud.google.com/iam/docs/understanding-roles:https://cloud.google.com/iam/docs/granting-changing-revoking-access:https://console.cloud.google.com/iam-admin/iam", + "DefaultValue": "By default, users do not have the Service Account User or Service Account Token Creator role assigned at project level." + } + ] + }, + { + "Id": "1.7", + "Description": "Ensure User-Managed/External Keys for Service Accounts Are Rotated Every 90 Days or Fewer", + "Checks": [ + "iam_sa_user_managed_key_rotate_90_days" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Service Account keys consist of a key ID (Private_key_Id) and Private key, which are used to sign programmatic requests users make to Google cloud services accessible to that particular service account. It is recommended that all Service Account keys are regularly rotated.", + "RationaleStatement": "Rotating Service Account keys will reduce the window of opportunity for an access key that is associated with a compromised or terminated account to be used. Service Account keys should be rotated to ensure that data cannot be accessed with an old key that might have been lost, cracked, or stolen. Each service account is associated with a key pair managed by Google Cloud Platform (GCP). It is used for service-to-service authentication within GCP. Google rotates the keys daily. GCP provides the option to create one or more user-managed (also called external key pairs) key pairs for use from outside GCP (for example, for use with Application Default Credentials). When a new key pair is created, the user is required to download the private key (which is not retained by Google). With external keys, users are responsible for keeping the private key secure and other management operations such as key rotation. External keys can be managed by the IAM API, gcloud command-line tool, or the Service Accounts page in the Google Cloud Platform Console. GCP facilitates up to 10 external service account keys per service account to facilitate key rotation.", + "ImpactStatement": "Rotating service account keys will break communication for dependent applications. Dependent applications need to be configured manually with the new key `ID` displayed in the `Service account keys` section and the `private key` downloaded by the user.", + "RemediationProcedure": "**From Google Cloud Console** **Delete any external (user-managed) Service Account Key older than 90 days:** 1. Go to `APIs & ServicesCredentials` using `https://console.cloud.google.com/apis/credentials` 2. In the Section `Service Account Keys`, for every external (user-managed) service account key where `creation date` is greater than or equal to the past 90 days, click `Delete Bin Icon` to `Delete Service Account key` **Create a new external (user-managed) Service Account Key for a Service Account:** 1. Go to `APIs & ServicesCredentials` using `https://console.cloud.google.com/apis/credentials` 2. Click `Create Credentials` and Select `Service Account Key`. 3. Choose the service account in the drop-down list for which an External (user-managed) Service Account key needs to be created. 4. Select the desired key type format among `JSON` or `P12`. 5. Click `Create`. It will download the `private key`. Keep it safe. 6. Click `Close` if prompted. 7. The site will redirect to the `APIs & ServicesCredentials` page. Make a note of the new `ID` displayed in the `Service account keys` section.", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `APIs & ServicesCredentials` using `https://console.cloud.google.com/apis/credentials` 2. In the section `Service Account Keys`, for every External (user-managed) service account key listed ensure the `creation date` is within the past 90 days. **From Google Cloud CLI** 1. List all Service accounts from a project. gcloud iam service-accounts list 2. For every service account list service account keys. gcloud iam service-accounts keys list --iam-account [Service_Account_Email_Id] --format=json 3. Ensure every service account key for a service account has a `validAfterTime` value within the past 90 days.", + "AdditionalInformation": "For user-managed Service Account key(s), key management is entirely the user's responsibility.", + "References": "https://cloud.google.com/iam/docs/understanding-service-accounts#managing_service_account_keys:https://cloud.google.com/sdk/gcloud/reference/iam/service-accounts/keys/list:https://cloud.google.com/iam/docs/service-accounts", + "DefaultValue": "GCP does not provide an automation option for External (user-managed) Service key rotation." + } + ] + }, + { + "Id": "1.8", + "Description": "Ensure That Separation of Duties Is Enforced While Assigning Service Account Related Roles to Users", + "Checks": [ + "iam_role_kms_enforce_separation_of_duties" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "It is recommended that the principle of 'Separation of Duties' is enforced while assigning service-account related roles to users.", + "RationaleStatement": "The built-in/predefined IAM role `Service Account admin` allows the user/identity to create, delete, and manage service account(s). The built-in/predefined IAM role `Service Account User` allows the user/identity (with adequate privileges on Compute and App Engine) to assign service account(s) to Apps/Compute Instances. Separation of duties is the concept of ensuring that one individual does not have all necessary permissions to be able to complete a malicious action. In Cloud IAM - service accounts, this could be an action such as using a service account to access resources that user should not normally have access to. Separation of duties is a business control typically used in larger organizations, meant to help avoid security or privacy incidents and errors. It is considered best practice. No user should have `Service Account Admin` and `Service Account User` roles assigned at the same time.", + "ImpactStatement": "The removed role should be assigned to a different user based on business needs.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `IAM & Admin/IAM` using `https://console.cloud.google.com/iam-admin/iam`. 2. For any member having both `Service Account Admin` and `Service account User` roles granted/assigned, click the `Delete Bin` icon to remove either role from the member. Removal of a role should be done based on the business requirements.", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `IAM & Admin/IAM` using `https://console.cloud.google.com/iam-admin/iam`. 2. Ensure no member has the roles `Service Account Admin` and `Service account User` assigned together. **From Google Cloud CLI** 1. List all users and role assignments: gcloud projects get-iam-policy [Project_ID] --format json | jq -r '[ ([Service_Account_Admin_and_User] | (., map(length*-))), ( [ .bindings[] | select(.role == roles/iam.serviceAccountAdmin or .role == roles/iam.serviceAccountUser).members[] ] | group_by(.) | map({User: ., Count: length}) | .[] | select(.Count == 2).User | unique ) ] | .[] | @tsv' 2. All common users listed under `Service_Account_Admin_and_User` are assigned both the `roles/iam.serviceAccountAdmin` and `roles/iam.serviceAccountUser` roles.", + "AdditionalInformation": "Users granted with Owner (roles/owner) and Editor (roles/editor) have privileges equivalent to `Service Account Admin` and `Service Account User`. To avoid the misuse, Owner and Editor roles should be granted to very limited users and Use of these primitive privileges should be minimal. These requirements are addressed in separate recommendations.", + "References": "https://cloud.google.com/iam/docs/service-accounts:https://cloud.google.com/iam/docs/understanding-roles:https://cloud.google.com/iam/docs/granting-roles-to-service-accounts", + "DefaultValue": "" + } + ] + }, + { + "Id": "1.9", + "Description": "Ensure That Cloud KMS Cryptokeys Are Not Anonymously or Publicly Accessible", + "Checks": [ + "kms_key_not_publicly_accessible" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended that the IAM policy on Cloud KMS `cryptokeys` should restrict anonymous and/or public access.", + "RationaleStatement": "Granting permissions to `allUsers` or `allAuthenticatedUsers` allows anyone to access the dataset. Such access might not be desirable if sensitive data is stored at the location. In this case, ensure that anonymous and/or public access to a Cloud KMS `cryptokey` is not allowed.", + "ImpactStatement": "Removing the binding for `allUsers` and `allAuthenticatedUsers` members denies accessing `cryptokeys` to anonymous or public users.", + "RemediationProcedure": "**From Google Cloud CLI** 1. List all Cloud KMS `Cryptokeys`. gcloud kms keys list --keyring=[key_ring_name] --location=global --format=json | jq '.[].name' 2. Remove IAM policy binding for a KMS key to remove access to `allUsers` and `allAuthenticatedUsers` using the below command. gcloud kms keys remove-iam-policy-binding [key_name] --keyring=[key_ring_name] --location=global --member='allAuthenticatedUsers' --role='[role]' gcloud kms keys remove-iam-policy-binding [key_name] --keyring=[key_ring_name] --location=global --member='allUsers' --role='[role]' ", + "AuditProcedure": "**From Google Cloud CLI** 1. List all Cloud KMS `Cryptokeys`. gcloud kms keys list --keyring=[key_ring_name] --location=global --format=json | jq '.[].name' 2. Ensure the below command's output does not contain `allUsers` or `allAuthenticatedUsers`. gcloud kms keys get-iam-policy [key_name] --keyring=[key_ring_name] --location=global --format=json | jq '.bindings[].members[]' ", + "AdditionalInformation": "[key_ring_name] : Is the resource ID of the key ring, which is the fully-qualified Key ring name. This value is case-sensitive and in the form: projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING You can retrieve the key ring resource ID using the Cloud Console: 1. Open the `Cryptographic Keys` page in the Cloud Console. 2. For the key ring whose resource ID you are retrieving, click the `More icon (3 vertical dots)`. 3. Click `Copy Resource ID`. The resource ID for the key ring is copied to your clipboard. [key_name] : Is the resource ID of the key, which is the fully-qualified CryptoKey name. This value is case-sensitive and in the form: projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY You can retrieve the key resource ID using the Cloud Console: 1. Open the `Cryptographic Keys` page in the Cloud Console. 2. Click the name of the key ring that contains the key. 3. For the key whose resource ID you are retrieving, click the `More icon (3 vertical dots)`. 4. Click `Copy Resource ID`. The resource ID for the key is copied to your clipboard. [role] : The role to remove the member from.", + "References": "https://cloud.google.com/sdk/gcloud/reference/kms/keys/remove-iam-policy-binding:https://cloud.google.com/sdk/gcloud/reference/kms/keys/set-iam-policy:https://cloud.google.com/sdk/gcloud/reference/kms/keys/get-iam-policy:https://cloud.google.com/kms/docs/object-hierarchy#key_resource_id", + "DefaultValue": "By default Cloud KMS does not allow access to `allUsers` or `allAuthenticatedUsers`." + } + ] + }, + { + "Id": "1.10", + "Description": "Ensure KMS Encryption Keys Are Rotated Within a Period of 90 Days", + "Checks": [ + "kms_key_rotation_enabled" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Google Cloud Key Management Service stores cryptographic keys in a hierarchical structure designed for useful and elegant access control management. The format for the rotation schedule depends on the client library that is used. For the gcloud command-line tool, the next rotation time must be in `ISO` or `RFC3339` format, and the rotation period must be in the form `INTEGER[UNIT]`, where units can be one of seconds (s), minutes (m), hours (h) or days (d).", + "RationaleStatement": "Set a key rotation period and starting time. A key can be created with a specified `rotation period`, which is the time between when new key versions are generated automatically. A key can also be created with a specified next rotation time. A key is a named object representing a `cryptographic key` used for a specific purpose. The key material, the actual bits used for `encryption`, can change over time as new key versions are created. A key is used to protect some `corpus of data`. A collection of files could be encrypted with the same key and people with `decrypt` permissions on that key would be able to decrypt those files. Therefore, it's necessary to make sure the `rotation period` is set to a specific time.", + "ImpactStatement": "After a successful key rotation, the older key version is required in order to decrypt the data encrypted by that previous key version.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `Cryptographic Keys` by visiting: [https://console.cloud.google.com/security/kms](https://console.cloud.google.com/security/kms). 2. Click on the specific key ring 3. From the list of keys, choose the specific key and Click on `Right side pop up the blade (3 dots)`. 4. Click on `Edit rotation period`. 5. On the pop-up window, `Select a new rotation period` in days which should be less than 90 and then choose `Starting on` date (date from which the rotation period begins). **From Google Cloud CLI** 1. Update and schedule rotation by `ROTATION_PERIOD` and `NEXT_ROTATION_TIME` for each key: gcloud kms keys update new --keyring=KEY_RING --location=LOCATION --next-rotation-time=NEXT_ROTATION_TIME --rotation-period=ROTATION_PERIOD ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `Cryptographic Keys` by visiting: [https://console.cloud.google.com/security/kms](https://console.cloud.google.com/security/kms). 2. Click on each key ring, then ensure each key in the keyring has `Next Rotation` set for less than 90 days from the current date. **From Google Cloud CLI** 1. Ensure rotation is scheduled by `ROTATION_PERIOD` and `NEXT_ROTATION_TIME` for each key : gcloud kms keys list --keyring= --location= --format=json'(rotationPeriod)' Ensure outcome values for `rotationPeriod` and `nextRotationTime` satisfy the below criteria: `rotationPeriod is <= 129600m` `rotationPeriod is <= 7776000s` `rotationPeriod is <= 2160h` `rotationPeriod is <= 90d` `nextRotationTime is <= 90days` from current DATE", + "AdditionalInformation": "- Key rotation does NOT re-encrypt already encrypted data with the newly generated key version. If you suspect unauthorized use of a key, you should re-encrypt the data protected by that key and then disable or schedule destruction of the prior key version. - It is not recommended to rely solely on irregular rotation, but rather to use irregular rotation if needed in conjunction with a regular rotation schedule.", + "References": "https://cloud.google.com/kms/docs/key-rotation#frequency_of_key_rotation:https://cloud.google.com/kms/docs/re-encrypt-data", + "DefaultValue": "By default, KMS encryption keys are rotated every 90 days." + } + ] + }, + { + "Id": "1.11", + "Description": "Ensure That Separation of Duties Is Enforced While Assigning KMS Related Roles to Users", + "Checks": [ + "iam_role_kms_enforce_separation_of_duties" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "It is recommended that the principle of 'Separation of Duties' is enforced while assigning KMS related roles to users.", + "RationaleStatement": "The built-in/predefined IAM role `Cloud KMS Admin` allows the user/identity to create, delete, and manage service account(s). The built-in/predefined IAM role `Cloud KMS CryptoKey Encrypter/Decrypter` allows the user/identity (with adequate privileges on concerned resources) to encrypt and decrypt data at rest using an encryption key(s). The built-in/predefined IAM role `Cloud KMS CryptoKey Encrypter` allows the user/identity (with adequate privileges on concerned resources) to encrypt data at rest using an encryption key(s). The built-in/predefined IAM role `Cloud KMS CryptoKey Decrypter` allows the user/identity (with adequate privileges on concerned resources) to decrypt data at rest using an encryption key(s). Separation of duties is the concept of ensuring that one individual does not have all necessary permissions to be able to complete a malicious action. In Cloud KMS, this could be an action such as using a key to access and decrypt data a user should not normally have access to. Separation of duties is a business control typically used in larger organizations, meant to help avoid security or privacy incidents and errors. It is considered best practice. No user(s) should have `Cloud KMS Admin` and any of the `Cloud KMS CryptoKey Encrypter/Decrypter`, `Cloud KMS CryptoKey Encrypter`, `Cloud KMS CryptoKey Decrypter` roles assigned at the same time.", + "ImpactStatement": "Removed roles should be assigned to another user based on business needs.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `IAM & Admin/IAM` using `https://console.cloud.google.com/iam-admin/iam` 2. For any member having `Cloud KMS Admin` and any of the `Cloud KMS CryptoKey Encrypter/Decrypter`, `Cloud KMS CryptoKey Encrypter`, `Cloud KMS CryptoKey Decrypter` roles granted/assigned, click the `Delete Bin` icon to remove the role from the member. Note: Removing a role should be done based on the business requirement.", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `IAM & Admin/IAM` by visiting: [https://console.cloud.google.com/iam-admin/iam](https://console.cloud.google.com/iam-admin/iam) 2. Ensure no member has the roles `Cloud KMS Admin` and any of the `Cloud KMS CryptoKey Encrypter/Decrypter`, `Cloud KMS CryptoKey Encrypter`, `Cloud KMS CryptoKey Decrypter` assigned. **From Google Cloud CLI** 1. List all users and role assignments: gcloud projects get-iam-policy PROJECT_ID 2. Ensure that there are no common users found in the member section for roles `cloudkms.admin` and any one of `Cloud KMS CryptoKey Encrypter/Decrypter`, `Cloud KMS CryptoKey Encrypter`, `Cloud KMS CryptoKey Decrypter`", + "AdditionalInformation": "Users granted with Owner (roles/owner) and Editor (roles/editor) have privileges equivalent to `Cloud KMS Admin` and `Cloud KMS CryptoKey Encrypter/Decrypter`. To avoid misuse, Owner and Editor roles should be granted to a very limited group of users. Use of these primitive privileges should be minimal.", + "References": "https://cloud.google.com/kms/docs/separation-of-duties", + "DefaultValue": "" + } + ] + }, + { + "Id": "1.12", + "Description": "Ensure API Keys Only Exist for Active Services", + "Checks": [ + "apikeys_key_exists" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "API Keys should only be used for services in cases where other authentication methods are unavailable. Unused keys with their permissions in tact may still exist within a project. Keys are insecure because they can be viewed publicly, such as from within a browser, or they can be accessed on a device where the key resides. It is recommended to use standard authentication flow instead.", + "RationaleStatement": "To avoid the security risk in using API keys, it is recommended to use standard authentication flow instead. Security risks involved in using API-Keys appear below: - API keys are simple encrypted strings - API keys do not identify the user or the application making the API request - API keys are typically accessible to clients, making it easy to discover and steal an API key", + "ImpactStatement": "Deleting an API key will break dependent applications (if any).", + "RemediationProcedure": "**From Console:** 1. Go to `APIs & ServicesCredentials` using https://console.cloud.google.com/apis/credentials. 1. In the section `API Keys`, to delete API Keys: Click the `Delete Bin Icon` in front of every `API Key Name`. **From Google Cloud Command Line** 1. Run the following from within the project you wish to audit gcloud services api-keys list --filter 1. Run the following command, providing the ID of the key or fully qualified identifier for the key for : gcloud services api-keys delete ", + "AuditProcedure": "**From Console:** 1. From within the Project you wish to audit Go to `APIs & ServicesCredentials`. 1. In the section `API Keys`, no API key should be listed. **From Google Cloud Command Line** 1. Run the following from within the project you wish to audit gcloud services api-keys list --filter 1. There should be no keys listed at the project level.", + "AdditionalInformation": "Google recommends using the standard authentication flow instead of using API keys. However, there are limited cases where API keys are more appropriate. For example, if there is a mobile application that needs to use the Google Cloud Translation API, but doesn't otherwise need a backend server, API keys are the simplest way to authenticate to that API. If a business requires API keys to be used, then the API keys should be secured properly.", + "References": "https://cloud.google.com/docs/authentication/api-keys:https://cloud.google.com/sdk/gcloud/reference/services/api-keys/list:https://cloud.google.com/docs/authentication:https://cloud.google.com/sdk/gcloud/reference/services/api-keys/delete", + "DefaultValue": "By default, API keys are not created for a project." + } + ] + }, + { + "Id": "1.13", + "Description": "Ensure API Keys Are Restricted To Use by Only Specified Hosts and Apps", + "Checks": [], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "API Keys should only be used for services in cases where other authentication methods are unavailable. In this case, unrestricted keys are insecure because they can be viewed publicly, such as from within a browser, or they can be accessed on a device where the key resides. It is recommended to restrict API key usage to trusted hosts, HTTP referrers and apps. It is recommended to use the more secure standard authentication flow instead.", + "RationaleStatement": "Security risks involved in using API-Keys appear below: - API keys are simple encrypted strings - API keys do not identify the user or the application making the API request - API keys are typically accessible to clients, making it easy to discover and steal an API key In light of these potential risks, Google recommends using the standard authentication flow instead of API keys. However, there are limited cases where API keys are more appropriate. For example, if there is a mobile application that needs to use the Google Cloud Translation API, but doesn't otherwise need a backend server, API keys are the simplest way to authenticate to that API. In order to reduce attack vectors, API-Keys can be restricted only to trusted hosts, HTTP referrers and applications.", + "ImpactStatement": "Setting `Application Restrictions` may break existing application functioning, if not done carefully.", + "RemediationProcedure": "**From Google Cloud Console** ***Leaving Keys in Place*** 1. Go to `APIs & ServicesCredentials` using `https://console.cloud.google.com/apis/credentials` 2. In the section `API Keys`, Click the `API Key Name`. The API Key properties display on a new page. 3. In the `Key restrictions` section, set the application restrictions to any of `HTTP referrers, IP addresses, Android apps, iOS apps`. 4. Click `Save`. 1. Repeat steps 2,3,4 for every unrestricted API key. **Note:** Do not set `HTTP referrers` to wild-cards (* or *.[TLD] or *.[TLD]/*) allowing access to any/wide HTTP referrer(s) Do not set `IP addresses` and referrer to `any host (0.0.0.0 or 0.0.0.0/0 or ::0)` ***Removing Keys*** Another option is to remove the keys entirely. 1. Go to `APIs & ServicesCredentials` using `https://console.cloud.google.com/apis/credentials` 2. In the section `API Keys`, select the checkbox next to each key you wish to remove 3. Select `Delete` and confirm.", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `APIs & ServicesCredentials` using `https://console.cloud.google.com/apis/credentials` 1. In the section `API Keys`, Click the `API Key Name`. The API Key properties display on a new page. 1. For every API Key, ensure the section `Key restrictions` parameter `Application restrictions` is not set to `None`. Or, 1. Ensure `Application restrictions` is set to `HTTP referrers` and the referrer is not set to wild-cards `(* or *.[TLD] or *.[TLD]/*) allowing access to any/wide HTTP referrer(s)` Or, 1. Ensure `Application restrictions` is set to `IP addresses` and referrer is not set to `any host (0.0.0.0 or 0.0.0.0/0 or ::0)` **From Google Cloud Command Line** 1. Run the following from within the project you wish to audit gcloud services api-keys list --filter=-restrictions:* --format=table[box](displayName:label='Key With No Restrictions') ", + "AdditionalInformation": "", + "References": "https://cloud.google.com/docs/authentication/api-keys:https://cloud.google.com/sdk/gcloud/reference/services/api-keys/list:https://cloud.google.com/sdk/gcloud/reference/services/api-keys/update", + "DefaultValue": "By default, `Application Restrictions` are set to `None`." + } + ] + }, + { + "Id": "1.14", + "Description": "Ensure API Keys Are Restricted to Only APIs That Application Needs Access", + "Checks": [ + "apikeys_api_restrictions_configured" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "API Keys should only be used for services in cases where other authentication methods are unavailable. API keys are always at risk because they can be viewed publicly, such as from within a browser, or they can be accessed on a device where the key resides. It is recommended to restrict API keys to use (call) only APIs required by an application.", + "RationaleStatement": "Security risks involved in using API-Keys are below: - API keys are simple encrypted strings - API keys do not identify the user or the application making the API request - API keys are typically accessible to clients, making it easy to discover and steal an API key In light of these potential risks, Google recommends using the standard authentication flow instead of API-Keys. However, there are limited cases where API keys are more appropriate. For example, if there is a mobile application that needs to use the Google Cloud Translation API, but doesn't otherwise need a backend server, API keys are the simplest way to authenticate to that API. In order to reduce attack surfaces by providing `least privileges`, API-Keys can be restricted to use (call) only APIs required by an application.", + "ImpactStatement": "Setting `API restrictions` may break existing application functioning, if not done carefully.", + "RemediationProcedure": "**From Console:** 1. Go to `APIs & ServicesCredentials` using `https://console.cloud.google.com/apis/credentials` 2. In the section `API Keys`, Click the `API Key Name`. The API Key properties display on a new page. 3. In the `Key restrictions` section go to `API restrictions`. 4. Click the `Select API` drop-down to choose an API. 5. Click `Save`. 6. Repeat steps 2,3,4,5 for every unrestricted API key **Note:** Do not set `API restrictions` to `Google Cloud APIs`, as this option allows access to all services offered by Google cloud. **From Google Cloud CLI** 1. List all API keys. gcloud services api-keys list 2. Note the `UID` of the key to add restrictions to. 3. Run the update command with the appropriate API target service or flags file with API target services and methods to add the required restrictions. Command with appropriate API target service: gcloud services api-keys update --api-target=service= Command with flags file: gcloud services api-keys update --flags-file=.yaml Content of flags file: - --api-target: service: foo.service.com - --api-target: service: bar.service.com methods: - foomethod - barmethod Note: Flags can be found by running: gcloud services api-keys update --help Note: Services can be found by running: gcloud services list or in this documentation https://cloud.google.com/sdk/gcloud/reference/services/api-keys/update", + "AuditProcedure": "**From Console:** 1. Go to `APIs & ServicesCredentials` using `https://console.cloud.google.com/apis/credentials` 2. In the section `API Keys`, Click the `API Key Name`. The API Key properties display on a new page. 3. For every API Key, ensure the section `Key restrictions` parameter `API restrictions` is not set to `None`. Or, Ensure `API restrictions` is not set to `Google Cloud APIs` **Note:** `Google Cloud APIs` represents the API collection of all cloud services/APIs offered by Google cloud. **From Google Cloud CLI** 1. List all API Keys. gcloud services api-keys list Each key should have a line that says `restrictions:` followed by varying parameters and NOT have a line saying `- service: cloudapis.googleapis.com` as shown here restrictions: apiTargets: - service: cloudapis.googleapis.com ", + "AdditionalInformation": "", + "References": "https://cloud.google.com/docs/authentication/api-keys:https://cloud.google.com/apis/docs/overview", + "DefaultValue": "By default, `API restrictions` are set to `None`." + } + ] + }, + { + "Id": "1.15", + "Description": "Ensure API Keys Are Rotated Every 90 Days", + "Checks": [ + "apikeys_key_rotated_in_90_days" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "API Keys should only be used for services in cases where other authentication methods are unavailable. If they are in use it is recommended to rotate API keys every 90 days.", + "RationaleStatement": "Security risks involved in using API-Keys are listed below: - API keys are simple encrypted strings - API keys do not identify the user or the application making the API request - API keys are typically accessible to clients, making it easy to discover and steal an API key Because of these potential risks, Google recommends using the standard authentication flow instead of API Keys. However, there are limited cases where API keys are more appropriate. For example, if there is a mobile application that needs to use the Google Cloud Translation API, but doesn't otherwise need a backend server, API keys are the simplest way to authenticate to that API. Once a key is stolen, it has no expiration, meaning it may be used indefinitely unless the project owner revokes or regenerates the key. Rotating API keys will reduce the window of opportunity for an access key that is associated with a compromised or terminated account to be used. API keys should be rotated to ensure that data cannot be accessed with an old key that might have been lost, cracked, or stolen.", + "ImpactStatement": "`Regenerating Key` may break existing client connectivity as the client will try to connect with older API keys they have stored on devices.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `APIs & ServicesCredentials` using `https://console.cloud.google.com/apis/credentials` 2. In the section `API Keys`, Click the `API Key Name`. The API Key properties display on a new page. 3. Click `REGENERATE KEY` to rotate API key. 4. Click `Save`. 5. Repeat steps 2,3,4 for every API key that has not been rotated in the last 90 days. **Note:** Do not set `HTTP referrers` to wild-cards (* or *.[TLD] or *.[TLD]/*) allowing access to any/wide HTTP referrer(s) Do not set `IP addresses` and referrer to `any host (0.0.0.0 or 0.0.0.0/0 or ::0)` **From Google Cloud CLI** There is not currently a way to regenerate and API key using gcloud commands. To 'regenerate' a key you will need to create a new one, duplicate the restrictions from the key being rotated, and delete the old key. 1. List existing keys. gcloud services api-keys list 2. Note the `UID` and restrictions of the key to regenerate. 3. Run this command to create a new API key. is the display name of the new key. ` gcloud services api-keys create --display-name= ` Note the `UID` of the newly created key 4. Run the update command to add required restrictions. Note - the restriction may vary for each key. Refer to this documentation for the appropriate flags. https://cloud.google.com/sdk/gcloud/reference/services/api-keys/update gcloud services api-keys update 5. Delete the old key. gcloud services api-keys delete ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `APIs & ServicesCredentials` using `https://console.cloud.google.com/apis/credentials` 2. In the section `API Keys`, for every key ensure the `creation date` is less than 90 days. **From Google Cloud CLI** To list keys, use the command gcloud services api-keys list Ensure the date in `createTime` is within 90 days.", + "AdditionalInformation": "There is no option to automatically regenerate (rotate) API keys periodically.", + "References": "https://developers.google.com/maps/api-security-best-practices#regenerate-apikey:https://cloud.google.com/sdk/gcloud/reference/services/api-keys/update", + "DefaultValue": "" + } + ] + }, + { + "Id": "1.16", + "Description": "Ensure Essential Contacts is Configured for Organization", + "Checks": [ + "iam_organization_essential_contacts_configured" + ], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended that Essential Contacts is configured to designate email addresses for Google Cloud services to notify of important technical or security information.", + "RationaleStatement": "Many Google Cloud services, such as Cloud Billing, send out notifications to share important information with Google Cloud users. By default, these notifications are sent to members with certain Identity and Access Management (IAM) roles. With Essential Contacts, you can customize who receives notifications by providing your own list of contacts.", + "ImpactStatement": "There is no charge for Essential Contacts except for the 'Technical Incidents' category that is only available to premium support customers.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `Essential Contacts` by visiting https://console.cloud.google.com/iam-admin/essential-contacts 2. Make sure the organization appears in the resource selector at the top of the page. The resource selector tells you what project, folder, or organization you are currently managing contacts for. 3. Click `+Add contact` 4. In the `Email` and `Confirm Email` fields, enter the email address of the contact. 5. From the `Notification categories` drop-down menu, select the notification categories that you want the contact to receive communications for. 6. Click `Save` **From Google Cloud CLI** 1. To add an organization Essential Contacts run a command: gcloud essential-contacts create --email= --notification-categories= --organization= ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `Essential Contacts` by visiting https://console.cloud.google.com/iam-admin/essential-contacts 2. Make sure the organization appears in the resource selector at the top of the page. The resource selector tells you what project, folder, or organization you are currently managing contacts for. 3. Ensure that appropriate email addresses are configured for each of the following notification categories: - `Legal` - `Security` - `Suspension` - `Technical` Alternatively, appropriate email addresses can be configured for the `All` notification category to receive all possible important notifications. **From Google Cloud CLI** 1. To list all configured organization Essential Contacts run a command: gcloud essential-contacts list --organization= 2. Ensure at least one appropriate email address is configured for each of the following notification categories: - `LEGAL` - `SECURITY` - `SUSPENSION` - `TECHNICAL` Alternatively, appropriate email addresses can be configured for the `ALL` notification category to receive all possible important notifications.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/resource-manager/docs/managing-notification-contacts", + "DefaultValue": "By default, there are no Essential Contacts configured. In the absence of an Essential Contact, the following IAM roles are used to identify users to notify for the following categories: - **Legal**: `roles/billing.admin` - **Security**: `roles/resourcemanager.organizationAdmin` - **Suspension**: `roles/owner` - **Technical**: `roles/owner` - **Technical Incidents**: `roles/owner`" + } + ] + }, + { + "Id": "1.17", + "Description": "Ensure Secrets are Not Stored in Cloud Functions Environment Variables by Using Secret Manager", + "Checks": [], + "Attributes": [ + { + "Section": "1 Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Google Cloud Functions allow you to host serverless code that is executed when an event is triggered, without the requiring the management a host operating system. These functions can also store environment variables to be used by the code that may contain authentication or other information that needs to remain confidential.", + "RationaleStatement": "It is recommended to use the Secret Manager, because environment variables are stored unencrypted, and accessible for all users who have access to the code.", + "ImpactStatement": "There should be no impact on the Cloud Function. There are minor costs after 10,000 requests a month to the Secret Manager API as well for a high use of other functions. Modifying the Cloud Function to use the Secret Manager may prevent it running to completion.", + "RemediationProcedure": "Enable Secret Manager API for your Project **From Google Cloud Console** 1. Within the project you wish to enable, select the Navigation hamburger menu in the top left. Hover over 'APIs & Services' to under the heading 'Serverless', then select 'Enabled APIs & Services' in the menu that opens up. 2. Click the button '+ Enable APIS and Services' 3. In the Search bar, search for 'Secret Manager API' and select it. 4. Click the blue box that says 'Enable'. **From Google Cloud CLI** 1. Within the project you wish to enable the API in, run the following command. gcloud services enable Secret Manager API Reviewing Environment Variables That Should Be Migrated to Secret Manager **From Google Cloud Console** 1. Log in to the Google Cloud Web Portal (https://console.cloud.google.com/) 1. Go to Cloud Functions 1. Click on a function name from the list 1. Click on Edit and review the Runtime environment for variables that should be secrets. Leave this list open for the next step. **From Google Cloud CLI** 1. To view a list of your cloud functions run gcloud functions list 2. For each cloud function run the following command. gcloud functions describe 3. Review the settings of the buildEnvironmentVariables and environmentVariables. Keep this information for the next step. Migrating Environment Variables to Secrets within the Secret Manager **From Google Cloud Console** 1. Go to the Secret Manager page in the Cloud Console. 1. On the Secret Manager page, click Create Secret. 1. On the Create secret page, under Name, enter the name of the Environment Variable you are replacing. This will then be the Secret Variable you will reference in your code. 1. You will also need to add a version. This is the actual value of the variable that will be referenced from the code. To add a secret version when creating the initial secret, in the Secret value field, enter the value from the Environment Variable you are replacing. 1. Leave the Regions section unchanged. 1. Click the Create secret button. 1. Repeat for all Environment Variables **From Google Cloud CLI** 1. Run the following command with the Environment Variable name you are replacing in the ``. It is most secure to point this command to a file with the Environment Variable value located in it, as if you entered it via command line it would show up in your shell’s command history. gcloud secrets create --data-file=/path/to/file.txt Granting your Runtime's Service Account Access to Secrets **From Google Cloud Console** 1. Within the project containing your runtime login with account that has the 'roles/secretmanager.secretAccessor' permission. 2. Select the Navigation hamburger menu in the top left. Hover over 'Security' to under the then select 'Secret Manager' in the menu that opens up. 3. Click the name of a secret listed in this screen. 4. If it is not already open, click Show Info Panel in this screen to open the panel. 5.In the info panel, click Add principal. 6.In the New principals field, enter the service account your function uses for its identity. (If you need help locating or updating your runtime's service account, please see the 'docs/securing/function-identity#runtime_service_account' reference.) 7. In the Select a role dropdown, choose Secret Manager and then Secret Manager Secret Accessor. **From Google Cloud CLI** As of the time of writing, using Google CLI to list Runtime variables is only in beta. Because this is likely to change we are not including it here. Modifying the Code to use the Secrets in Secret Manager **From Google Cloud Console** This depends heavily on which language your runtime is in. For the sake of the brevity of this recommendation, please see the '/docs/creating-and-accessing-secrets#access' reference for language specific instructions. **From Google Cloud CLI** This depends heavily on which language your runtime is in. For the sake of the brevity of this recommendation, please see the' /docs/creating-and-accessing-secrets#access' reference for language specific instructions. Deleting the Insecure Environment Variables **Be certain to do this step last.** Removing variables from code actively referencing them will prevent it from completing successfully. **From Google Cloud Console** 1. Select the Navigation hamburger menu in the top left. Hover over 'Security' then select 'Secret Manager' in the menu that opens up. 1. Click the name of a function. Click Edit. 1. Click Runtime, build and connections settings to expand the advanced configuration options. 1. Click 'Security’. Hover over the secret you want to remove, then click 'Delete'. 1. Click Next. Click Deploy. The latest version of the runtime will now reference the secrets in Secret Manager. **From Google Cloud CLI** gcloud functions deploy --remove-env-vars If you need to find the env vars to remove, they are from the step where ‘gcloud functions describe ``’ was run.", + "AuditProcedure": "Determine if Confidential Information is Stored in your Functions in Cleartext **From Google Cloud Console** 1. Within the project you wish to audit, select the Navigation hamburger menu in the top left. Scroll down to under the heading 'Serverless', then select 'Cloud Functions' 1. Click on a function name from the list 1. Open the Variables tab and you will see both buildEnvironmentVariables and environmentVariables 1. Review the variables whether they are secrets 1. Repeat step 3-5 until all functions are reviewed **From Google Cloud CLI** 1. To view a list of your cloud functions run gcloud functions list 2. For each cloud function in the list run the following command. gcloud functions describe 3. Review the settings of the buildEnvironmentVariables and environmentVariables. Determine if this is data that should not be publicly accessible. Determine if Secret Manager API is 'Enabled' for your Project **From Google Cloud Console** 1. Within the project you wish to audit, select the Navigation hamburger menu in the top left. Hover over 'APIs & Services' to under the heading 'Serverless', then select 'Enabled APIs & Services' in the menu that opens up. 1. Click the button '+ Enable APIS and Services' 1. In the Search bar, search for 'Secret Manager API' and select it. 1. If it is enabled, the blue box that normally says 'Enable' will instead say 'Manage'. **From Google Cloud CLI** 1. Within the project you wish to audit, run the following command. gcloud services list 2. If 'Secret Manager API' is in the list, it is enabled.", + "AdditionalInformation": "There are slight additional costs to using the Secret Manager API. Review the documentation to determine your organizations' needs.", + "References": "https://cloud.google.com/functions/docs/configuring/env-var#managing_secrets:https://cloud.google.com/secret-manager/docs/overview", + "DefaultValue": "By default Secret Manager is not enabled." + } + ] + }, + { + "Id": "2.1", + "Description": "Ensure That Cloud Audit Logging Is Configured Properly", + "Checks": [ + "iam_audit_logs_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended that Cloud Audit Logging is configured to track all admin activities and read, write access to user data.", + "RationaleStatement": "Cloud Audit Logging maintains two audit logs for each project, folder, and organization: Admin Activity and Data Access. 1. Admin Activity logs contain log entries for API calls or other administrative actions that modify the configuration or metadata of resources. Admin Activity audit logs are enabled for all services and cannot be configured. 2. Data Access audit logs record API calls that create, modify, or read user-provided data. These are disabled by default and should be enabled. There are three kinds of Data Access audit log information: - Admin read: Records operations that read metadata or configuration information. Admin Activity audit logs record writes of metadata and configuration information that cannot be disabled. - Data read: Records operations that read user-provided data. - Data write: Records operations that write user-provided data. It is recommended to have an effective default audit config configured in such a way that: 1. logtype is set to DATA_READ (to log user activity tracking) and DATA_WRITES (to log changes/tampering to user data). 2. audit config is enabled for all the services supported by the Data Access audit logs feature. 3. Logs should be captured for all users, i.e., there are no exempted users in any of the audit config sections. This will ensure overriding the audit config will not contradict the requirement.", + "ImpactStatement": "There is no charge for Admin Activity audit logs. Enabling the Data Access audit logs might result in your project being charged for the additional logs usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `Audit Logs` by visiting [https://console.cloud.google.com/iam-admin/audit](https://console.cloud.google.com/iam-admin/audit). 2. Follow the steps at [https://cloud.google.com/logging/docs/audit/configure-data-access](https://cloud.google.com/logging/docs/audit/configure-data-access) to enable audit logs for all Google Cloud services. Ensure that no exemptions are allowed. **From Google Cloud CLI** 1. To read the project's IAM policy and store it in a file run a command: gcloud projects get-iam-policy PROJECT_ID > /tmp/project_policy.yaml Alternatively, the policy can be set at the organization or folder level. If setting the policy at the organization level, it is not necessary to also set it for each folder or project. gcloud organizations get-iam-policy ORGANIZATION_ID > /tmp/org_policy.yaml gcloud resource-manager folders get-iam-policy FOLDER_ID > /tmp/folder_policy.yaml 2. Edit policy in /tmp/policy.yaml, adding or changing only the audit logs configuration to: **Note: Admin Activity Logs are enabled by default, and cannot be disabled. So they are not listed in these configuration changes.** auditConfigs: - auditLogConfigs: - logType: DATA_WRITE - logType: DATA_READ service: allServices **Note:** `exemptedMembers:` is not set as audit logging should be enabled for all the users 3. To write new IAM policy run command: gcloud organizations set-iam-policy ORGANIZATION_ID /tmp/org_policy.yaml gcloud resource-manager folders set-iam-policy FOLDER_ID /tmp/folder_policy.yaml gcloud projects set-iam-policy PROJECT_ID /tmp/project_policy.yaml If the preceding command reports a conflict with another change, then repeat these steps, starting with the first step.", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `Audit Logs` by visiting [https://console.cloud.google.com/iam-admin/audit](https://console.cloud.google.com/iam-admin/audit). 2. Ensure that Admin Read, Data Write, and Data Read are enabled for all Google Cloud services and that no exemptions are allowed. **From Google Cloud CLI** 1. List the Identity and Access Management (IAM) policies for the project, folder, or organization: gcloud organizations get-iam-policy ORGANIZATION_ID gcloud resource-manager folders get-iam-policy FOLDER_ID gcloud projects get-iam-policy PROJECT_ID 2. Policy should have a default auditConfigs section which has the logtype set to DATA_WRITES and DATA_READ for all services. Note that projects inherit settings from folders, which in turn inherit settings from the organization. When called, projects get-iam-policy, the result shows only the policies set in the project, not the policies inherited from the parent folder or organization. Nevertheless, if the parent folder has Cloud Audit Logging enabled, the project does as well. Sample output for default audit configs may look like this: auditConfigs: - auditLogConfigs: - logType: ADMIN_READ - logType: DATA_WRITE - logType: DATA_READ service: allServices 3. Any of the auditConfigs sections should not have parameter exemptedMembers: set, which will ensure that Logging is enabled for all users and no user is exempted.", + "AdditionalInformation": "- Log type `DATA_READ` is equally important to that of `DATA_WRITE` to track detailed user activities. - BigQuery Data Access logs are handled differently from other data access logs. BigQuery logs are enabled by default and cannot be disabled. They do not count against logs allotment and cannot result in extra logs charges.", + "References": "https://cloud.google.com/logging/docs/audit/:https://cloud.google.com/logging/docs/audit/configure-data-access", + "DefaultValue": "Admin Activity logs are always enabled. They cannot be disabled. Data Access audit logs are disabled by default because they can be quite large." + } + ] + }, + { + "Id": "2.2", + "Description": "Ensure That Sinks Are Configured for All Log Entries", + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to create a sink that will export copies of all the log entries. This can help aggregate logs from multiple projects and export them to a Security Information and Event Management (SIEM).", + "RationaleStatement": "Log entries are held in Cloud Logging. To aggregate logs, export them to a SIEM. To keep them longer, it is recommended to set up a log sink. Exporting involves writing a filter that selects the log entries to export, and choosing a destination in Cloud Storage, BigQuery, or Cloud Pub/Sub. The filter and destination are held in an object called a sink. To ensure all log entries are exported to sinks, ensure that there is no filter configured for a sink. Sinks can be created in projects, organizations, folders, and billing accounts.", + "ImpactStatement": "There are no costs or limitations in Cloud Logging for exporting logs, but the export destinations charge for storing or transmitting the log data.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `Logs Router` by visiting [https://console.cloud.google.com/logs/router](https://console.cloud.google.com/logs/router). 2. Click on the arrow symbol with `CREATE SINK` text. 3. Fill out the fields for `Sink details`. 4. Choose Cloud Logging bucket in the Select sink destination drop down menu. 5. Choose a log bucket in the next drop down menu. 6. If an inclusion filter is not provided for this sink, all ingested logs will be routed to the destination provided above. This may result in higher than expected resource usage. 7. Click `Create Sink`. For more information, see [https://cloud.google.com/logging/docs/export/configure_export_v2#dest-create](https://cloud.google.com/logging/docs/export/configure_export_v2#dest-create). **From Google Cloud CLI** To create a sink to export all log entries in a Google Cloud Storage bucket: gcloud logging sinks create storage.googleapis.com/DESTINATION_BUCKET_NAME Sinks can be created for a folder or organization, which will include all projects. gcloud logging sinks create storage.googleapis.com/DESTINATION_BUCKET_NAME --include-children --folder=FOLDER_ID | --organization=ORGANIZATION_ID **Note:** 1. A sink created by the command-line above will export logs in storage buckets. However, sinks can be configured to export logs into BigQuery, or Cloud Pub/Sub, or `Custom Destination`. 2. While creating a sink, the sink option `--log-filter` is not used to ensure the sink exports all log entries. 3. A sink can be created at a folder or organization level that collects the logs of all the projects underneath bypassing the option `--include-children` in the gcloud command.", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `Logs Router` by visiting [https://console.cloud.google.com/logs/router](https://console.cloud.google.com/logs/router). 2. For every sink, click the 3-dot button for Menu options and select `View sink details`. 3. Ensure there is at least one sink with an `empty` Inclusion filter. 4. Additionally, ensure that the resource configured as `Destination` exists. **From Google Cloud CLI** 1. Ensure that a sink with an `empty filter` exists. List the sinks for the project, folder or organization. If sinks are configured at a folder or organization level, they do not need to be configured for each project: gcloud logging sinks list --folder=FOLDER_ID | --organization=ORGANIZATION_ID | --project=PROJECT_ID The output should list at least one sink with an `empty filter`. 2. Additionally, ensure that the resource configured as `Destination` exists. See [https://cloud.google.com/sdk/gcloud/reference/beta/logging/sinks/list](https://cloud.google.com/sdk/gcloud/reference/beta/logging/sinks/list) for more information.", + "AdditionalInformation": "For Command-Line Audit and Remediation, the sink destination of type `Cloud Storage Bucket` is considered. However, the destination could be configured to `Cloud Storage Bucket` or `BigQuery` or `Cloud PubSub` or `Custom Destination`. Command Line Interface commands would change accordingly.", + "References": "https://cloud.google.com/logging/docs/reference/tools/gcloud-logging:https://cloud.google.com/logging/quotas:https://cloud.google.com/logging/docs/routing/overview:https://cloud.google.com/logging/docs/export/using_exported_logs:https://cloud.google.com/logging/docs/export/configure_export_v2:https://cloud.google.com/logging/docs/export/aggregated_exports:https://cloud.google.com/sdk/gcloud/reference/beta/logging/sinks/list", + "DefaultValue": "By default, there are no sinks configured." + } + ] + }, + { + "Id": "2.3", + "Description": "Ensure That Retention Policies on Cloud Storage Buckets Used for Exporting Logs Are Configured Using Bucket Lock", + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "Enabling retention policies on log buckets will protect logs stored in cloud storage buckets from being overwritten or accidentally deleted. It is recommended to set up retention policies and configure Bucket Lock on all storage buckets that are used as log sinks.", + "RationaleStatement": "Logs can be exported by creating one or more sinks that include a log filter and a destination. As Cloud Logging receives new log entries, they are compared against each sink. If a log entry matches a sink's filter, then a copy of the log entry is written to the destination. Sinks can be configured to export logs in storage buckets. It is recommended to configure a data retention policy for these cloud storage buckets and to lock the data retention policy; thus permanently preventing the policy from being reduced or removed. This way, if the system is ever compromised by an attacker or a malicious insider who wants to cover their tracks, the activity logs are definitely preserved for forensics and security investigations.", + "ImpactStatement": "Locking a bucket is an irreversible action. Once you lock a bucket, you cannot remove the retention policy from the bucket or decrease the retention period for the policy. You will then have to wait for the retention period for all items within the bucket before you can delete them, and then the bucket.", + "RemediationProcedure": "**From Google Cloud Console** 1. If sinks are **not** configured, first follow the instructions in the recommendation: `Ensure that sinks are configured for all Log entries`. 2. For each storage bucket configured as a sink, go to the Cloud Storage browser at `https://console.cloud.google.com/storage/browser/`. 3. Select the Bucket Lock tab near the top of the page. 4. In the Retention policy entry, click the Add Duration link. The `Set a retention policy` dialog box appears. 5. Enter the desired length of time for the retention period and click `Save policy`. 6. Set the `Lock status` for this retention policy to `Locked`. **From Google Cloud CLI** 1. To list all sinks destined to storage buckets: gcloud logging sinks list --folder=FOLDER_ID | --organization=ORGANIZATION_ID | --project=PROJECT_ID 2. For each storage bucket listed above, set a retention policy and lock it: gsutil retention set [TIME_DURATION] gs://[BUCKET_NAME] gsutil retention lock gs://[BUCKET_NAME] For more information, visit [https://cloud.google.com/storage/docs/using-bucket-lock#set-policy](https://cloud.google.com/storage/docs/using-bucket-lock#set-policy).", + "AuditProcedure": "**From Google Cloud Console** 1. Open the Cloud Storage browser in the Google Cloud Console by visiting [https://console.cloud.google.com/storage/browser](https://console.cloud.google.com/storage/browser). 2. In the Column display options menu, make sure `Retention policy` is checked. 3. In the list of buckets, the retention period of each bucket is found in the `Retention policy` column. If the retention policy is locked, an image of a lock appears directly to the left of the retention period. **From Google Cloud CLI** 1. To list all sinks destined to storage buckets: gcloud logging sinks list --folder=FOLDER_ID | --organization=ORGANIZATION_ID | --project=PROJECT_ID 2. For every storage bucket listed above, verify that retention policies and Bucket Lock are enabled: gsutil retention get gs://BUCKET_NAME For more information, see [https://cloud.google.com/storage/docs/using-bucket-lock#view-policy](https://cloud.google.com/storage/docs/using-bucket-lock#view-policy).", + "AdditionalInformation": "Caution: Locking a retention policy is an irreversible action. Once locked, you must delete the entire bucket in order to remove the bucket's retention policy. However, before you can delete the bucket, you must be able to delete all the objects in the bucket, which itself is only possible if all the objects have reached the retention period set by the retention policy.", + "References": "https://cloud.google.com/storage/docs/bucket-lock:https://cloud.google.com/storage/docs/using-bucket-lock:https://cloud.google.com/storage/docs/bucket-lock", + "DefaultValue": "By default, storage buckets used as log sinks do not have retention policies and Bucket Lock configured." + } + ] + }, + { + "Id": "2.4", + "Description": "Ensure Log Metric Filter and Alerts Exist for Project Ownership Assignments/Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "In order to prevent unnecessary project ownership assignments to users/service-accounts and further misuses of projects and resources, all `roles/Owner` assignments should be monitored. Members (users/Service-Accounts) with a role assignment to primitive role `roles/Owner` are project owners. The project owner has all the privileges on the project the role belongs to. These are summarized below: - All viewer permissions on all GCP Services within the project - Permissions for actions that modify the state of all GCP services within the project - Manage roles and permissions for a project and all resources within the project - Set up billing for a project Granting the owner role to a member (user/Service-Account) will allow that member to modify the Identity and Access Management (IAM) policy. Therefore, grant the owner role only if the member has a legitimate purpose to manage the IAM policy. This is because the project IAM policy contains sensitive access control data. Having a minimal set of users allowed to manage IAM policy will simplify any auditing that may be necessary.", + "RationaleStatement": "Project ownership has the highest level of privileges on a project. To avoid misuse of project resources, the project ownership assignment/change actions mentioned above should be monitored and alerted to concerned recipients. - Sending project ownership invites - Acceptance/Rejection of project ownership invite by user - Adding `roleOwner` to a user/service-account - Removing a user/Service account from `roleOwner`", + "ImpactStatement": "Enabling of logging may result in your project being charged for the additional logs usage.", + "RemediationProcedure": "**From Google Cloud Console** **Create the prescribed log metric:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics) and click CREATE METRIC. 2. Click the down arrow symbol on the `Filter Bar` at the rightmost corner and select `Convert to Advanced Filter`. 3. Clear any text and add: (protoPayload.serviceName=cloudresourcemanager.googleapis.com) AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=REMOVE AND protoPayload.serviceData.policyDelta.bindingDeltas.role=roles/owner) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=ADD AND protoPayload.serviceData.policyDelta.bindingDeltas.role=roles/owner) 4. Click `Submit Filter`. The logs display based on the filter text entered by the user. 5. In the `Metric Editor` menu on the right, fill out the name field. Set `Units` to `1` (default) and the `Type` to `Counter`. This ensures that the log metric counts the number of log entries matching the advanced logs query. 6. Click `Create Metric`. **Create the display prescribed Alert Policy:** 1. Identify the newly created metric under the section `User-defined Metrics` at [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. Click the 3-dot icon in the rightmost column for the desired metric and select `Create alert from Metric`. A new page opens. 3. Fill out the alert policy configuration and click `Save`. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero(0) for the most recent value will ensure that a notification is triggered for every owner change in the project: Set `Aggregator` to `Count` Set `Configuration`: - Condition: above - Threshold: 0 - For: most recent value 4. Configure the desired notifications channels in the section `Notifications`. 5. Name the policy and click `Save`. **From Google Cloud CLI** Create a prescribed Log Metric: - Use the command: gcloud beta logging metrics create - Reference for Command Usage: https://cloud.google.com/sdk/gcloud/reference/beta/logging/metrics/create Create prescribed Alert Policy - Use the command: gcloud alpha monitoring policies create - Reference for Command Usage: https://cloud.google.com/sdk/gcloud/reference/alpha/monitoring/policies/create", + "AuditProcedure": "**From Google Cloud Console** **Ensure that the prescribed log metric is present:** 1. Go to `Logging/Log-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. In the `User-defined Metrics` section, ensure that at least one metric `` is present with filter text: (protoPayload.serviceName=cloudresourcemanager.googleapis.com) AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=REMOVE AND protoPayload.serviceData.policyDelta.bindingDeltas.role=roles/owner) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=ADD AND protoPayload.serviceData.policyDelta.bindingDeltas.role=roles/owner) **Ensure that the prescribed Alerting Policy is present:** 3. Go to `Alerting` by visiting [https://console.cloud.google.com/monitoring/alerting](https://console.cloud.google.com/monitoring/alerting). 4. Under the `Policies` section, ensure that at least one alert policy exists for the log metric above. Clicking on the policy should show that it is configured with a condition. For example, `Violates when: Any logging.googleapis.com/user/ stream` `is above a threshold of zero(0) for greater than zero(0) seconds` means that the alert will trigger for any new owner change. Verify that the chosen alerting thresholds make sense for your organization. 5. Ensure that the appropriate notifications channels have been set up. **From Google Cloud CLI** **Ensure that the prescribed log metric is present:** 1. List the log metrics: gcloud logging metrics list --format json 2. Ensure that the output contains at least one metric with filter set to: (protoPayload.serviceName=cloudresourcemanager.googleapis.com) AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=REMOVE AND protoPayload.serviceData.policyDelta.bindingDeltas.role=roles/owner) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action=ADD AND protoPayload.serviceData.policyDelta.bindingDeltas.role=roles/owner) 3. Note the value of the property `metricDescriptor.type` for the identified metric, in the format `logging.googleapis.com/user/`. **Ensure that the prescribed alerting policy is present:** 4. List the alerting policies: gcloud alpha monitoring policies list --format json 5. Ensure that the output contains an least one alert policy where: - `conditions.conditionThreshold.filter` is set to `metric.type=logging.googleapis.com/user/` - AND `enabled` is set to `true`", + "AdditionalInformation": "1. Project ownership assignments for a user cannot be done using the gcloud utility as assigning project ownership to a user requires sending, and the user accepting, an invitation. 2. Project Ownership assignment to a service account does not send any invites. SetIAMPolicy to `role/owner`is directly performed on service accounts.", + "References": "https://cloud.google.com/logging/docs/logs-based-metrics/:https://cloud.google.com/monitoring/custom-metrics/:https://cloud.google.com/monitoring/alerts/:https://cloud.google.com/logging/docs/reference/tools/gcloud-logging", + "DefaultValue": "" + } + ] + }, + { + "Id": "2.5", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for Audit Configuration Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Google Cloud Platform (GCP) services write audit log entries to the Admin Activity and Data Access logs to help answer the questions of, who did what, where, and when? within GCP projects. Cloud audit logging records information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by GCP services. Cloud audit logging provides a history of GCP API calls for an account, including API calls made via the console, SDKs, command-line tools, and other GCP services.", + "RationaleStatement": "Admin activity and data access logs produced by cloud audit logging enable security analysis, resource change tracking, and compliance auditing. Configuring the metric filter and alerts for audit configuration changes ensures the recommended state of audit configuration is maintained so that all activities in the project are audit-able at any point in time.", + "ImpactStatement": "Enabling of logging may result in your project being charged for the additional logs usage.", + "RemediationProcedure": "**From Google Cloud Console** **Create the prescribed log metric:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics) and click CREATE METRIC. 2. Click the down arrow symbol on the `Filter Bar` at the rightmost corner and select `Convert to Advanced Filter`. 3. Clear any text and add: protoPayload.methodName=SetIamPolicy AND protoPayload.serviceData.policyDelta.auditConfigDeltas:* 4. Click `Submit Filter`. Display logs appear based on the filter text entered by the user. 5. In the `Metric Editor` menu on the right, fill out the name field. Set `Units` to `1` (default) and `Type` to `Counter`. This will ensure that the log metric counts the number of log entries matching the user's advanced logs query. 6. Click `Create Metric`. **Create a prescribed Alert Policy:** 1. Identify the new metric the user just created, under the section `User-defined Metrics` at [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. Click the 3-dot icon in the rightmost column for the new metric and select `Create alert from Metric`. A new page opens. 3. Fill out the alert policy configuration and click `Save`. Choose the alerting threshold and configuration that makes sense for the organization. For example, a threshold of zero(0) for the most recent value will ensure that a notification is triggered for every owner change in the project: Set `Aggregator` to `Count` Set `Configuration`: - Condition: above - Threshold: 0 - For: most recent value 4. Configure the desired notifications channels in the section `Notifications`. 5. Name the policy and click `Save`. **From Google Cloud CLI** Create a prescribed Log Metric: - Use the command: gcloud beta logging metrics create - Reference for command usage: [https://cloud.google.com/sdk/gcloud/reference/beta/logging/metrics/create ](https://cloud.google.com/sdk/gcloud/reference/beta/logging/metrics/create) Create prescribed Alert Policy - Use the command: gcloud alpha monitoring policies create - Reference for command usage: [https://cloud.google.com/sdk/gcloud/reference/alpha/monitoring/policies/create](https://cloud.google.com/sdk/gcloud/reference/alpha/monitoring/policies/create)", + "AuditProcedure": "**From Google Cloud Console** **Ensure the prescribed log metric is present:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. In the `User-defined Metrics` section, ensure that at least one metric `` is present with the filter text: protoPayload.methodName=SetIamPolicy AND protoPayload.serviceData.policyDelta.auditConfigDeltas:* **Ensure that the prescribed alerting policy is present:** 3. Go to `Alerting` by visiting [https://console.cloud.google.com/monitoring/alerting](https://console.cloud.google.com/monitoring/alerting). 4. Under the `Policies` section, ensure that at least one alert policy exists for the log metric above. Clicking on the policy should show that it is configured with a condition. For example, `Violates when: Any logging.googleapis.com/user/ stream` `is above a threshold of 0 for greater than zero(0) seconds`, means that the alert will trigger for any new owner change. Verify that the chosen alerting thresholds make sense for the user's organization. 5. Ensure that appropriate notifications channels have been set up. **From Google Cloud CLI** **Ensure that the prescribed log metric is present:** 1. List the log metrics: gcloud beta logging metrics list --format json 2. Ensure that the output contains at least one metric with the filter set to: protoPayload.methodName=SetIamPolicy AND protoPayload.serviceData.policyDelta.auditConfigDeltas:* 3. Note the value of the property `metricDescriptor.type` for the identified metric, in the format `logging.googleapis.com/user/`. **Ensure that the prescribed alerting policy is present:** 4. List the alerting policies: gcloud alpha monitoring policies list --format json 5. Ensure that the output contains at least one alert policy where: - `conditions.conditionThreshold.filter` is set to `metric.type=logging.googleapis.com/user/` - AND `enabled` is set to `true`", + "AdditionalInformation": "", + "References": "https://cloud.google.com/logging/docs/logs-based-metrics/:https://cloud.google.com/monitoring/custom-metrics/:https://cloud.google.com/monitoring/alerts/:https://cloud.google.com/logging/docs/reference/tools/gcloud-logging:https://cloud.google.com/logging/docs/audit/configure-data-access#getiampolicy-setiampolicy", + "DefaultValue": "" + } + ] + }, + { + "Id": "2.6", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for Custom Role Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended that a metric filter and alarm be established for changes to Identity and Access Management (IAM) role creation, deletion and updating activities.", + "RationaleStatement": "Google Cloud IAM provides predefined roles that give granular access to specific Google Cloud Platform resources and prevent unwanted access to other resources. However, to cater to organization-specific needs, Cloud IAM also provides the ability to create custom roles. Project owners and administrators with the Organization Role Administrator role or the IAM Role Administrator role can create custom roles. Monitoring role creation, deletion and updating activities will help in identifying any over-privileged role at early stages.", + "ImpactStatement": "Enabling of logging may result in your project being charged for the additional logs usage.", + "RemediationProcedure": "**From Console:** **Create the prescribed log metric:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics) and click CREATE METRIC. 1. Click the down arrow symbol on the `Filter Bar` at the rightmost corner and select `Convert to Advanced Filter`. 1. Clear any text and add: resource.type=iam_role AND (protoPayload.methodName = google.iam.admin.v1.CreateRole OR protoPayload.methodName=google.iam.admin.v1.DeleteRole OR protoPayload.methodName=google.iam.admin.v1.UpdateRole OR protoPayload.methodName=google.iam.admin.v1.UndeleteRole) 1. Click `Submit Filter`. Display logs appear based on the filter text entered by the user. 1. In the `Metric Editor` menu on the right, fill out the name field. Set `Units` to `1` (default) and `Type` to `Counter`. This ensures that the log metric counts the number of log entries matching the advanced logs query. 1. Click `Create Metric`. **Create a prescribed Alert Policy:** 1. Identify the new metric that was just created under the section `User-defined Metrics` at [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. Click the 3-dot icon in the rightmost column for the metric and select `Create alert from Metric`. A new page displays. 3. Fill out the alert policy configuration and click `Save`. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero(0) for the most recent value ensures that a notification is triggered for every owner change in the project: Set `Aggregator` to `Count` Set `Configuration`: - Condition: above - Threshold: 0 - For: most recent value 1. Configure the desired notification channels in the section `Notifications`. 1. Name the policy and click `Save`. **From Google Cloud CLI** Create the prescribed Log Metric: - Use the command: gcloud logging metrics create Create the prescribed Alert Policy: - Use the command: gcloud alpha monitoring policies create ", + "AuditProcedure": "**From Console:** **Ensure that the prescribed log metric is present:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. In the `User-defined Metrics` section, ensure that at least one metric `` is present with filter text: resource.type=iam_role AND (protoPayload.methodName=google.iam.admin.v1.CreateRole OR protoPayload.methodName=google.iam.admin.v1.DeleteRole OR protoPayload.methodName=google.iam.admin.v1.UpdateRole OR protoPayload.methodName=google.iam.admin.v1.UndeleteRole) **Ensure that the prescribed alerting policy is present:** 3. Go to `Alerting` by visiting [https://console.cloud.google.com/monitoring/alerting](https://console.cloud.google.com/monitoring/alerting). 4. Under the `Policies` section, ensure that at least one alert policy exists for the log metric above. Clicking on the policy should show that it is configured with a condition. For example, `Violates when: Any logging.googleapis.com/user/ stream` `is above a threshold of zero(0) for greater than zero(0) seconds` means that the alert will trigger for any new owner change. Verify that the chosen alerting thresholds make sense for the user's organization. 5. Ensure that the appropriate notifications channels have been set up. **From Google Cloud CLI** Ensure that the prescribed log metric is present: 1. List the log metrics: gcloud logging metrics list --format json 2. Ensure that the output contains at least one metric with the filter set to: resource.type=iam_role AND (protoPayload.methodName = google.iam.admin.v1.CreateRole OR protoPayload.methodName=google.iam.admin.v1.DeleteRole OR protoPayload.methodName=google.iam.admin.v1.UpdateRole OR protoPayload.methodName=google.iam.admin.v1.UndeleteRole) 3. Note the value of the property `metricDescriptor.type` for the identified metric, in the format `logging.googleapis.com/user/`. **Ensure that the prescribed alerting policy is present:** 4. List the alerting policies: gcloud alpha monitoring policies list --format json 5. Ensure that the output contains an least one alert policy where: - `conditions.conditionThreshold.filter` is set to `metric.type=logging.googleapis.com/user/` - AND `enabled` is set to `true`.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/logging/docs/logs-based-metrics/:https://cloud.google.com/monitoring/custom-metrics/:https://cloud.google.com/monitoring/alerts/:https://cloud.google.com/logging/docs/reference/tools/gcloud-logging:https://cloud.google.com/iam/docs/understanding-custom-roles", + "DefaultValue": "" + } + ] + }, + { + "Id": "2.7", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for VPC Network Firewall Rule Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "It is recommended that a metric filter and alarm be established for Virtual Private Cloud (VPC) Network Firewall rule changes.", + "RationaleStatement": "Monitoring for Create or Update Firewall rule events gives insight to network access changes and may reduce the time it takes to detect suspicious activity.", + "ImpactStatement": "Enabling of logging may result in your project being charged for the additional logs usage. These charges could be significant depending on the size of the organization.", + "RemediationProcedure": "**From Google Cloud Console** **Create the prescribed log metric:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics) and click CREATE METRIC. 2. Click the down arrow symbol on the `Filter Bar` at the rightmost corner and select `Convert to Advanced Filter`. 3. Clear any text and add: resource.type=gce_firewall_rule AND (protoPayload.methodName:compute.firewalls.patch OR protoPayload.methodName:compute.firewalls.insert OR protoPayload.methodName:compute.firewalls.delete) 4. Click `Submit Filter`. Display logs appear based on the filter text entered by the user. 5. In the `Metric Editor` menu on the right, fill out the name field. Set `Units` to `1` (default) and `Type` to `Counter`. This ensures that the log metric counts the number of log entries matching the advanced logs query. 6. Click `Create Metric`. **Create the prescribed Alert Policy:** 1. Identify the newly created metric under the section `User-defined Metrics` at [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. Click the 3-dot icon in the rightmost column for the new metric and select `Create alert from Metric`. A new page displays. 3. Fill out the alert policy configuration and click `Save`. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero(0) for the most recent value ensures that a notification is triggered for every owner change in the project: Set `Aggregator` to `Count` Set `Configuration`: - Condition: above - Threshold: 0 - For: most recent value 4. Configure the desired notifications channels in the section `Notifications`. 5. Name the policy and click `Save`. **From Google Cloud CLI** Create the prescribed Log Metric - Use the command: gcloud logging metrics create Create the prescribed alert policy: - Use the command: gcloud alpha monitoring policies create", + "AuditProcedure": "**From Google Cloud Console** **Ensure that the prescribed log metric is present:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. In the `User-defined Metrics` section, ensure at least one metric `` is present with this filter text: resource.type=gce_firewall_rule AND (protoPayload.methodName:compute.firewalls.patch OR protoPayload.methodName:compute.firewalls.insert OR protoPayload.methodName:compute.firewalls.delete) **Ensure that the prescribed alerting policy is present:** 3. Go to `Alerting` by visiting [https://console.cloud.google.com/monitoring/alerting](https://console.cloud.google.com/monitoring/alerting). 4. Under the `Policies` section, ensure that at least one alert policy exists for the log metric above. Clicking on the policy should show that it is configured with a condition. For example, `Violates when: Any logging.googleapis.com/user/ stream` `is above a threshold of zero(0) for greater than zero(0) seconds` means that the alert will trigger for any new owner change. Verify that the chosen alerting thresholds make sense for the user's organization. 5. Ensure that appropriate notification channels have been set up. **From Google Cloud CLI** **Ensure that the prescribed log metric is present:** 1. List the log metrics: gcloud logging metrics list --format json 2. Ensure that the output contains at least one metric with the filter set to: resource.type=gce_firewall_rule AND (protoPayload.methodName:compute.firewalls.patch OR protoPayload.methodName:compute.firewalls.insert OR protoPayload.methodName:compute.firewalls.delete) 3. Note the value of the property `metricDescriptor.type` for the identified metric, in the format `logging.googleapis.com/user/`. **Ensure that the prescribed alerting policy is present:** 4. List the alerting policies: gcloud alpha monitoring policies list --format json 5. Ensure that the output contains an least one alert policy where: - `conditions.conditionThreshold.filter` is set to `metric.type=logging.googleapis.com/user/` - AND `enabled` is set to `true`", + "AdditionalInformation": "", + "References": "https://cloud.google.com/logging/docs/logs-based-metrics/:https://cloud.google.com/monitoring/custom-metrics/:https://cloud.google.com/monitoring/alerts/:https://cloud.google.com/logging/docs/reference/tools/gcloud-logging:https://cloud.google.com/vpc/docs/firewalls", + "DefaultValue": "" + } + ] + }, + { + "Id": "2.8", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for VPC Network Route Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "It is recommended that a metric filter and alarm be established for Virtual Private Cloud (VPC) network route changes.", + "RationaleStatement": "Google Cloud Platform (GCP) routes define the paths network traffic takes from a VM instance to another destination. The other destination can be inside the organization VPC network (such as another VM) or outside of it. Every route consists of a destination and a next hop. Traffic whose destination IP is within the destination range is sent to the next hop for delivery. Monitoring changes to route tables will help ensure that all VPC traffic flows through an expected path.", + "ImpactStatement": "Enabling of logging may result in your project being charged for the additional logs usage. These charges could be significant depending on the size of the organization.", + "RemediationProcedure": "**From Google Cloud Console** **Create the prescribed Log Metric:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics) and click CREATE METRIC. 2. Click the down arrow symbol on the `Filter Bar` at the rightmost corner and select `Convert to Advanced Filter` 3. Clear any text and add: resource.type=gce_route AND (protoPayload.methodName:compute.routes.delete OR protoPayload.methodName:compute.routes.insert) 4. Click `Submit Filter`. Display logs appear based on the filter text entered by the user. 5. In the `Metric Editor` menu on the right, fill out the name field. Set `Units` to `1` (default) and `Type` to `Counter`. This ensures that the log metric counts the number of log entries matching the user's advanced logs query. 6. Click `Create Metric`. **Create the prescribed alert policy:** 1. Identify the newly created metric under the section `User-defined Metrics` at [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. Click the 3-dot icon in the rightmost column for the new metric and select `Create alert from Metric`. A new page displays. 3. Fill out the alert policy configuration and click `Save`. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero(0) for the most recent value ensures that a notification is triggered for every owner change in the project: Set `Aggregator` to `Count` Set `Configuration`: - Condition: above - Threshold: 0 - For: most recent value 4. Configure the desired notification channels in the section `Notifications`. 5. Name the policy and click `Save`. **From Google Cloud CLI** Create the prescribed Log Metric: - Use the command: gcloud logging metrics create Create the prescribed the alert policy: - Use the command: gcloud alpha monitoring policies create", + "AuditProcedure": "**From Google Cloud Console** **Ensure that the prescribed Log metric is present:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. In the `User-defined Metrics` section, ensure that at least one metric `` is present with the filter text: resource.type=gce_route AND (protoPayload.methodName:compute.routes.delete OR protoPayload.methodName:compute.routes.insert) **Ensure the prescribed alerting policy is present:** 3. Go to `Alerting` by visiting: [https://console.cloud.google.com/monitoring/alerting](https://console.cloud.google.com/monitoring/alerting). 4. Under the `Policies` section, ensure that at least one alert policy exists for the log metric above. Clicking on the policy should show that it is configured with a condition. For example, `Violates when: Any logging.googleapis.com/user/ stream` `is above a threshold of 0 for greater than zero(0) seconds` means that the alert will trigger for any new owner change. Verify that the chosen alert thresholds make sense for the user's organization. 5. Ensure that the appropriate notification channels have been set up. **From Google Cloud CLI** **Ensure the prescribed log metric is present:** 1. List the log metrics: gcloud logging metrics list --format json 2. Ensure that the output contains at least one metric with the filter set to: resource.type=gce_route AND (protoPayload.methodName:compute.routes.delete OR protoPayload.methodName:compute.routes.insert) 3. Note the value of the property `metricDescriptor.type` for the identified metric, in the format `logging.googleapis.com/user/`. **Ensure that the prescribed alerting policy is present:** 4. List the alerting policies: gcloud alpha monitoring policies list --format json 5. Ensure that the output contains an least one alert policy where: - `conditions.conditionThreshold.filter` is set to `metric.type=logging.googleapis.com/user/` - AND `enabled` is set to `true`", + "AdditionalInformation": "", + "References": "https://cloud.google.com/logging/docs/logs-based-metrics/:https://cloud.google.com/monitoring/custom-metrics/:https://cloud.google.com/monitoring/alerts/:https://cloud.google.com/logging/docs/reference/tools/gcloud-logging:https://cloud.google.com/storage/docs/access-control/iam:https://cloud.google.com/sdk/gcloud/reference/beta/logging/metrics/create:https://cloud.google.com/sdk/gcloud/reference/alpha/monitoring/policies/create", + "DefaultValue": "" + } + ] + }, + { + "Id": "2.9", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for VPC Network Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "It is recommended that a metric filter and alarm be established for Virtual Private Cloud (VPC) network changes.", + "RationaleStatement": "It is possible to have more than one VPC within a project. In addition, it is also possible to create a peer connection between two VPCs enabling network traffic to route between VPCs. Monitoring changes to a VPC will help ensure VPC traffic flow is not getting impacted.", + "ImpactStatement": "Enabling of logging may result in your project being charged for the additional logs usage. These charges could be significant depending on the size of the organization.", + "RemediationProcedure": "**From Google Cloud Console** **Create the prescribed log metric:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics) and click CREATE METRIC. 2. Click the down arrow symbol on `Filter Bar` at the rightmost corner and select `Convert to Advanced Filter`. 3. Clear any text and add: resource.type=gce_network AND (protoPayload.methodName:compute.networks.insert OR protoPayload.methodName:compute.networks.patch OR protoPayload.methodName:compute.networks.delete OR protoPayload.methodName:compute.networks.removePeering OR protoPayload.methodName:compute.networks.addPeering) 4. Click `Submit Filter`. Display logs appear based on the filter text entered by the user. 5. In the `Metric Editor` menu on the right, fill out the name field. Set `Units` to `1` (default) and `Type` to `Counter`. This ensures that the log metric counts the number of log entries matching the user's advanced logs query. 6. Click `Create Metric`. **Create the prescribed alert policy:** 1. Identify the newly created metric under the section `User-defined Metrics` at [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. Click the 3-dot icon in the rightmost column for the new metric and select `Create alert from Metric`. A new page appears. 3. Fill out the alert policy configuration and click `Save`. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of 0 for the most recent value will ensure that a notification is triggered for every owner change in the project: Set `Aggregator` to `Count` Set `Configuration`: - Condition: above - Threshold: 0 - For: most recent value 4. Configure the desired notification channels in the section `Notifications`. 5. Name the policy and click `Save`. **From Google Cloud CLI** Create the prescribed Log Metric: - Use the command: gcloud logging metrics create Create the prescribed alert policy: - Use the command: gcloud alpha monitoring policies create", + "AuditProcedure": "**From Google Cloud Console** **Ensure the prescribed log metric is present:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. In the `User-defined Metrics` section, ensure at least one metric `` is present with filter text: resource.type=gce_network AND (protoPayload.methodName:compute.networks.insert OR protoPayload.methodName:compute.networks.patch OR protoPayload.methodName:compute.networks.delete OR protoPayload.methodName:compute.networks.removePeering OR protoPayload.methodName:compute.networks.addPeering) **Ensure the prescribed alerting policy is present:** 3. Go to `Alerting` by visiting [https://console.cloud.google.com/monitoring/alerting](https://console.cloud.google.com/monitoring/alerting). 4. Under the `Policies` section, ensure that at least one alert policy exists for the log metric above. Clicking on the policy should show that it is configured with a condition. For example, `Violates when: Any logging.googleapis.com/user/ stream` `is above a threshold of 0 for greater than 0 seconds` means that the alert will trigger for any new owner change. Verify that the chosen alerting thresholds make sense for the user's organization. 5. Ensure that appropriate notification channels have been set up. **From Google Cloud CLI** **Ensure the log metric is present:** 1. List the log metrics: gcloud logging metrics list --format json 2. Ensure that the output contains at least one metric with filter set to: resource.type=gce_network AND protoPayload.methodName=beta.compute.networks.insert OR protoPayload.methodName=beta.compute.networks.patch OR protoPayload.methodName=v1.compute.networks.delete OR protoPayload.methodName=v1.compute.networks.removePeering OR protoPayload.methodName=v1.compute.networks.addPeering 3. Note the value of the property `metricDescriptor.type` for the identified metric, in the format `logging.googleapis.com/user/`. **Ensure the prescribed alerting policy is present:** 4. List the alerting policies: gcloud alpha monitoring policies list --format json 5. Ensure that the output contains at least one alert policy where: - `conditions.conditionThreshold.filter` is set to `metric.type=logging.googleapis.com/user/` - AND `enabled` is set to `true`", + "AdditionalInformation": "", + "References": "https://cloud.google.com/logging/docs/logs-based-metrics/:https://cloud.google.com/monitoring/custom-metrics/:https://cloud.google.com/monitoring/alerts/:https://cloud.google.com/logging/docs/reference/tools/gcloud-logging:https://cloud.google.com/vpc/docs/overview", + "DefaultValue": "" + } + ] + }, + { + "Id": "2.10", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for Cloud Storage IAM Permission Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "It is recommended that a metric filter and alarm be established for Cloud Storage Bucket IAM changes.", + "RationaleStatement": "Monitoring changes to cloud storage bucket permissions may reduce the time needed to detect and correct permissions on sensitive cloud storage buckets and objects inside the bucket.", + "ImpactStatement": "Enabling of logging may result in your project being charged for the additional logs usage. These charges could be significant depending on the size of the organization.", + "RemediationProcedure": "**From Google Cloud Console** **Create the prescribed log metric:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics) and click CREATE METRIC. 2. Click the down arrow symbol on the `Filter Bar` at the rightmost corner and select `Convert to Advanced Filter`. 3. Clear any text and add: resource.type=gcs_bucket AND protoPayload.methodName=storage.setIamPermissions 4. Click `Submit Filter`. Display logs appear based on the filter text entered by the user. 5. In the `Metric Editor` menu on right, fill out the name field. Set `Units` to `1` (default) and `Type` to `Counter`. This ensures that the log metric counts the number of log entries matching the user's advanced logs query. 6. Click `Create Metric`. **Create the prescribed Alert Policy:** 1. Identify the newly created metric under the section `User-defined Metrics` at [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. Click the 3-dot icon in the rightmost column for the new metric and select `Create alert from Metric`. A new page appears. 3. Fill out the alert policy configuration and click `Save`. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero(0) for the most recent value will ensure that a notification is triggered for every owner change in the project: Set `Aggregator` to `Count` Set `Configuration`: - Condition: above - Threshold: 0 - For: most recent value 4. Configure the desired notifications channels in the section `Notifications`. 5. Name the policy and click `Save`. **From Google Cloud CLI** Create the prescribed Log Metric: - Use the command: gcloud beta logging metrics create Create the prescribed alert policy: - Use the command: gcloud alpha monitoring policies create", + "AuditProcedure": "**From Google Cloud Console** **Ensure the prescribed log metric is present:** 1. For each project that contains cloud storage buckets, go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. In the `User-defined Metrics` section, ensure at least one metric `` is present with the filter text: resource.type=gcs_bucket AND protoPayload.methodName=storage.setIamPermissions **Ensure that the prescribed alerting policy is present:** 3. Go to `Alerting` by visiting [https://console.cloud.google.com/monitoring/alerting](https://console.cloud.google.com/monitoring/alerting). 4. Under the `Policies` section, ensure that at least one alert policy exists for the log metric above. Clicking on the policy should show that it is configured with a condition. For example, `Violates when: Any logging.googleapis.com/user/ stream` `is above a threshold of 0 for greater than 0 seconds` means that the alert will trigger for any new owner change. Verify that the chosen alerting thresholds make sense for the user's organization. 5. Ensure that the appropriate notifications channels have been set up. **From Google Cloud CLI** **Ensure that the prescribed log metric is present:** 1. List the log metrics: gcloud logging metrics list --format json 2. Ensure that the output contains at least one metric with the filter set to: resource.type=gcs_bucket AND protoPayload.methodName=storage.setIamPermissions 3. Note the value of the property `metricDescriptor.type` for the identified metric, in the format `logging.googleapis.com/user/`. **Ensure the prescribed alerting policy is present:** 4. List the alerting policies: gcloud alpha monitoring policies list --format json 5. Ensure that the output contains an least one alert policy where: - `conditions.conditionThreshold.filter` is set to `metric.type=logging.googleapis.com/user/` - AND `enabled` is set to `true`", + "AdditionalInformation": "", + "References": "https://cloud.google.com/logging/docs/logs-based-metrics/:https://cloud.google.com/monitoring/custom-metrics/:https://cloud.google.com/monitoring/alerts/:https://cloud.google.com/logging/docs/reference/tools/gcloud-logging:https://cloud.google.com/storage/docs/overview:https://cloud.google.com/storage/docs/access-control/iam-roles", + "DefaultValue": "" + } + ] + }, + { + "Id": "2.11", + "Description": "Ensure That the Log Metric Filter and Alerts Exist for SQL Instance Configuration Changes", + "Checks": [ + "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "It is recommended that a metric filter and alarm be established for SQL instance configuration changes.", + "RationaleStatement": "Monitoring changes to SQL instance configuration changes may reduce the time needed to detect and correct misconfigurations done on the SQL server. Below are a few of the configurable options which may the impact security posture of an SQL instance: - Enable auto backups and high availability: Misconfiguration may adversely impact business continuity, disaster recovery, and high availability - Authorize networks: Misconfiguration may increase exposure to untrusted networks", + "ImpactStatement": "Enabling of logging may result in your project being charged for the additional logs usage. These charges could be significant depending on the size of the organization.", + "RemediationProcedure": "**From Google Cloud Console** **Create the prescribed Log Metric:** 1. Go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics) and click CREATE METRIC. 2. Click the down arrow symbol on the `Filter Bar` at the rightmost corner and select `Convert to Advanced Filter`. 3. Clear any text and add: protoPayload.methodName=cloudsql.instances.update 4. Click `Submit Filter`. Display logs appear based on the filter text entered by the user. 5. In the `Metric Editor` menu on right, fill out the name field. Set `Units` to `1` (default) and `Type` to `Counter`. This ensures that the log metric counts the number of log entries matching the user's advanced logs query. 6. Click `Create Metric`. **Create the prescribed alert policy:** 1. Identify the newly created metric under the section `User-defined Metrics` at [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. Click the 3-dot icon in the rightmost column for the new metric and select `Create alert from Metric`. A new page appears. 3. Fill out the alert policy configuration and click `Save`. Choose the alerting threshold and configuration that makes sense for the user's organization. For example, a threshold of zero(0) for the most recent value will ensure that a notification is triggered for every owner change in the user's project: Set `Aggregator` to `Count` Set `Configuration`: - Condition: above - Threshold: 0 - For: most recent value 4. Configure the desired notification channels in the section `Notifications`. 5. Name the policy and click `Save`. **From Google Cloud CLI** Create the prescribed log metric: - Use the command: gcloud logging metrics create Create the prescribed alert policy: - Use the command: gcloud alpha monitoring policies create - Reference for command usage: [https://cloud.google.com/sdk/gcloud/reference/alpha/monitoring/policies/create](https://cloud.google.com/sdk/gcloud/reference/alpha/monitoring/policies/create)", + "AuditProcedure": "**From Google Cloud Console** **Ensure the prescribed log metric is present:** 1. For each project that contains Cloud SQL instances, go to `Logging/Logs-based Metrics` by visiting [https://console.cloud.google.com/logs/metrics](https://console.cloud.google.com/logs/metrics). 2. In the `User-defined Metrics` section, ensure that at least one metric `` is present with the filter text: protoPayload.methodName=cloudsql.instances.update **Ensure that the prescribed alerting policy is present:** 3. Go to `Alerting` by visiting [https://console.cloud.google.com/monitoring/alerting](https://console.cloud.google.com/monitoring/alerting). 4. Under the `Policies` section, ensure that at least one alert policy exists for the log metric above. Clicking on the policy should show that it is configured with a condition. For example, `Violates when: Any logging.googleapis.com/user/ stream` `is above a threshold of zero(0) for greater than zero(0) seconds` means that the alert will trigger for any new owner change. Verify that the chosen alerting thresholds make sense for the user's organization. 5. Ensure that the appropriate notifications channels have been set up. **From Google Cloud CLI** **Ensure that the prescribed log metric is present:** 1. List the log metrics: gcloud logging metrics list --format json 2. Ensure that the output contains at least one metric with the filter set to protoPayload.methodName=cloudsql.instances.update 3. Note the value of the property `metricDescriptor.type` for the identified metric, in the format `logging.googleapis.com/user/`. **Ensure that the prescribed alerting policy is present:** 4. List the alerting policies: gcloud alpha monitoring policies list --format json 5. Ensure that the output contains at least one alert policy where: - `conditions.conditionThreshold.filter` is set to `metric.type=logging.googleapis.com/user/` - AND `enabled` is set to `true`", + "AdditionalInformation": "", + "References": "https://cloud.google.com/logging/docs/logs-based-metrics/:https://cloud.google.com/monitoring/custom-metrics/:https://cloud.google.com/monitoring/alerts/:https://cloud.google.com/logging/docs/reference/tools/gcloud-logging:https://cloud.google.com/storage/docs/overview:https://cloud.google.com/sql/docs/:https://cloud.google.com/sql/docs/mysql/:https://cloud.google.com/sql/docs/postgres/", + "DefaultValue": "" + } + ] + }, + { + "Id": "2.12", + "Description": "Ensure That Cloud DNS Logging Is Enabled for All VPC Networks", + "Checks": [], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Cloud DNS logging records the queries from the name servers within your VPC to Stackdriver. Logged queries can come from Compute Engine VMs, GKE containers, or other GCP resources provisioned within the VPC.", + "RationaleStatement": "Security monitoring and forensics cannot depend solely on IP addresses from VPC flow logs, especially when considering the dynamic IP usage of cloud resources, HTTP virtual host routing, and other technology that can obscure the DNS name used by a client from the IP address. Monitoring of Cloud DNS logs provides visibility to DNS names requested by the clients within the VPC. These logs can be monitored for anomalous domain names, evaluated against threat intelligence, and Note: For full capture of DNS, firewall must block egress UDP/53 (DNS) and TCP/443 (DNS over HTTPS) to prevent client from using external DNS name server for resolution.", + "ImpactStatement": "Enabling of Cloud DNS logging might result in your project being charged for the additional logs usage.", + "RemediationProcedure": "**From Google Cloud CLI** **Add New DNS Policy With Logging Enabled** For each VPC network that needs a DNS policy with logging enabled: gcloud dns policies create enable-dns-logging --enable-logging --description=Enable DNS Logging --networks=VPC_NETWORK_NAME The VPC_NETWORK_NAME can be one or more networks in comma-separated list **Enable Logging for Existing DNS Policy** For each VPC network that has an existing DNS policy that needs logging enabled: gcloud dns policies update POLICY_NAME --enable-logging --networks=VPC_NETWORK_NAME The VPC_NETWORK_NAME can be one or more networks in comma-separated list", + "AuditProcedure": "**From Google Cloud CLI** 1. List all VPCs networks in a project: gcloud compute networks list --format=table[box,title='All VPC Networks'](name:label='VPC Network Name') 2. List all DNS policies, logging enablement, and associated VPC networks: gcloud dns policies list --flatten=networks[] --format=table[box,title='All DNS Policies By VPC Network'](name:label='Policy Name',enableLogging:label='Logging Enabled':align=center,networks.networkUrl.basename():label='VPC Network Name') Each VPC Network should be associated with a DNS policy with logging enabled.", + "AdditionalInformation": "Additional Info - Only queries that reach a name server are logged. Cloud DNS resolvers cache responses, queries answered from caches, or direct queries to an external DNS resolver outside the VPC are not logged.", + "References": "https://cloud.google.com/dns/docs/monitoring", + "DefaultValue": "Cloud DNS logging is disabled by default on each network." + } + ] + }, + { + "Id": "2.13", + "Description": "Ensure Cloud Asset Inventory Is Enabled", + "Checks": [ + "iam_cloud_asset_inventory_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "GCP Cloud Asset Inventory is services that provides a historical view of GCP resources and IAM policies through a time-series database. The information recorded includes metadata on Google Cloud resources, metadata on policies set on Google Cloud projects or resources, and runtime information gathered within a Google Cloud resource. Cloud Asset Inventory Service (CAIS) API enablement is not required for operation of the service, but rather enables the mechanism for searching/exporting CAIS asset data directly.", + "RationaleStatement": "The GCP resources and IAM policies captured by GCP Cloud Asset Inventory enables security analysis, resource change tracking, and compliance auditing. It is recommended GCP Cloud Asset Inventory be enabled for all GCP projects.", + "ImpactStatement": "", + "RemediationProcedure": "**From Google Cloud Console** Enable the Cloud Asset API: 1. Go to `API & Services/Library` by visiting [https://console.cloud.google.com/apis/library](https://console.cloud.google.com/apis/library) 2. Search for `Cloud Asset API` and select the result for _Cloud Asset API_ 3. Click the `ENABLE` button. **From Google Cloud CLI** Enable the Cloud Asset API: 1. Enable the Cloud Asset API through the services interface: gcloud services enable cloudasset.googleapis.com ", + "AuditProcedure": "**From Google Cloud Console** Ensure that the Cloud Asset API is enabled: 1. Go to `API & Services/Library` by visiting [https://console.cloud.google.com/apis/library](https://console.cloud.google.com/apis/library) 2. Search for `Cloud Asset API` and select the result for _Cloud Asset API_ 3. Ensure that `API Enabled` is displayed. **From Google Cloud CLI** Ensure that the Cloud Asset API is enabled: 1. Query enabled services: gcloud services list --enabled --filter=name:cloudasset.googleapis.com If the API is listed, then it is enabled. If the response is `Listed 0 items` the API is not enabled.", + "AdditionalInformation": "Additional info - Cloud Asset Inventory only keeps a five-week history of Google Cloud asset metadata. If a longer history is desired, automation to export the history to Cloud Storage or BigQuery should be evaluated. Users need not enable CAI API if they don't have any plans to export.", + "References": "https://cloud.google.com/asset-inventory/docs", + "DefaultValue": "The Cloud Asset Inventory API is disabled by default in each project." + } + ] + }, + { + "Id": "2.14", + "Description": "Ensure 'Access Transparency' is 'Enabled'", + "Checks": [], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "GCP Access Transparency provides audit logs for all actions that Google personnel take in your Google Cloud resources.", + "RationaleStatement": "Controlling access to your information is one of the foundations of information security. Given that Google Employees do have access to your organizations' projects for support reasons, you should have logging in place to view who, when, and why your information is being accessed.", + "ImpactStatement": "To use Access Transparency your organization will need to have at one of the following support level: Premium, Enterprise, Platinum, or Gold. There will be subscription costs associated with support, as well as increased storage costs for storing the logs. You will also not be able to turn Access Transparency off yourself, and you will need to submit a service request to Google Cloud Support.", + "RemediationProcedure": "**From Google Cloud Console** **Add privileges to enable Access Transparency** 1. From the Google Cloud Home, within the project you wish to check, click on the Navigation hamburger menu in the top left. Hover over the 'IAM and Admin'. Select `IAM` in the top of the column that opens. 2. Click the blue button the says `+add` at the top of the screen. 3. In the `principals` field, select a user or group by typing in their associated email address. 4. Click on the `role` field to expand it. In the filter field enter `Access Transparency Admin` and select it. 5. Click `save`. **Verify that the Google Cloud project is associated with a billing account** 1. From the Google Cloud Home, click on the Navigation hamburger menu in the top left. Select `Billing`. 2. If you see `This project is not associated with a billing account` you will need to enter billing information or switch to a project with a billing account. **Enable Access Transparency** 1. From the Google Cloud Home, click on the Navigation hamburger menu in the top left. Hover over the IAM & Admin Menu. Select `settings` in the middle of the column that opens. 2. Click the blue button labeled Enable `Access Transparency for Organization`", + "AuditProcedure": "**From Google Cloud Console** **Determine if Access Transparency is Enabled** 1. From the Google Cloud Home, click on the Navigation hamburger menu in the top left. Hover over the IAM & Admin Menu. Select `settings` in the middle of the column that opens. 2. The status will be under the heading `Access Transparency`. Status should be `Enabled`", + "AdditionalInformation": "To enable Access Transparency for your Google Cloud organization, your Google Cloud organization must have one of the following customer support levels: Premium, Enterprise, Platinum, or Gold.", + "References": "https://cloud.google.com/cloud-provider-access-management/access-transparency/docs/overview:https://cloud.google.com/cloud-provider-access-management/access-transparency/docs/enable:https://cloud.google.com/cloud-provider-access-management/access-transparency/docs/reading-logs:https://cloud.google.com/cloud-provider-access-management/access-transparency/docs/reading-logs#justification_reason_codes:https://cloud.google.com/cloud-provider-access-management/access-transparency/docs/supported-services", + "DefaultValue": "By default Access Transparency is not enabled." + } + ] + }, + { + "Id": "2.15", + "Description": "Ensure 'Access Approval' is 'Enabled'", + "Checks": [ + "iam_account_access_approval_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "GCP Access Approval enables you to require your organizations' explicit approval whenever Google support try to access your projects. You can then select users within your organization who can approve these requests through giving them a security role in IAM. All access requests display which Google Employee requested them in an email or Pub/Sub message that you can choose to Approve. This adds an additional control and logging of who in your organization approved/denied these requests.", + "RationaleStatement": "Controlling access to your information is one of the foundations of information security. Google Employees do have access to your organizations' projects for support reasons. With Access Approval, organizations can then be certain that their information is accessed by only approved Google Personnel.", + "ImpactStatement": "To use Access Approval your organization will need have enabled Access Transparency and have at one of the following support level: Enhanced or Premium. There will be subscription costs associated with these support levels, as well as increased storage costs for storing the logs. You will also not be able to turn the Access Transparency which Access Approval depends on, off yourself. To do so you will need to submit a service request to Google Cloud Support. There will also be additional overhead in managing user permissions. There may also be a potential delay in support times as Google Personnel will have to wait for their access to be approved.", + "RemediationProcedure": "**From Google Cloud Console** 1. From the Google Cloud Home, within the project you wish to enable, click on the Navigation hamburger menu in the top left. Hover over the `Security` Menu. Select `Access Approval` in the middle of the column that opens. 2. The status will be displayed here. On this screen, there is an option to click `Enroll`. If it is greyed out and you see an error bar at the top of the screen that says `Access Transparency is not enabled` please view the corresponding reference within this section to enable it. 3. In the second screen click `Enroll`. **Grant an IAM Group or User the role with permissions to Add Users to be Access Approval message Recipients** 1. From the Google Cloud Home, within the project you wish to enable, click on the Navigation hamburger menu in the top left. Hover over the `IAM and Admin`. Select `IAM` in the middle of the column that opens. 2. Click the blue button the says `+ ADD` at the top of the screen. 3. In the `principals` field, select a user or group by typing in their associated email address. 4. Click on the role field to expand it. In the filter field enter `Access Approval Approver` and select it. 5. Click `save`. **Add a Group or User as an Approver for Access Approval Requests** 1. As a user with the `Access Approval Approver` permission, within the project where you wish to add an email address to which request will be sent, click on the Navigation hamburger menu in the top left. Hover over the `Security` Menu. Select `Access Approval` in the middle of the column that opens. 2. Click `Manage Settings` 3. Under `Set up approval notifications`, enter the email address associated with a Google Cloud User or Group you wish to send Access Approval requests to. All future access approvals will be sent as emails to this address. **From Google Cloud CLI** 1. To update all services in an entire project, run the following command from an account that has permissions as an 'Approver for Access Approval Requests' gcloud access-approval settings update --project= --enrolled_services=all --notification_emails='@' ", + "AuditProcedure": "**From Google Cloud Console** **Determine if Access Transparency is Enabled as it is a Dependency** 1. From the Google Cloud Home inside the project you wish to audit, click on the Navigation hamburger menu in the top left. Hover over the `IAM & Admin` Menu. Select `settings` in the middle of the column that opens. 2. The status should be Enabled' under the heading `Access Transparency` **Determine if Access Approval is Enabled** 1. From the Google Cloud Home, within the project you wish to check, click on the Navigation hamburger menu in the top left. Hover over the `Security` Menu. Select `Access Approval` in the middle of the column that opens. 2. The status will be displayed here. If you see a screen saying you need to enroll in Access Approval, it is not enabled. **From Google Cloud CLI** **Determine if Access Approval is Enabled** 1. From within the project you wish to audit, run the following command. gcloud access-approval settings get 2. The status will be displayed in the output. IF Access Approval is not enabled you should get this output: API [accessapproval.googleapis.com] not enabled on project [-----]. Would you like to enable and retry (this will take a few minutes)? (y/N)? After entering `Y` if you get the following output, it means that `Access Transparency` is not enabled: ERROR: (gcloud.access-approval.settings.get) FAILED_PRECONDITION: Precondition check failed. ", + "AdditionalInformation": "The recipients of Access Requests will also need to be logged into a Google Cloud account associated with an email address in this list. To approve requests they can click approve within the email. Or they can view requests at the the Access Approval page within the Security submenu.", + "References": "https://cloud.google.com/cloud-provider-access-management/access-approval/docs:https://cloud.google.com/cloud-provider-access-management/access-approval/docs/overview:https://cloud.google.com/cloud-provider-access-management/access-approval/docs/quickstart-custom-key:https://cloud.google.com/cloud-provider-access-management/access-approval/docs/supported-services:https://cloud.google.com/cloud-provider-access-management/access-approval/docs/view-historical-requests", + "DefaultValue": "By default Access Approval and its dependency of Access Transparency are not enabled." + } + ] + }, + { + "Id": "2.16", + "Description": "Ensure Logging is enabled for HTTP(S) Load Balancer", + "Checks": [ + "compute_loadbalancer_logging_enabled" + ], + "Attributes": [ + { + "Section": "2 Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "Logging enabled on a HTTPS Load Balancer will show all network traffic and its destination.", + "RationaleStatement": "Logging will allow you to view HTTPS network traffic to your web applications.", + "ImpactStatement": "On high use systems with a high percentage sample rate, the logging file may grow to high capacity in a short amount of time. Ensure that the sample rate is set appropriately so that storage costs are not exorbitant.", + "RemediationProcedure": "**From Google Cloud Console** 1. From Google Cloud home open the Navigation Menu in the top left. 1. Under the `Networking` heading select `Network services`. 1. Select the HTTPS load-balancer you wish to audit. 1. Select `Edit` then `Backend Configuration`. 1. Select `Edit` on the corresponding backend service. 1. Click `Enable Logging`. 1. Set `Sample Rate` to a desired value. This is a percentage as a decimal point. 1.0 is 100%. **From Google Cloud CLI** 1. Run the following command gcloud compute backend-services update --region=REGION --enable-logging --logging-sample-rate= ", + "AuditProcedure": "**From Google Cloud Console** 1. From Google Cloud home open the Navigation Menu in the top left. 1. Under the `Networking` heading select `Network services`. 1. Select the HTTPS load-balancer you wish to audit. 1. Select `Edit` then `Backend Configuration`. 1. Select `Edit` on the corresponding backend service. 1. Ensure that `Enable Logging` is selected. Also ensure that `Sample Rate` is set to an appropriate level for your needs. **From Google Cloud CLI** 1. Run the following command gcloud compute backend-services describe 1. Ensure that enable-logging is enabled and sample rate is set to your desired level.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/load-balancing/:https://cloud.google.com/load-balancing/docs/https/https-logging-monitoring#gcloud:-global-mode:https://cloud.google.com/sdk/gcloud/reference/compute/backend-services/", + "DefaultValue": "By default logging for https load balancing is disabled. When logging is enabled it sets the default sample rate as 1.0 or 100%. Ensure this value fits the need of your organization to avoid high storage costs." + } + ] + }, + { + "Id": "3.1", + "Description": "Ensure That the Default Network Does Not Exist in a Project", + "Checks": [ + "compute_network_default_in_use" + ], + "Attributes": [ + { + "Section": "3 Networking", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "To prevent use of `default` network, a project should not have a `default` network.", + "RationaleStatement": "The `default` network has a preconfigured network configuration and automatically generates the following insecure firewall rules: - default-allow-internal: Allows ingress connections for all protocols and ports among instances in the network. - default-allow-ssh: Allows ingress connections on TCP port 22(SSH) from any source to any instance in the network. - default-allow-rdp: Allows ingress connections on TCP port 3389(RDP) from any source to any instance in the network. - default-allow-icmp: Allows ingress ICMP traffic from any source to any instance in the network. These automatically created firewall rules do not get audit logged by default. Furthermore, the default network is an auto mode network, which means that its subnets use the same predefined range of IP addresses, and as a result, it's not possible to use Cloud VPN or VPC Network Peering with the default network. Based on organization security and networking requirements, the organization should create a new network and delete the `default` network.", + "ImpactStatement": "When an organization deletes the default network, it will need to remove all asests from that network and migrate them to a new network.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the `VPC networks` page by visiting: [https://console.cloud.google.com/networking/networks/list](https://console.cloud.google.com/networking/networks/list). 2. Click the network named `default`. 2. On the network detail page, click `EDIT`. 3. Click `DELETE VPC NETWORK`. 4. If needed, create a new network to replace the default network. **From Google Cloud CLI** For each Google Cloud Platform project, 1. Delete the default network: gcloud compute networks delete default 2. If needed, create a new network to replace it: gcloud compute networks create NETWORK_NAME **Prevention:** The user can prevent the default network and its insecure default firewall rules from being created by setting up an Organization Policy to `Skip default network creation` at [https://console.cloud.google.com/iam-admin/orgpolicies/compute-skipDefaultNetworkCreation](https://console.cloud.google.com/iam-admin/orgpolicies/compute-skipDefaultNetworkCreation).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the `VPC networks` page by visiting: [https://console.cloud.google.com/networking/networks/list](https://console.cloud.google.com/networking/networks/list). 2. Ensure that a network with the name `default` is not present. **From Google Cloud CLI** 1. Set the project name in the Google Cloud Shell: gcloud config set project PROJECT_ID 2. List the networks configured in that project: gcloud compute networks list It should not list `default` as one of the available networks in that project.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/compute/docs/networking#firewall_rules:https://cloud.google.com/compute/docs/reference/latest/networks/insert:https://cloud.google.com/compute/docs/reference/latest/networks/delete:https://cloud.google.com/vpc/docs/firewall-rules-logging:https://cloud.google.com/vpc/docs/vpc#default-network:https://cloud.google.com/sdk/gcloud/reference/compute/networks/delete", + "DefaultValue": "By default, for each project, a `default` network is created." + } + ] + }, + { + "Id": "3.2", + "Description": "Ensure Legacy Networks Do Not Exist for Older Projects", + "Checks": [ + "compute_network_not_legacy" + ], + "Attributes": [ + { + "Section": "3 Networking", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "In order to prevent use of legacy networks, a project should not have a legacy network configured. As of now, Legacy Networks are gradually being phased out, and you can no longer create projects with them. This recommendation is to check older projects to ensure that they are not using Legacy Networks.", + "RationaleStatement": "Legacy networks have a single network IPv4 prefix range and a single gateway IP address for the whole network. The network is global in scope and spans all cloud regions. Subnetworks cannot be created in a legacy network and are unable to switch from legacy to auto or custom subnet networks. Legacy networks can have an impact for high network traffic projects and are subject to a single point of contention or failure.", + "ImpactStatement": "None.", + "RemediationProcedure": "**From Google Cloud CLI** For each Google Cloud Platform project, 1. Follow the documentation and create a non-legacy network suitable for the organization's requirements. 2. Follow the documentation and delete the networks in the `legacy` mode.", + "AuditProcedure": "**From Google Cloud CLI** For each Google Cloud Platform project, 1. Set the project name in the Google Cloud Shell: gcloud config set project 2. List the networks configured in that project: gcloud compute networks list None of the listed networks should be in the `legacy` mode.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/vpc/docs/using-legacy#creating_a_legacy_network:https://cloud.google.com/vpc/docs/using-legacy#deleting_a_legacy_network", + "DefaultValue": "By default, networks are not created in the `legacy` mode." + } + ] + }, + { + "Id": "3.3", + "Description": "Ensure That DNSSEC Is Enabled for Cloud DNS", + "Checks": [ + "dns_dnssec_disabled" + ], + "Attributes": [ + { + "Section": "3 Networking", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Cloud Domain Name System (DNS) is a fast, reliable and cost-effective domain name system that powers millions of domains on the internet. Domain Name System Security Extensions (DNSSEC) in Cloud DNS enables domain owners to take easy steps to protect their domains against DNS hijacking and man-in-the-middle and other attacks.", + "RationaleStatement": "Domain Name System Security Extensions (DNSSEC) adds security to the DNS protocol by enabling DNS responses to be validated. Having a trustworthy DNS that translates a domain name like www.example.com into its associated IP address is an increasingly important building block of today’s web-based applications. Attackers can hijack this process of domain/IP lookup and redirect users to a malicious site through DNS hijacking and man-in-the-middle attacks. DNSSEC helps mitigate the risk of such attacks by cryptographically signing DNS records. As a result, it prevents attackers from issuing fake DNS responses that may misdirect browsers to nefarious websites.", + "ImpactStatement": "", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `Cloud DNS` by visiting [https://console.cloud.google.com/net-services/dns/zones](https://console.cloud.google.com/net-services/dns/zones). 2. For each zone of `Type` `Public`, set `DNSSEC` to `On`. **From Google Cloud CLI** Use the below command to enable `DNSSEC` for Cloud DNS Zone Name. gcloud dns managed-zones update ZONE_NAME --dnssec-state on ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `Cloud DNS` by visiting [https://console.cloud.google.com/net-services/dns/zones](https://console.cloud.google.com/net-services/dns/zones). 2. For each zone of `Type` `Public`, ensure that `DNSSEC` is set to `On`. **From Google Cloud CLI** 1. List all the Managed Zones in a project: gcloud dns managed-zones list 2. For each zone of `VISIBILITY` `public`, get its metadata: gcloud dns managed-zones describe ZONE_NAME 3. Ensure that `dnssecConfig.state` property is `on`.", + "AdditionalInformation": "", + "References": "https://cloudplatform.googleblog.com/2017/11/DNSSEC-now-available-in-Cloud-DNS.html:https://cloud.google.com/dns/dnssec-config#enabling:https://cloud.google.com/dns/dnssec", + "DefaultValue": "By default DNSSEC is not enabled." + } + ] + }, + { + "Id": "3.4", + "Description": "Ensure That RSASHA1 Is Not Used for the Key-Signing Key in Cloud DNS DNSSEC", + "Checks": [ + "dns_rsasha1_in_use_to_key_sign_in_dnssec" + ], + "Attributes": [ + { + "Section": "3 Networking", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "NOTE: Currently, the SHA1 algorithm has been removed from general use by Google, and, if being used, needs to be whitelisted on a project basis by Google and will also, therefore, require a Google Cloud support contract. DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. The algorithm used for key signing should be a recommended one and it should be strong.", + "RationaleStatement": "Domain Name System Security Extensions (DNSSEC) algorithm numbers in this registry may be used in CERT RRs. Zonesigning (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. The algorithm used for key signing should be a recommended one and it should be strong. When enabling DNSSEC for a managed zone, or creating a managed zone with DNSSEC, the user can select the DNSSEC signing algorithms and the denial-of-existence type. Changing the DNSSEC settings is only effective for a managed zone if DNSSEC is not already enabled. If there is a need to change the settings for a managed zone where it has been enabled, turn DNSSEC off and then re-enable it with different settings.", + "ImpactStatement": "", + "RemediationProcedure": "**From Google Cloud CLI** 1. If it is necessary to change the settings for a managed zone where it has been enabled, DNSSEC must be turned off and re-enabled with different settings. To turn off DNSSEC, run the following command: gcloud dns managed-zones update ZONE_NAME --dnssec-state off 2. To update key-signing for a reported managed DNS Zone, run the following command: gcloud dns managed-zones update ZONE_NAME --dnssec-state on --ksk-algorithm KSK_ALGORITHM --ksk-key-length KSK_KEY_LENGTH --zsk-algorithm ZSK_ALGORITHM --zsk-key-length ZSK_KEY_LENGTH --denial-of-existence DENIAL_OF_EXISTENCE Supported algorithm options and key lengths are as follows. Algorithm KSK Length ZSK Length --------- ---------- ---------- RSASHA1 1024,2048 1024,2048 RSASHA256 1024,2048 1024,2048 RSASHA512 1024,2048 1024,2048 ECDSAP256SHA256 256 256 ECDSAP384SHA384 384 384", + "AuditProcedure": "**From Google Cloud CLI** Ensure the property algorithm for keyType keySigning is not using `RSASHA1`. gcloud dns managed-zones describe ZONENAME --format=json(dnsName,dnssecConfig.state,dnssecConfig.defaultKeySpecs)", + "AdditionalInformation": "1. RSASHA1 key-signing support may be required for compatibility reasons. 2. Remediation CLI works well with gcloud-cli version 221.0.0 and later.", + "References": "https://cloud.google.com/dns/dnssec-advanced#advanced_signing_options", + "DefaultValue": "" + } + ] + }, + { + "Id": "3.5", + "Description": "Ensure That RSASHA1 Is Not Used for the Zone-Signing Key in Cloud DNS DNSSEC", + "Checks": [ + "dns_rsasha1_in_use_to_zone_sign_in_dnssec" + ], + "Attributes": [ + { + "Section": "3 Networking", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "NOTE: Currently, the SHA1 algorithm has been removed from general use by Google, and, if being used, needs to be whitelisted on a project basis by Google and will also, therefore, require a Google Cloud support contract. DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. The algorithm used for key signing should be a recommended one and it should be strong.", + "RationaleStatement": "DNSSEC algorithm numbers in this registry may be used in CERT RRs. Zone signing (DNSSEC) and transaction security mechanisms (SIG(0) and TSIG) make use of particular subsets of these algorithms. The algorithm used for key signing should be a recommended one and it should be strong. When enabling DNSSEC for a managed zone, or creating a managed zone with DNSSEC, the DNSSEC signing algorithms and the denial-of-existence type can be selected. Changing the DNSSEC settings is only effective for a managed zone if DNSSEC is not already enabled. If the need exists to change the settings for a managed zone where it has been enabled, turn DNSSEC off and then re-enable it with different settings.", + "ImpactStatement": "", + "RemediationProcedure": "**From Google Cloud CLI** 1. If the need exists to change the settings for a managed zone where it has been enabled, DNSSEC must be turned off and then re-enabled with different settings. To turn off DNSSEC, run following command: gcloud dns managed-zones update ZONE_NAME --dnssec-state off 2. To update zone-signing for a reported managed DNS Zone, run the following command: gcloud dns managed-zones update ZONE_NAME --dnssec-state on --ksk-algorithm KSK_ALGORITHM --ksk-key-length KSK_KEY_LENGTH --zsk-algorithm ZSK_ALGORITHM --zsk-key-length ZSK_KEY_LENGTH --denial-of-existence DENIAL_OF_EXISTENCE Supported algorithm options and key lengths are as follows. Algorithm KSK Length ZSK Length --------- ---------- ---------- RSASHA1 1024,2048 1024,2048 RSASHA256 1024,2048 1024,2048 RSASHA512 1024,2048 1024,2048 ECDSAP256SHA256 256 384 ECDSAP384SHA384 384 384", + "AuditProcedure": "**From Google Cloud CLI** Ensure the property algorithm for keyType zone signing is not using RSASHA1. gcloud dns managed-zones describe --format=json(dnsName,dnssecConfig.state,dnssecConfig.defaultKeySpecs) ", + "AdditionalInformation": "1. RSASHA1 zone-signing support may be required for compatibility reasons. 2. The remediation CLI works well with gcloud-cli version 221.0.0 and later.", + "References": "https://cloud.google.com/dns/dnssec-advanced#advanced_signing_options", + "DefaultValue": "" + } + ] + }, + { + "Id": "3.6", + "Description": "Ensure That SSH Access Is Restricted From the Internet", + "Checks": [ + "compute_firewall_ssh_access_from_the_internet_allowed" + ], + "Attributes": [ + { + "Section": "3 Networking", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "GCP `Firewall Rules` are specific to a `VPC Network`. Each rule either `allows` or `denies` traffic when its conditions are met. Its conditions allow the user to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, only an `IPv4` address or `IPv4 block in CIDR` notation can be used. Generic `(0.0.0.0/0)` incoming traffic from the internet to VPC or VM instance using `SSH` on `Port 22` can be avoided.", + "RationaleStatement": "GCP `Firewall Rules` within a `VPC Network` apply to outgoing (egress) traffic from instances and incoming (ingress) traffic to instances in the network. Egress and ingress traffic flows are controlled even if the traffic stays within the network (for example, instance-to-instance communication). For an instance to have outgoing Internet access, the network must have a valid Internet gateway route or custom route whose destination IP is specified. This route simply defines the path to the Internet, to avoid the most general `(0.0.0.0/0)` destination `IP Range` specified from the Internet through `SSH` with the default `Port 22`. Generic access from the Internet to a specific IP Range needs to be restricted.", + "ImpactStatement": "All Secure Shell (SSH) connections from outside of the network to the concerned VPC(s) will be blocked. There could be a business need where SSH access is required from outside of the network to access resources associated with the VPC. In that case, specific source IP(s) should be mentioned in firewall rules to white-list access to SSH port for the concerned VPC(s).", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `VPC Network`. 2. Go to the `Firewall Rules`. 3. Click the `Firewall Rule` you want to modify. 4. Click `Edit`. 5. Modify `Source IP ranges` to specific `IP`. 6. Click `Save`. **From Google Cloud CLI** 1.Update the Firewall rule with the new `SOURCE_RANGE` from the below command: gcloud compute firewall-rules update FirewallName --allow=[PROTOCOL[:PORT[-PORT]],...] --source-ranges=[CIDR_RANGE,...]", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `VPC network`. 2. Go to the `Firewall Rules`. 3. Ensure that `Port` is not equal to `22` and `Action` is not set to `Allow`. 4. Ensure `IP Ranges` is not equal to `0.0.0.0/0` under `Source filters`. **From Google Cloud CLI** gcloud compute firewall-rules list --format=table'(name,direction,sourceRanges,allowed)' Ensure that there is no rule matching the below criteria: - `SOURCE_RANGES` is `0.0.0.0/0` - AND `DIRECTION` is `INGRESS` - AND IPProtocol is `tcp` or `ALL` - AND `PORTS` is set to `22` or `range containing 22` or `Null (not set)` Note: - When ALL TCP ports are allowed in a rule, PORT does not have any value set (`NULL`) - When ALL Protocols are allowed in a rule, PORT does not have any value set (`NULL`)", + "AdditionalInformation": "Currently, GCP VPC only supports IPV4; however, Google is already working on adding IPV6 support for VPC. In that case along with source IP range `0.0.0.0`, the rule should be checked for IPv6 equivalent `::/0` as well.", + "References": "https://cloud.google.com/vpc/docs/firewalls#blockedtraffic:https://cloud.google.com/blog/products/identity-security/cloud-iap-enables-context-aware-access-to-vms-via-ssh-and-rdp-without-bastion-hosts", + "DefaultValue": "" + } + ] + }, + { + "Id": "3.7", + "Description": "Ensure That RDP Access Is Restricted From the Internet", + "Checks": [ + "compute_firewall_rdp_access_from_the_internet_allowed" + ], + "Attributes": [ + { + "Section": "3 Networking", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "GCP `Firewall Rules` are specific to a `VPC Network`. Each rule either `allows` or `denies` traffic when its conditions are met. Its conditions allow users to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, an `IPv4` address or `IPv4 block in CIDR` notation can be used. Generic `(0.0.0.0/0)` incoming traffic from the Internet to a VPC or VM instance using `RDP` on `Port 3389` can be avoided.", + "RationaleStatement": "GCP `Firewall Rules` within a `VPC Network`. These rules apply to outgoing (egress) traffic from instances and incoming (ingress) traffic to instances in the network. Egress and ingress traffic flows are controlled even if the traffic stays within the network (for example, instance-to-instance communication). For an instance to have outgoing Internet access, the network must have a valid Internet gateway route or custom route whose destination IP is specified. This route simply defines the path to the Internet, to avoid the most general `(0.0.0.0/0)` destination `IP Range` specified from the Internet through `RDP` with the default `Port 3389`. Generic access from the Internet to a specific IP Range should be restricted.", + "ImpactStatement": "All Remote Desktop Protocol (RDP) connections from outside of the network to the concerned VPC(s) will be blocked. There could be a business need where secure shell access is required from outside of the network to access resources associated with the VPC. In that case, specific source IP(s) should be mentioned in firewall rules to white-list access to RDP port for the concerned VPC(s).", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `VPC Network`. 2. Go to the `Firewall Rules`. 3. Click the `Firewall Rule` to be modified. 4. Click `Edit`. 5. Modify `Source IP ranges` to specific `IP`. 6. Click `Save`. **From Google Cloud CLI** 1.Update RDP Firewall rule with new `SOURCE_RANGE` from the below command: gcloud compute firewall-rules update FirewallName --allow=[PROTOCOL[:PORT[-PORT]],...] --source-ranges=[CIDR_RANGE,...]", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `VPC network`. 2. Go to the `Firewall Rules`. 3. Ensure `Port` is not equal to `3389` and `Action` is not `Allow`. 4. Ensure `IP Ranges` is not equal to `0.0.0.0/0` under `Source filters`. **From Google Cloud CLI** gcloud compute firewall-rules list --format=table'(name,direction,sourceRanges,allowed)' Ensure that there is no rule matching the below criteria: - `SOURCE_RANGES` is `0.0.0.0/0` - AND `DIRECTION` is `INGRESS` - AND IPProtocol is `TCP` or `ALL` - AND `PORTS` is set to `3389` or `range containing 3389` or `Null (not set)` Note: - When ALL TCP ports are allowed in a rule, PORT does not have any value set (`NULL`) - When ALL Protocols are allowed in a rule, PORT does not have any value set (`NULL`)", + "AdditionalInformation": "Currently, GCP VPC only supports IPV4; however, Google is already working on adding IPV6 support for VPC. In that case along with source IP range `0.0.0.0`, the rule should be checked for IPv6 equivalent `::/0` as well.", + "References": "https://cloud.google.com/vpc/docs/firewalls#blockedtraffic:https://cloud.google.com/blog/products/identity-security/cloud-iap-enables-context-aware-access-to-vms-via-ssh-and-rdp-without-bastion-hosts", + "DefaultValue": "" + } + ] + }, + { + "Id": "3.8", + "Description": "Ensure that VPC Flow Logs is Enabled for Every Subnet in a VPC Network", + "Checks": [ + "compute_subnet_flow_logs_enabled" + ], + "Attributes": [ + { + "Section": "3 Networking", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "Flow Logs is a feature that enables users to capture information about the IP traffic going to and from network interfaces in the organization's VPC Subnets. Once a flow log is created, the user can view and retrieve its data in Stackdriver Logging. It is recommended that Flow Logs be enabled for every business-critical VPC subnet.", + "RationaleStatement": "VPC networks and subnetworks not reserved for internal HTTP(S) load balancing provide logically isolated and secure network partitions where GCP resources can be launched. When Flow Logs are enabled for a subnet, VMs within that subnet start reporting on all Transmission Control Protocol (TCP) and User Datagram Protocol (UDP) flows. Each VM samples the TCP and UDP flows it sees, inbound and outbound, whether the flow is to or from another VM, a host in the on-premises datacenter, a Google service, or a host on the Internet. If two GCP VMs are communicating, and both are in subnets that have VPC Flow Logs enabled, both VMs report the flows. Flow Logs supports the following use cases: - Network monitoring - Understanding network usage and optimizing network traffic expenses - Network forensics - Real-time security analysis Flow Logs provide visibility into network traffic for each VM inside the subnet and can be used to detect anomalous traffic or provide insight during security workflows. The Flow Logs must be configured such that all network traffic is logged, the interval of logging is granular to provide detailed information on the connections, no logs are filtered, and metadata to facilitate investigations are included. **Note**: Subnets reserved for use by internal HTTP(S) load balancers do not support VPC flow logs.", + "ImpactStatement": "Standard pricing for Stackdriver Logging, BigQuery, or Cloud Pub/Sub applies. VPC Flow Logs generation will be charged starting in GA as described in reference: https://cloud.google.com/vpc/", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the VPC network GCP Console visiting `https://console.cloud.google.com/networking/networks/list` 2. Click the name of a subnet, The `Subnet details` page displays. 3. Click the `EDIT` button. 4. Set `Flow Logs` to `On`. 5. Expand the `Configure Logs` section. 6. Set `Aggregation Interval` to `5 SEC`. 7. Check the box beside `Include metadata`. 8. Set `Sample rate` to `100`. 9. Click Save. **Note**: It is not possible to configure a Log filter from the console. **From Google Cloud CLI** To enable VPC Flow Logs for a network subnet, run the following command: gcloud compute networks subnets update [SUBNET_NAME] --region [REGION] --enable-flow-logs --logging-aggregation-interval=interval-5-sec --logging-flow-sampling=1 --logging-metadata=include-all ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the VPC network GCP Console visiting `https://console.cloud.google.com/networking/networks/list` 2. From the list of network subnets, make sure for each subnet: - `Flow Logs` is set to `On` - `Aggregation Interval` is set to `5 sec` - `Include metadata` checkbox is checked - `Sample rate` is set to `100%` **Note**: It is not possible to determine if a Log filter has been defined from the console. **From Google Cloud CLI** gcloud compute networks subnets list --format json | jq -r '([Subnet,Purpose,Flow_Logs,Aggregation_Interval,Flow_Sampling,Metadata,Logs_Filtered] | (., map(length*-))), (.[] | [ .name, .purpose, (if has(enableFlowLogs) and .enableFlowLogs == true then Enabled else Disabled end), (if has(logConfig) then .logConfig.aggregationInterval else N/A end), (if has(logConfig) then .logConfig.flowSampling else N/A end), (if has(logConfig) then .logConfig.metadata else N/A end), (if has(logConfig) then (.logConfig | has(filterExpr)) else N/A end) ] ) | @tsv' | column -t The output of the above command will list: - each subnet - the subnet's purpose - a `Enabled` or `Disabled` value if `Flow Logs` are enabled - the value for `Aggregation Interval` or `N/A` if disabled, the value for `Flow Sampling` or `N/A` if disabled - the value for `Metadata` or `N/A` if disabled - 'true' or 'false' if a Logging Filter is configured or 'N/A' if disabled. If the subnet's purpose is `PRIVATE` then `Flow Logs` should be `Enabled`. If `Flow Logs` is enabled then: - `Aggregation_Interval` should be `INTERVAL_5_SEC` - `Flow_Sampling` should be 1 - `Metadata` should be `INCLUDE_ALL_METADATA` - `Logs_Filtered` should be `false`.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/vpc/docs/using-flow-logs#enabling_vpc_flow_logging:https://cloud.google.com/vpc/", + "DefaultValue": "By default, Flow Logs is set to Off when a new VPC network subnet is created." + } + ] + }, + { + "Id": "3.9", + "Description": "Ensure No HTTPS or SSL Proxy Load Balancers Permit SSL Policies With Weak Cipher Suites", + "Checks": [], + "Attributes": [ + { + "Section": "3 Networking", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Secure Sockets Layer (SSL) policies determine what port Transport Layer Security (TLS) features clients are permitted to use when connecting to load balancers. To prevent usage of insecure features, SSL policies should use (a) at least TLS 1.2 with the MODERN profile; or (b) the RESTRICTED profile, because it effectively requires clients to use TLS 1.2 regardless of the chosen minimum TLS version; or (3) a CUSTOM profile that does not support any of the following features: TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_256_GCM_SHA384 TLS_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_256_CBC_SHA TLS_RSA_WITH_3DES_EDE_CBC_SHA ", + "RationaleStatement": "Load balancers are used to efficiently distribute traffic across multiple servers. Both SSL proxy and HTTPS load balancers are external load balancers, meaning they distribute traffic from the Internet to a GCP network. GCP customers can configure load balancer SSL policies with a minimum TLS version (1.0, 1.1, or 1.2) that clients can use to establish a connection, along with a profile (Compatible, Modern, Restricted, or Custom) that specifies permissible cipher suites. To comply with users using outdated protocols, GCP load balancers can be configured to permit insecure cipher suites. In fact, the GCP default SSL policy uses a minimum TLS version of 1.0 and a Compatible profile, which allows the widest range of insecure cipher suites. As a result, it is easy for customers to configure a load balancer without even knowing that they are permitting outdated cipher suites.", + "ImpactStatement": "Creating more secure SSL policies can prevent clients using older TLS versions from establishing a connection.", + "RemediationProcedure": "**From Google Cloud Console** If the TargetSSLProxy or TargetHttpsProxy does not have an SSL policy configured, create a new SSL policy. Otherwise, modify the existing insecure policy. 1. Navigate to the `SSL Policies` page by visiting: [https://console.cloud.google.com/net-security/sslpolicies](https://console.cloud.google.com/net-security/sslpolicies) 2. Click on the name of the insecure policy to go to its `SSL policy details` page. 3. Click `EDIT`. 4. Set `Minimum TLS version` to `TLS 1.2`. 5. Set `Profile` to `Modern` or `Restricted`. 6. Alternatively, if teh user selects the profile `Custom`, make sure that the following features are disabled: TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_256_GCM_SHA384 TLS_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_256_CBC_SHA TLS_RSA_WITH_3DES_EDE_CBC_SHA **From Google Cloud CLI** 1. For each insecure SSL policy, update it to use secure cyphers: gcloud compute ssl-policies update NAME [--profile COMPATIBLE|MODERN|RESTRICTED|CUSTOM] --min-tls-version 1.2 [--custom-features FEATURES] 2. If the target proxy has a GCP default SSL policy, use the following command corresponding to the proxy type to update it. gcloud compute target-ssl-proxies update TARGET_SSL_PROXY_NAME --ssl-policy SSL_POLICY_NAME gcloud compute target-https-proxies update TARGET_HTTPS_POLICY_NAME --ssl-policy SSL_POLICY_NAME ", + "AuditProcedure": "**From Google Cloud Console** 1. See all load balancers by visiting [https://console.cloud.google.com/net-services/loadbalancing/loadBalancers/list](https://console.cloud.google.com/net-services/loadbalancing/loadBalancers/list). 2. For each load balancer for `SSL (Proxy)` or `HTTPS`, click on its name to go the `Load balancer details` page. 3. Ensure that each target proxy entry in the `Frontend` table has an `SSL Policy` configured. 4. Click on each SSL policy to go to its `SSL policy details` page. 5. Ensure that the SSL policy satisfies one of the following conditions: - has a `Min TLS` set to `TLS 1.2` and `Profile` set to `Modern` profile, or - has `Profile` set to `Restricted`. Note that a Restricted profile effectively requires clients to use TLS 1.2 regardless of the chosen minimum TLS version, or - has `Profile` set to `Custom` and the following features are all disabled: TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_256_GCM_SHA384 TLS_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_256_CBC_SHA TLS_RSA_WITH_3DES_EDE_CBC_SHA **From Google Cloud CLI** 1. List all TargetHttpsProxies and TargetSslProxies. gcloud compute target-https-proxies list gcloud compute target-ssl-proxies list 2. For each target proxy, list its properties: gcloud compute target-https-proxies describe TARGET_HTTPS_PROXY_NAME gcloud compute target-ssl-proxies describe TARGET_SSL_PROXY_NAME 3. Ensure that the `sslPolicy` field is present and identifies the name of the SSL policy: sslPolicy: https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/sslPolicies/SSL_POLICY_NAME If the `sslPolicy` field is missing from the configuration, it means that the GCP default policy is used, which is insecure. 4. Describe the SSL policy: gcloud compute ssl-policies describe SSL_POLICY_NAME 5. Ensure that the policy satisfies one of the following conditions: - has `Profile` set to `Modern` and `minTlsVersion` set to `TLS_1_2`, or - has `Profile` set to `Restricted`, or - has `Profile` set to `Custom` and `enabledFeatures` does not contain any of the following values: TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_256_GCM_SHA384 TLS_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_256_CBC_SHA TLS_RSA_WITH_3DES_EDE_CBC_SHA ", + "AdditionalInformation": "", + "References": "https://cloud.google.com/load-balancing/docs/use-ssl-policies:https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-52r2.pdf", + "DefaultValue": "The GCP default SSL policy is the least secure setting: Min TLS 1.0 and Compatible profile" + } + ] + }, + { + "Id": "3.1", + "Description": "Use Identity Aware Proxy (IAP) to Ensure Only Traffic From Google IP Addresses are 'Allowed'", + "Checks": [], + "Attributes": [ + { + "Section": "3 Networking", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "IAP authenticates the user requests to your apps via a Google single sign in. You can then manage these users with permissions to control access. It is recommended to use both IAP permissions and firewalls to restrict this access to your apps with sensitive information.", + "RationaleStatement": "IAP ensure that access to VMs is controlled by authenticating incoming requests. Access to your apps and the VMs should be restricted by firewall rules that allow only the proxy IAP IP addresses contained in the 35.235.240.0/20 subnet. Otherwise, unauthenticated requests can be made to your apps. To ensure that load balancing works correctly health checks should also be allowed.", + "ImpactStatement": "If firewall rules are not configured correctly, legitimate business services could be negatively impacted. It is recommended to make these changes during a time of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud Console [VPC network > Firewall rules](https://console.cloud.google.com/networking/firewalls/list?_ga=2.72166934.480049361.1580860862-1336643914.1580248695). 2. Select the checkbox next to the following rules: - default-allow-http - default-allow-https - default-allow-internal 3. Click `Delete`. 4. Click `Create firewall rule` and set the following values: - Name: allow-iap-traffic - Targets: All instances in the network - Source IP ranges (press Enter after you paste each value in the box, copy each full CIDR IP address): - IAP Proxy Addresses `35.235.240.0/20` - Google Health Check `130.211.0.0/22` - Google Health Check `35.191.0.0/16` - Protocols and ports: - Specified protocols and ports required for access and management of your app. For example most health check connection protocols would be covered by; - tcp:80 (Default HTTP Health Check port) - tcp:443 (Default HTTPS Health Check port) **Note: if you have custom ports used by your load balancers, you will need to list them here** 5. When you're finished updating values, click `Create`.", + "AuditProcedure": "**From Google Cloud Console** 1. For each of your apps that have IAP enabled go to the Cloud Console VPC network > Firewall rules. 2. Verify that the only rules correspond to the following values: - Targets: All instances in the network - Source IP ranges: - IAP Proxy Addresses `35.235.240.0/20` - Google Health Check `130.211.0.0/22` - Google Health Check `35.191.0.0/16` - Protocols and ports: - Specified protocols and ports required for access and management of your app. For example most health check connection protocols would be covered by; - tcp:80 (Default HTTP Health Check port) - tcp:443 (Default HTTPS Health Check port) **Note: if you have custom ports used by your load balancers, you will need to list them here**", + "AdditionalInformation": "", + "References": "https://cloud.google.com/iap/docs/concepts-overview:https://cloud.google.com/iap/docs/load-balancer-howto:https://cloud.google.com/load-balancing/docs/health-checks:https://cloud.google.com/blog/products/identity-security/cloud-iap-enables-context-aware-access-to-vms-via-ssh-and-rdp-without-bastion-hosts", + "DefaultValue": "By default all traffic is allowed." + } + ] + }, + { + "Id": "4.1", + "Description": "Ensure That Instances Are Not Configured To Use the Default Service Account", + "Checks": [ + "compute_instance_default_service_account_in_use" + ], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to configure your instance to not use the default Compute Engine service account because it has the Editor role on the project.", + "RationaleStatement": "When a default Compute Engine service account is created, it is automatically granted the Editor role (roles/editor) on your project which allows read and write access to most Google Cloud Services. This role includes a very large number of permissions. To defend against privilege escalations if your VM is compromised and prevent an attacker from gaining access to all of your project, you should either revoke the Editor role from the default Compute Engine service account or create a new service account and assign only the permissions needed by your instance. To mitigate this at scale, we strongly recommend that you disable the automatic role grant by adding a constraint to your organization policy. The default Compute Engine service account is named `[PROJECT_NUMBER]-compute@developer.gserviceaccount.com`.", + "ImpactStatement": "", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. Click on the instance name to go to its `VM instance details` page. 3. Click `STOP` and then click `EDIT`. 4. Under the section `API and identity management`, select a service account other than the default Compute Engine service account. You may first need to create a new service account. 5. Click `Save` and then click `START`. **From Google Cloud CLI** 1. Stop the instance: gcloud compute instances stop 2. Update the instance: gcloud compute instances set-service-account --service-account= 3. Restart the instance: gcloud compute instances start ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. Click on each instance name to go to its `VM instance details` page. 3. Under the section `API and identity management`, ensure that the default Compute Engine service account is not used. This account is named `[PROJECT_NUMBER]-compute@developer.gserviceaccount.com`. **From Google Cloud CLI** 1. List the instances in your project and get details on each instance: gcloud compute instances list --format=json | jq -r '. | SA: (.[].serviceAccounts[].email) Name: (.[].name)' 2. Ensure that the service account section has an email that does not match the pattern `[PROJECT_NUMBER]-compute@developer.gserviceaccount.com`. **Exception:** VMs created by GKE should be excluded. These VMs have names that start with `gke-` and are labeled `goog-gke-node`.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/compute/docs/access/service-accounts:https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances:https://cloud.google.com/sdk/gcloud/reference/compute/instances/set-service-account", + "DefaultValue": "By default, Compute instances are configured to use the default Compute Engine service account." + } + ] + }, + { + "Id": "4.2", + "Description": "Ensure That Instances Are Not Configured To Use the Default Service Account With Full Access to All Cloud APIs", + "Checks": [ + "compute_instance_default_service_account_in_use_with_full_api_access" + ], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "To support principle of least privileges and prevent potential privilege escalation it is recommended that instances are not assigned to default service account `Compute Engine default service account` with Scope `Allow full access to all Cloud APIs`.", + "RationaleStatement": "Along with ability to optionally create, manage and use user managed custom service accounts, Google Compute Engine provides default service account `Compute Engine default service account` for an instances to access necessary cloud services. `Project Editor` role is assigned to `Compute Engine default service account` hence, This service account has almost all capabilities over all cloud services except billing. However, when `Compute Engine default service account` assigned to an instance it can operate in 3 scopes. 1. Allow default access: Allows only minimum access required to run an Instance (Least Privileges) 2. Allow full access to all Cloud APIs: Allow full access to all the cloud APIs/Services (Too much access) 3. Set access for each API: Allows Instance administrator to choose only those APIs that are needed to perform specific business functionality expected by instance When an instance is configured with `Compute Engine default service account` with Scope `Allow full access to all Cloud APIs`, based on IAM roles assigned to the user(s) accessing Instance, it may allow user to perform cloud operations/API calls that user is not supposed to perform leading to successful privilege escalation.", + "ImpactStatement": "In order to change service account or scope for an instance, it needs to be stopped.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. Click on the impacted VM instance. 3. If the instance is not stopped, click the `Stop` button. Wait for the instance to be stopped. 4. Next, click the `Edit` button. 5. Scroll down to the `Service Account` section. 6. Select a different service account or ensure that `Allow full access to all Cloud APIs` is not selected. 7. Click the `Save` button to save your changes and then click `START`. **From Google Cloud CLI** 1. Stop the instance: gcloud compute instances stop 2. Update the instance: gcloud compute instances set-service-account --service-account= --scopes [SCOPE1, SCOPE2...] 3. Restart the instance: gcloud compute instances start ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. Click on each instance name to go to its `VM instance details` page. 3. Under the `API and identity management`, ensure that `Cloud API access scopes` is not set to `Allow full access to all Cloud APIs`. **From Google Cloud CLI** 1. List the instances in your project and get details on each instance: gcloud compute instances list --format=json | jq -r '. | SA Scopes: (.[].serviceAccounts[].scopes) Name: (.[].name) Email: (.[].serviceAccounts[].email)' 2. Ensure that the service account section has an email that does not match the pattern `[PROJECT_NUMBER]-compute@developer.gserviceaccount.com`. **Exception:** VMs created by GKE should be excluded. These VMs have names that start with `gke-` and are labeled `goog-gke-node", + "AdditionalInformation": "- User IAM roles will override service account scope but configuring minimal scope ensures defense in depth - Non-default service accounts do not offer selection of access scopes like default service account. IAM roles with non-default service accounts should be used to control VM access.", + "References": "https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances:https://cloud.google.com/compute/docs/access/service-accounts", + "DefaultValue": "While creating an VM instance, default service account is used with scope `Allow default access`." + } + ] + }, + { + "Id": "4.3", + "Description": "Ensure “Block Project-Wide SSH Keys” Is Enabled for VM Instances", + "Checks": [ + "compute_instance_block_project_wide_ssh_keys_disabled" + ], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to use Instance specific SSH key(s) instead of using common/shared project-wide SSH key(s) to access Instances.", + "RationaleStatement": "Project-wide SSH keys are stored in Compute/Project-meta-data. Project wide SSH keys can be used to login into all the instances within project. Using project-wide SSH keys eases the SSH key management but if compromised, poses the security risk which can impact all the instances within project. It is recommended to use Instance specific SSH keys which can limit the attack surface if the SSH keys are compromised.", + "ImpactStatement": "Users already having Project-wide ssh key pairs and using third party SSH clients will lose access to the impacted Instances. For Project users using gcloud or GCP Console based SSH option, no manual key creation and distribution is required and will be handled by GCE (Google Compute Engine) itself. To access Instance using third party SSH clients Instance specific SSH key pairs need to be created and distributed to the required users.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). It will list all the instances in your project. 2. Click on the name of the Impacted instance 3. Click `Edit` in the toolbar 4. Under SSH Keys, go to the `Block project-wide SSH keys` checkbox 5. To block users with project-wide SSH keys from connecting to this instance, select `Block project-wide SSH keys` 6. Click `Save` at the bottom of the page 7. Repeat steps for every impacted Instance **From Google Cloud CLI** To block project-wide public SSH keys, set the metadata value to `TRUE`: gcloud compute instances add-metadata --metadata block-project-ssh-keys=TRUE ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the `VM instances` page by visiting [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). It will list all the instances in your project. 2. For every instance, click on the name of the instance. 3. Under `SSH Keys`, ensure `Block project-wide SSH keys` is selected. **From Google Cloud CLI** 1. List the instances in your project and get details on each instance: gcloud compute instances list --format=json 2. Ensure `key: block-project-ssh-keys` is set to `value: 'true'`.", + "AdditionalInformation": "If OS Login is enabled, SSH keys in instance metadata are ignored, and therefore blocking project-wide SSH keys is not necessary.", + "References": "https://cloud.google.com/compute/docs/instances/adding-removing-ssh-keys:https://cloud.google.com/sdk/gcloud/reference/topic/formats", + "DefaultValue": "By Default `Block Project-wide SSH keys` is not enabled." + } + ] + }, + { + "Id": "4.4", + "Description": "Ensure Oslogin Is Enabled for a Project", + "Checks": [ + "compute_project_os_login_enabled" + ], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Enabling OS login binds SSH certificates to IAM users and facilitates effective SSH certificate management.", + "RationaleStatement": "Enabling osLogin ensures that SSH keys used to connect to instances are mapped with IAM users. Revoking access to IAM user will revoke all the SSH keys associated with that particular user. It facilitates centralized and automated SSH key pair management which is useful in handling cases like response to compromised SSH key pairs and/or revocation of external/third-party/Vendor users.", + "ImpactStatement": "Enabling OS Login on project disables metadata-based SSH key configurations on all instances from a project. Disabling OS Login restores SSH keys that you have configured in project or instance meta-data.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the VM compute metadata page by visiting: [https://console.cloud.google.com/compute/metadata](https://console.cloud.google.com/compute/metadata). 2. Click `Edit`. 3. Add a metadata entry where the key is `enable-oslogin` and the value is `TRUE`. 4. Click `Save` to apply the changes. 5. For every instance that overrides the project setting, go to the `VM Instances` page at [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 6. Click the name of the instance on which you want to remove the metadata value. 7. At the top of the instance details page, click `Edit` to edit the instance settings. 8. Under `Custom metadata`, remove any entry with key `enable-oslogin` and the value is `FALSE` 9. At the bottom of the instance details page, click `Save` to apply your changes to the instance. **From Google Cloud CLI** 1. Configure oslogin on the project: gcloud compute project-info add-metadata --metadata enable-oslogin=TRUE 2. Remove instance metadata that overrides the project setting. gcloud compute instances remove-metadata --keys=enable-oslogin Optionally, you can enable two factor authentication for OS login. For more information, see: [https://cloud.google.com/compute/docs/oslogin/setup-two-factor-authentication](https://cloud.google.com/compute/docs/oslogin/setup-two-factor-authentication).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the VM compute metadata page by visiting [https://console.cloud.google.com/compute/metadata](https://console.cloud.google.com/compute/metadata). 2. Ensure that key `enable-oslogin` is present with value set to `TRUE`. 3. Because instances can override project settings, ensure that no instance has custom metadata with key `enable-oslogin` and value `FALSE`. **From Google Cloud CLI** 1. List the instances in your project and get details on each instance: gcloud compute instances list --format=json 2. Verify that the section `commonInstanceMetadata` has a key `enable-oslogin` set to value `TRUE`. **Exception:** VMs created by GKE should be excluded. These VMs have names that start with `gke-` and are labeled `goog-gke-node`", + "AdditionalInformation": "1. In order to use osLogin, instance using Custom Images must have the latest version of the Linux Guest Environment installed. The following image families do not yet support OS Login: Project cos-cloud (Container-Optimized OS) image family cos-stable. All project coreos-cloud (CoreOS) image families Project suse-cloud (SLES) image family sles-11 All Windows Server and SQL Server image families 2. Project enable-oslogin can be over-ridden by setting enable-oslogin parameter to an instance metadata individually.", + "References": "https://cloud.google.com/compute/docs/instances/managing-instance-access:https://cloud.google.com/compute/docs/instances/managing-instance-access#enable_oslogin:https://cloud.google.com/sdk/gcloud/reference/compute/instances/remove-metadata:https://cloud.google.com/compute/docs/oslogin/setup-two-factor-authentication", + "DefaultValue": "By default, parameter `enable-oslogin` is not set, which is equivalent to setting it to `FALSE`." + } + ] + }, + { + "Id": "4.5", + "Description": "Ensure ‘Enable Connecting to Serial Ports’ Is Not Enabled for VM Instance", + "Checks": [ + "compute_instance_serial_ports_in_use" + ], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Interacting with a serial port is often referred to as the serial console, which is similar to using a terminal window, in that input and output is entirely in text mode and there is no graphical interface or mouse support. If you enable the interactive serial console on an instance, clients can attempt to connect to that instance from any IP address. Therefore interactive serial console support should be disabled.", + "RationaleStatement": "A virtual machine instance has four virtual serial ports. Interacting with a serial port is similar to using a terminal window, in that input and output is entirely in text mode and there is no graphical interface or mouse support. The instance's operating system, BIOS, and other system-level entities often write output to the serial ports, and can accept input such as commands or answers to prompts. Typically, these system-level entities use the first serial port (port 1) and serial port 1 is often referred to as the serial console. The interactive serial console does not support IP-based access restrictions such as IP whitelists. If you enable the interactive serial console on an instance, clients can attempt to connect to that instance from any IP address. This allows anybody to connect to that instance if they know the correct SSH key, username, project ID, zone, and instance name. Therefore interactive serial console support should be disabled.", + "ImpactStatement": "", + "RemediationProcedure": "**From Google Cloud Console** 1. Login to Google Cloud console 2. Go to Computer Engine 3. Go to VM instances 4. Click on the Specific VM 5. Click `EDIT` 6. Unselect `Enable connecting to serial ports` below `Remote access` block. 7. Click `Save` **From Google Cloud CLI** Use the below command to disable gcloud compute instances add-metadata --zone= --metadata=serial-port-enable=false or gcloud compute instances add-metadata --zone= --metadata=serial-port-enable=0 **Prevention:** You can prevent VMs from having serial port access enable by `Disable VM serial port access` organization policy: [https://console.cloud.google.com/iam-admin/orgpolicies/compute-disableSerialPortAccess](https://console.cloud.google.com/iam-admin/orgpolicies/compute-disableSerialPortAccess).", + "AuditProcedure": "**From Google Cloud Console** 1. Login to Google Cloud console 2. Go to Compute Engine 3. Go to VM instances 4. Click on the Specific VM 5. Ensure the statement `Connecting to serial serial ports is disabled` is displayed at the top of the details tab, just below the `Connect to serial console` drop-down.. **From Google Cloud CLI** Ensure the below command's output shows `null`: gcloud compute instances describe --zone= --format=json(metadata.items[].key,metadata.items[].value) or `key` and `value` properties from below command's json response are equal to `serial-port-enable` and `0` or `false` respectively. { metadata: { items: [ { key: serial-port-enable, value: 0 } ] } } ", + "AdditionalInformation": "", + "References": "https://cloud.google.com/compute/docs/instances/interacting-with-serial-console", + "DefaultValue": "By default, connecting to serial ports is not enabled." + } + ] + }, + { + "Id": "4.6", + "Description": "Ensure That IP Forwarding Is Not Enabled on Instances", + "Checks": [ + "compute_instance_ip_forwarding_is_enabled" + ], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Compute Engine instance cannot forward a packet unless the source IP address of the packet matches the IP address of the instance. Similarly, GCP won't deliver a packet whose destination IP address is different than the IP address of the instance receiving the packet. However, both capabilities are required if you want to use instances to help route packets. Forwarding of data packets should be disabled to prevent data loss or information disclosure.", + "RationaleStatement": "Compute Engine instance cannot forward a packet unless the source IP address of the packet matches the IP address of the instance. Similarly, GCP won't deliver a packet whose destination IP address is different than the IP address of the instance receiving the packet. However, both capabilities are required if you want to use instances to help route packets. To enable this source and destination IP check, disable the `canIpForward` field, which allows an instance to send and receive packets with non-matching destination or source IPs.", + "ImpactStatement": "", + "RemediationProcedure": "You only edit the `canIpForward` setting at instance creation or using CLI. **From Google Cloud CLI** 1. Use the instances export command to export the existing instance properties: gcloud compute instances export --project --zone --destination= **Note**Replace the following: INSTANCE_NAME the name for the instance that you want to export. PROJECT_ID: the project ID for this request. ZONE: the zone for this instance. FILE_PATH: the output path where you want to save the instance configuration file on your local workstation. 2. Use a text editor to modify this file Replace `canIpForward: true` with `canIpForward: false` 3. Run this command to import the file you just modified gcloud compute instances update-from-file INSTANCE_NAME --project PROJECT_ID --zone ZONE --source=FILE_PATH --most-disruptive-allowed-action=REFRESH If the update request is valid and the required resources are available, the instance update process begins. You can monitor the status of this operation by viewing the audit logs. This update requires only a REFRESH not a full restart.", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the `VM Instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. For every instance, click on its name to go to the `VM instance details` page. 3. Under the `Network interfaces` section, ensure that `IP forwarding` is set to `Off` for every network interface. **From Google Cloud CLI** 1. List all instances: gcloud compute instances list --format='table(name,canIpForward)' 2. Ensure that `CAN_IP_FORWARD` column in the output of above command does not contain `True` for any VM instance. **Exception:** Instances created by GKE should be excluded because they need to have IP forwarding enabled and cannot be changed. Instances created by GKE have names that start with gke-.", + "AdditionalInformation": "You can only set the `canIpForward` field at instance creation time or using CLI.", + "References": "https://cloud.google.com/vpc/docs/using-routes#canipforward:https://cloud.google.com/compute/docs/instances/update-instance-properties", + "DefaultValue": "By default, instances are not configured to allow IP forwarding." + } + ] + }, + { + "Id": "4.7", + "Description": "Ensure VM Disks for Critical VMs Are Encrypted With Customer-Supplied Encryption Keys (CSEK)", + "Checks": [ + "compute_instance_encryption_with_csek_enabled" + ], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "Customer-Supplied Encryption Keys (CSEK) are a feature in Google Cloud Storage and Google Compute Engine. If you supply your own encryption keys, Google uses your key to protect the Google-generated keys used to encrypt and decrypt your data. By default, Google Compute Engine encrypts all data at rest. Compute Engine handles and manages this encryption for you without any additional actions on your part. However, if you wanted to control and manage this encryption yourself, you can provide your own encryption keys.", + "RationaleStatement": "By default, Google Compute Engine encrypts all data at rest. Compute Engine handles and manages this encryption for you without any additional actions on your part. However, if you wanted to control and manage this encryption yourself, you can provide your own encryption keys. If you provide your own encryption keys, Compute Engine uses your key to protect the Google-generated keys used to encrypt and decrypt your data. Only users who can provide the correct key can use resources protected by a customer-supplied encryption key. Google does not store your keys on its servers and cannot access your protected data unless you provide the key. This also means that if you forget or lose your key, there is no way for Google to recover the key or to recover any data encrypted with the lost key. At least business critical VMs should have VM disks encrypted with CSEK.", + "ImpactStatement": "If you lose your encryption key, you will not be able to recover the data.", + "RemediationProcedure": "Currently there is no way to update the encryption of an existing disk. Therefore you should create a new disk with `Encryption` set to `Customer supplied`. **From Google Cloud Console** 1. Go to Compute Engine `Disks` by visiting: [https://console.cloud.google.com/compute/disks](https://console.cloud.google.com/compute/disks). 2. Click `CREATE DISK`. 3. Set `Encryption type` to `Customer supplied`, 4. Provide the `Key` in the box. 5. Select `Wrapped key`. 6. Click `Create`. **From Google Cloud CLI** In the gcloud compute tool, encrypt a disk using the --csek-key-file flag during instance creation. If you are using an RSA-wrapped key, use the gcloud beta component: gcloud compute instances create --csek-key-file To encrypt a standalone persistent disk: gcloud compute disks create --csek-key-file ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to Compute Engine `Disks` by visiting: [https://console.cloud.google.com/compute/disks](https://console.cloud.google.com/compute/disks). 2. Click on the disk for your critical VMs to see its configuration details. 4. Ensure that `Encryption type` is set to `Customer supplied`. **From Google Cloud CLI** Ensure `diskEncryptionKey` property in the below command's response is not null, and contains key `sha256` with corresponding value gcloud compute disks describe --zone --format=json(diskEncryptionKey,name) ", + "AdditionalInformation": "`Note 1:` When you delete a persistent disk, Google discards the cipher keys, rendering the data irretrievable. This process is irreversible. `Note 2:` It is up to you to generate and manage your key. You must provide a key that is a 256-bit string encoded in RFC 4648 standard base64 to Compute Engine. `Note 3:` An example key file looks like this. [ { uri: https://www.googleapis.com/compute/v1/projects/myproject/zones/us-central1-a/disks/example-disk, key: acXTX3rxrKAFTF0tYVLvydU1riRZTvUNC4g5I11NY-c=, key-type: raw }, { uri: https://www.googleapis.com/compute/v1/projects/myproject/global/snapshots/my-private-snapshot, key: ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHz0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDD6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oeQ5lAbtt7bYAAHf5l+gJWw3sUfs0/Glw5fpdjT8Uggrr+RMZezGrltJEF293rvTIjWOEB3z5OHyHwQkvdrPDFcTqsLfh+8Hr8g+mf+7zVPEC8nEbqpdl3GPv3A7AwpFp7MA== key-type: rsa-encrypted } ]", + "References": "https://cloud.google.com/compute/docs/disks/customer-supplied-encryption#encrypt_a_new_persistent_disk_with_your_own_keys:https://cloud.google.com/compute/docs/reference/rest/v1/disks/get:https://cloud.google.com/compute/docs/disks/customer-supplied-encryption#key_file", + "DefaultValue": "By default, VM disks are encrypted with Google-managed keys. They are not encrypted with Customer-Supplied Encryption Keys." + } + ] + }, + { + "Id": "4.8", + "Description": "Ensure Compute Instances Are Launched With Shielded VM Enabled", + "Checks": [ + "compute_instance_shielded_vm_enabled" + ], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "To defend against advanced threats and ensure that the boot loader and firmware on your VMs are signed and untampered, it is recommended that Compute instances are launched with Shielded VM enabled.", + "RationaleStatement": "Shielded VMs are virtual machines (VMs) on Google Cloud Platform hardened by a set of security controls that help defend against rootkits and bootkits. Shielded VM offers verifiable integrity of your Compute Engine VM instances, so you can be confident your instances haven't been compromised by boot- or kernel-level malware or rootkits. Shielded VM's verifiable integrity is achieved through the use of Secure Boot, virtual trusted platform module (vTPM)-enabled Measured Boot, and integrity monitoring. Shielded VM instances run firmware which is signed and verified using Google's Certificate Authority, ensuring that the instance's firmware is unmodified and establishing the root of trust for Secure Boot. Integrity monitoring helps you understand and make decisions about the state of your VM instances and the Shielded VM vTPM enables Measured Boot by performing the measurements needed to create a known good boot baseline, called the integrity policy baseline. The integrity policy baseline is used for comparison with measurements from subsequent VM boots to determine if anything has changed. Secure Boot helps ensure that the system only runs authentic software by verifying the digital signature of all boot components, and halting the boot process if signature verification fails.", + "ImpactStatement": "", + "RemediationProcedure": "To be able turn on `Shielded VM` on an instance, your instance must use an image with Shielded VM support. **From Google Cloud Console** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. Click on the instance name to see its `VM instance details` page. 3. Click `STOP` to stop the instance. 4. When the instance has stopped, click `EDIT`. 5. In the Shielded VM section, select `Turn on vTPM` and `Turn on Integrity Monitoring`. 6. Optionally, if you do not use any custom or unsigned drivers on the instance, also select `Turn on Secure Boot`. 7. Click the `Save` button to modify the instance and then click `START` to restart it. **From Google Cloud CLI** You can only enable Shielded VM options on instances that have Shielded VM support. For a list of Shielded VM public images, run the gcloud compute images list command with the following flags: gcloud compute images list --project gce-uefi-images --no-standard-images 1. Stop the instance: gcloud compute instances stop 2. Update the instance: gcloud compute instances update --shielded-vtpm --shielded-vm-integrity-monitoring 3. Optionally, if you do not use any custom or unsigned drivers on the instance, also turn on secure boot. gcloud compute instances update --shielded-vm-secure-boot 4. Restart the instance: gcloud compute instances start **Prevention:** You can ensure that all new VMs will be created with Shielded VM enabled by setting up an Organization Policy to for `Shielded VM` at [https://console.cloud.google.com/iam-admin/orgpolicies/compute-requireShieldedVm](https://console.cloud.google.com/iam-admin/orgpolicies/compute-requireShieldedVm). Learn more at: [https://cloud.google.com/security/shielded-cloud/shielded-vm#organization-policy-constraint](https://cloud.google.com/security/shielded-cloud/shielded-vm#organization-policy-constraint).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. Click on the instance name to see its `VM instance details` page. 3. Under the section `Shielded VM`, ensure that `vTPM` and `Integrity Monitoring` are `on`. **From Google Cloud CLI** 1. For each instance in your project, get its metadata: gcloud compute instances list --format=json | jq -r '. | vTPM: (.[].shieldedInstanceConfig.enableVtpm) IntegrityMonitoring: (.[].shieldedInstanceConfig.enableIntegrityMonitoring) Name: (.[].name)' 2. Ensure that there is a `shieldedInstanceConfig` configuration and that configuration has the `enableIntegrityMonitoring` and `enableVtpm` set to `true`. If the VM is not a Shield VM image, you will not see a shieldedInstanceConfig` in the output.", + "AdditionalInformation": "If you do use custom or unsigned drivers on the instance, enabling Secure Boot will cause the machine to no longer boot. Turn on Secure Boot only on instances that have been verified to not have any custom drivers installed.", + "References": "https://cloud.google.com/compute/docs/instances/modifying-shielded-vm:https://cloud.google.com/shielded-vm:https://cloud.google.com/security/shielded-cloud/shielded-vm#organization-policy-constraint", + "DefaultValue": "By default, Compute Instances do not have Shielded VM enabled." + } + ] + }, + { + "Id": "4.9", + "Description": "Ensure That Compute Instances Do Not Have Public IP Addresses", + "Checks": [ + "compute_instance_public_ip" + ], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "Compute instances should not be configured to have external IP addresses.", + "RationaleStatement": "To reduce your attack surface, Compute instances should not have public IP addresses. Instead, instances should be configured behind load balancers, to minimize the instance's exposure to the internet.", + "ImpactStatement": "Removing the external IP address from your Compute instance may cause some applications to stop working.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. Click on the instance name to go the the `Instance detail page`. 3. Click `Edit`. 4. For each Network interface, ensure that `External IP` is set to `None`. 5. Click `Done` and then click `Save`. **From Google Cloud CLI** 1. Describe the instance properties: gcloud compute instances describe --zone= 2. Identify the access config name that contains the external IP address. This access config appears in the following format: networkInterfaces: - accessConfigs: - kind: compute#accessConfig name: External NAT natIP: 130.211.181.55 type: ONE_TO_ONE_NAT 3. Delete the access config. gcloud compute instances delete-access-config --zone= --access-config-name In the above example, the `ACCESS_CONFIG_NAME` is `External NAT`. The name of your access config might be different. **Prevention:** You can configure the `Define allowed external IPs for VM instances` Organization Policy to prevent VMs from being configured with public IP addresses. Learn more at: [https://console.cloud.google.com/orgpolicies/compute-vmExternalIpAccess](https://console.cloud.google.com/orgpolicies/compute-vmExternalIpAccess)", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. For every VM, ensure that there is no `External IP` configured. **From Google Cloud CLI** gcloud compute instances list --format=json 1. The output should not contain an `accessConfigs` section under `networkInterfaces`. Note that the `natIP` value is present only for instances that are running or for instances that are stopped but have a static IP address. For instances that are stopped and are configured to have an ephemeral public IP address, the `natIP` field will not be present. Example output: networkInterfaces: - accessConfigs: - kind: compute#accessConfig name: External NAT networkTier: STANDARD type: ONE_TO_ONE_NAT **Exception:** Instances created by GKE should be excluded because some of them have external IP addresses and cannot be changed by editing the instance settings. Instances created by GKE should be excluded. These instances have names that start with gke- and are labeled goog-gke-node.", + "AdditionalInformation": "You can connect to Linux VMs that do not have public IP addresses by using Identity-Aware Proxy for TCP forwarding. Learn more at [https://cloud.google.com/compute/docs/instances/connecting-advanced#sshbetweeninstances](https://cloud.google.com/compute/docs/instances/connecting-advanced#sshbetweeninstances) For Windows VMs, see [https://cloud.google.com/compute/docs/instances/connecting-to-instance](https://cloud.google.com/compute/docs/instances/connecting-to-instance).", + "References": "https://cloud.google.com/load-balancing/docs/backend-service#backends_and_external_ip_addresses:https://cloud.google.com/compute/docs/instances/connecting-advanced#sshbetweeninstances:https://cloud.google.com/compute/docs/instances/connecting-to-instance:https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address#unassign_ip:https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints", + "DefaultValue": "By default, Compute instances have a public IP address." + } + ] + }, + { + "Id": "4.1", + "Description": "Ensure That App Engine Applications Enforce HTTPS Connections", + "Checks": [], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "In order to maintain the highest level of security all connections to an application should be secure by default.", + "RationaleStatement": "Insecure HTTP connections maybe subject to eavesdropping which can expose sensitive data.", + "ImpactStatement": "All connections to appengine will automatically be redirected to the HTTPS endpoint ensuring that all connections are secured by TLS.", + "RemediationProcedure": "Add a line to the app.yaml file controlling the application which enforces secure connections. For example handlers: - url: /.* **secure: always** redirect_http_response_code: 301 script: auto [https://cloud.google.com/appengine/docs/standard/python3/config/appref]", + "AuditProcedure": "Verify that the app.yaml file controlling the application contains a line which enforces secure connections. For example handlers: - url: /.* secure: always redirect_http_response_code: 301 script: auto [https://cloud.google.com/appengine/docs/standard/python3/config/appref](https://cloud.google.com/appengine/docs/standard/python3/config/appref)", + "AdditionalInformation": "", + "References": "https://cloud.google.com/appengine/docs/standard/python3/config/appref:https://cloud.google.com/appengine/docs/flexible/nodejs/configuring-your-app-with-app-yaml", + "DefaultValue": "By default both HTTP and HTTP are supported" + } + ] + }, + { + "Id": "4.11", + "Description": "Ensure That Compute Instances Have Confidential Computing Enabled", + "Checks": [ + "compute_instance_confidential_computing_enabled" + ], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "Google Cloud encrypts data at-rest and in-transit, but customer data must be decrypted for processing. Confidential Computing is a breakthrough technology that encrypts data in-use while it is being processed. Confidential Computing environments keep data encrypted in memory and elsewhere outside the central processing unit (CPU). Confidential VMs leverage hardware-based memory encryption technologies, such as AMD Secure Encrypted Virtualization (SEV), AMD SEV-SNP, and Intel Trust Domain Extensions (TDX), depending on the chosen machine type and CPU platform. Customer data will stay encrypted while it is used, indexed, queried, or trained on. Encryption keys are generated by and reside solely in dedicated hardware and are not exportable, enhancing isolation and security. Built-in hardware optimizations ensure Confidential Computing workloads experience minimal to no significant performance penalties.", + "RationaleStatement": "Confidential Computing enables customers' sensitive code and other data encrypted in memory during processing. Google does not have access to the encryption keys. Confidential VM can help alleviate concerns about risk related to either dependency on Google infrastructure or Google insiders' access to customer data in the clear.", + "ImpactStatement": "- Confidential Computing for Compute instances does not support live migration. Unlike regular Compute instances, Confidential VMs experience disruptions during maintenance events like a software or hardware update. - Additional charges may be incurred when enabling this security feature. See [https://cloud.google.com/compute/confidential-vm/pricing](https://cloud.google.com/compute/confidential-vm/pricing) for more info.", + "RemediationProcedure": "Confidential Computing can only be enabled when an instance is created. You must delete the current instance and create a new one. **From Google Cloud Console** 1. Go to the VM instances page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. Click `CREATE INSTANCE`. 3. Fill out the desired configuration for your instance. 4. Under the `Confidential VM service` section, check the option `Enable the Confidential Computing service on this VM instance`. 5. Click `Create`. **From Google Cloud CLI** Create a new instance with Confidential Compute enabled. gcloud compute instances create --zone --confidential-compute --maintenance-policy=TERMINATE ", + "AuditProcedure": "Note: Confidential Computing is currently only supported on limited VM configurations. To learn more about VM configurations supported by Confidential Computing, visit [https://cloud.google.com/confidential-computing/confidential-vm/docs/supported-configurations](https://cloud.google.com/confidential-computing/confidential-vm/docs/supported-configurations) **From Google Cloud Console** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. Click on the instance name to see its VM instance details page. 3. Ensure that `Confidential VM service` is `Enabled`. **From Google Cloud CLI** 1. List the instances in your project and get details on each instance: gcloud compute instances list --format=json 2. Ensure that `enableConfidentialCompute` is set to `true` for all instances with machine type starting with n2d-. confidentialInstanceConfig: enableConfidentialCompute: true ", + "AdditionalInformation": "", + "References": "https://cloud.google.com/compute/confidential-vm/docs/creating-cvm-instance:https://cloud.google.com/compute/confidential-vm/docs/about-cvm:https://cloud.google.com/confidential-computing:https://cloud.google.com/blog/products/identity-security/introducing-google-cloud-confidential-computing-with-confidential-vms", + "DefaultValue": "By default, Confidential Computing is disabled for Compute instances." + } + ] + }, + { + "Id": "4.12", + "Description": "Ensure the Latest Operating System Updates Are Installed On Your Virtual Machines in All Projects", + "Checks": [], + "Attributes": [ + { + "Section": "4 Virtual Machines", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Google Cloud Virtual Machines have the ability via an OS Config agent API to periodically (about every 10 minutes) report OS inventory data. A patch compliance API periodically reads this data, and cross references metadata to determine if the latest updates are installed. This is not the only Patch Management solution available to your organization and you should weigh your needs before committing to using this method.", + "RationaleStatement": "Keeping virtual machine operating systems up to date is a security best practice. Using this service will simplify this process.", + "ImpactStatement": "Most Operating Systems require a restart or changing critical resources to apply the updates. Using the Google Cloud VM manager for its OS Patch management will incur additional costs for each VM managed by it. Please view the VM manager pricing reference for further information.", + "RemediationProcedure": "**From Google Cloud Console** **Enabling OS Patch Management on a Project by Project Basis** **Install OS Config API for the Project** 1. Navigate into a project. In the expanded portal menu located at the top left of the screen hover over APIs & Services. Then in the menu right of that select API Libraries 2. Search for VM Manager (OS Config API) or scroll down in the left hand column and select the filter labeled Compute where it is the last listed. Open this API. 3. Click the blue 'Enable' button. **Add MetaData Tags for OSConfig Parsing** 1. From the main Google Cloud console, open the portal menu in the top left. Mouse over Computer Engine to expand the menu next to it. 2. Under the Settings heading, select Metadata. 3. In this view there will be a list of the project wide metadata tags for VMs. Click edit and 'add item' in the key column type 'enable-osconfig' and in the value column set it to 'true'. From Command Line 1. For project wide tagging, run the following command gcloud compute project-info add-metadata --project --metadata=enable-osconfig=TRUE Please see the reference /compute/docs/troubleshooting/vm-manager/verify-setup#metadata-enabled at the bottom for more options like instance specific tagging. Note: Adding a new tag via commandline may overwrite existing tags. You will need to do this at a time of low usage for the least impact. **Install and Start the Local OSConfig for Data Parsing** There is no way to centrally manage or start the Local OSConfig agent. Please view the reference of manage-os#agent-install to view specific operating system commands. **Setup a project wide Service Account** Please view Recommendation 4.1 to view how to setup a service account. Rerun the audit procedure to test if it has taken effect. **Enable NAT or Configure Private Google Access to allow Access to Public Update Hosting** For the sake of brevity, please see the attached resources to enable NAT or Private Google Access. Rerun the audit procedure to test if it has taken effect. From Command Line: **Install OS Config API for the Project** 1. In each project you wish to audit run gcloud services enable osconfig.googleapis.com **Install and Start the Local OSConfig for Data Parsing** Please view the reference of manage-os#agent-install to view specific operating system commands. **Setup a project wide Service Account** Please view Recommendation 4.1 to view how to setup a service account. Rerun the audit procedure to test if it has taken effect. **Enable NAT or Configure Private Google Access to allow Access to Public Update Hosting** For the sake of brevity, please see the attached resources to enable NAT or Private Google Access. Rerun the audit procedure to test if it has taken effect. Determine if Instances can connect to public update hosting Linux Debian Based Operating Systems sudo apt update The output should have a numbered list of lines with Hit: URL of updates. Redhat Based Operating Systems yum check-update The output should show a list of packages that have updates available. Windows ping http://windowsupdate.microsoft.com/ The ping should successfully be delivered and received.", + "AuditProcedure": "**From Google Cloud Console** **Determine if OS Config API is Enabled for the Project** 1. Navigate into a project. In the expanded navigation menu located at the top left of the screen hover over `APIs & Services`. Then in the menu right of that select `API Libraries` 2. Search for VM Manager (OS Config API) or scroll down in the left hand column and select the filter labeled Compute where it is the last listed. Open this API. 3. Verify the blue button at the top is enabled. **Determine if VM Instances have correct metadata tags for OSConfig parsing** 1. From the main Google Cloud console, open the hamburger menu in the top left. Mouse over Computer Engine to expand the menu next to it. 1. Under the Settings heading, select Metadata. 1. In this view there will be a list of the project wide metadata tags for VMs. Determine if the tag enable-osconfig is set to true. **Determine if the Operating System of VM Instances have the local OS-Config Agent running** There is no way to determine this from the Google Cloud console. The only way is to run operating specific commands locally inside the operating system via remote connection. For the sake of brevity of this recommendation please view the docs/troubleshooting/vm-manager/verify-setup reference at the bottom of the page. If you initialized your VM instance with a Google Supplied OS Image with a build date of later than v20200114 it will have the service installed. You should still determine its status for proper operation. **Verify the service account you have setup for the project in Recommendation 4.1 is running** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. Click on each instance name to go to its `VM instance details` page. 3. Under the section `Service Account`, take note of the service account 4. Run the commands locally for your operating system that are located at the docs/troubleshooting/vm-manager/verify-setup#service-account-enabled reference located at the bottom of this page. They should return the name of your service account. **Determine if Instances can connect to public update hosting** Each type of operating system has its own update process. You will need to determine on each operating system that it can reach the update servers via its network connection. The VM Manager doesn't host the updates, it will only allow you to centrally issue a command to each VM to update. **Determine if OS Config API is Enabled for the Project** 1. In each project you wish to enable run the following command gcloud services list 2. If osconfig.googleapis.com is in the left hand column it is enabled for this project. **Determine if VM Manager is Enabled for the Project** 1. Within the project run the following command: gcloud compute instances os-inventory describe VM-NAME --zone=ZONE The output will look like INSTANCE_ID INSTANCE_NAME OS OSCONFIG_AGENT_VERSION UPDATE_TIME 29255009728795105 centos7 CentOS Linux 7 (Core) 20210217.00-g1.el7 2021-04-12T22:19:36.559Z 5138980234596718741 rhel-8 Red Hat Enterprise Linux 8.3 (Ootpa) 20210316.00-g1.el8 2021-09-16T17:19:24Z 7127836223366142250 windows Microsoft Windows Server 2019 Datacenter 20210316.00.0+win@1 2021-09-16T17:13:18Z **Determine if VM Instances have correct metadata tags for OSConfig parsing** 1. Select the project you want to view tagging in. **From Google Cloud Console** 1. From the main Google Cloud console, open the hamburger menu in the top left. Mouse over Computer Engine to expand the menu next to it. 2. Under the Settings heading, select Metadata. 3. In this view there will be a list of the project wide metadata tags for Vms. Verify a tag of ‘enable-osconfig’ is in this list and it is set to ‘true’. **From Command Line** Run the following command to view instance data gcloud compute instances list --format=table(name,status,tags.list()) On each instance it should have a tag of ‘enable-osconfig’ set to ‘true’ **Determine if the Operating System of VM Instances have the local OS-Config Agent running** There is no way to determine this from the Google Cloud CLI. The best way is to run the the commands inside the operating system located at 'Check OS-Config agent is installed and running' at the /docs/troubleshooting/vm-manager/verify-setup reference at the bottom of the page. If you initialized your VM instance with a Google Supplied OS Image with a build date of later than v20200114 it will have the service installed. You should still determine its status. **Verify the service account you have setup for the project in Recommendation 4.1 is running** 1. Go to the `VM instances` page by visiting: [https://console.cloud.google.com/compute/instances](https://console.cloud.google.com/compute/instances). 2. Click on each instance name to go to its `VM instance details` page. 3. Under the section `Service Account`, take note of the service account 4. View the compute/docs/troubleshooting/vm-manager/verify-setup#service-account-enabled resource at the bottom of the page for operating system specific commands to run locally. **Determine if Instances can connect to public update hosting** Linux Debian Based Operating Systems sudo apt update The output should have a numbered list of lines with Hit: URL of updates. Redhat Based Operating Systems yum check-update The output should show a list of packages that have updates available. Windows ping http://windowsupdate.microsoft.com/ The ping should successfully be delivered and received.", + "AdditionalInformation": "This is not your only solution to handle updates. This is a Google Cloud specific recommendation to leverage a resource to solve the need for comprehensive update procedures and policy. If you have a solution already in place you do not need to make the switch. There are also further resources that would be out of the scope of this recommendation. If you need to allow your VMs to access public hosted updates, please see the reference to setup NAT or Private Google Access.", + "References": "https://cloud.google.com/compute/docs/manage-os:https://cloud.google.com/compute/docs/os-patch-management:https://cloud.google.com/compute/docs/vm-manager:https://cloud.google.com/compute/docs/images/os-details#vm-manager:https://cloud.google.com/compute/docs/vm-manager#pricing:https://cloud.google.com/compute/docs/troubleshooting/vm-manager/verify-setup:https://cloud.google.com/compute/docs/instances/view-os-details#view-data-tools:https://cloud.google.com/compute/docs/os-patch-management/create-patch-job:https://cloud.google.com/nat/docs/set-up-network-address-translation:https://cloud.google.com/vpc/docs/configure-private-google-access:https://workbench.cisecurity.org/sections/811638/recommendations/1334335:https://cloud.google.com/compute/docs/manage-os#agent-install:https://cloud.google.com/compute/docs/troubleshooting/vm-manager/verify-setup#service-account-enabled:https://cloud.google.com/compute/docs/os-patch-management#use-dashboard:https://cloud.google.com/compute/docs/troubleshooting/vm-manager/verify-setup#metadata-enabled", + "DefaultValue": "By default most operating systems and programs do not update themselves. The Google Cloud VM Manager which is a dependency of the OS Patch management feature is installed on Google Built OS images with a build date of v20200114 or later. The VM manager is not enabled in a project by default and will need to be setup." + } + ] + }, + { + "Id": "5.1", + "Description": "Ensure That Cloud Storage Bucket Is Not Anonymously or Publicly Accessible", + "Checks": [ + "cloudstorage_bucket_public_access" + ], + "Attributes": [ + { + "Section": "5 Storage", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended that IAM policy on Cloud Storage bucket does not allows anonymous or public access.", + "RationaleStatement": "Allowing anonymous or public access grants permissions to anyone to access bucket content. Such access might not be desired if you are storing any sensitive data. Hence, ensure that anonymous or public access to a bucket is not allowed.", + "ImpactStatement": "No storage buckets would be publicly accessible. You would have to explicitly administer bucket access.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `Storage browser` by visiting [https://console.cloud.google.com/storage/browser](https://console.cloud.google.com/storage/browser). 2. Click on the bucket name to go to its `Bucket details` page. 3. Click on the `Permissions` tab. 4. Click `Delete` button in front of `allUsers` and `allAuthenticatedUsers` to remove that particular role assignment. **From Google Cloud CLI** Remove `allUsers` and `allAuthenticatedUsers` access. gsutil iam ch -d allUsers gs://BUCKET_NAME gsutil iam ch -d allAuthenticatedUsers gs://BUCKET_NAME **Prevention:** You can prevent Storage buckets from becoming publicly accessible by setting up the `Domain restricted sharing` organization policy at:[ https://console.cloud.google.com/iam-admin/orgpolicies/iam-allowedPolicyMemberDomains ](https://console.cloud.google.com/iam-admin/orgpolicies/iam-allowedPolicyMemberDomains).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `Storage browser` by visiting [https://console.cloud.google.com/storage/browser](https://console.cloud.google.com/storage/browser). 2. Click on each bucket name to go to its `Bucket details` page. 3. Click on the `Permissions` tab. 4. Ensure that `allUsers` and `allAuthenticatedUsers` are not in the `Members` list. **From Google Cloud CLI** 1. List all buckets in a project gsutil ls 2. Check the IAM Policy for each bucket: gsutil iam get gs://BUCKET_NAME No role should contain `allUsers` and/or `allAuthenticatedUsers` as a member. **Using Rest API** 1. List all buckets in a project Get https://www.googleapis.com/storage/v1/b?project= 2. Check the IAM Policy for each bucket GET https://www.googleapis.com/storage/v1/b//iam No role should contain `allUsers` and/or `allAuthenticatedUsers` as a member.", + "AdditionalInformation": "To implement Access restrictions on buckets, configuring Bucket IAM is preferred way than configuring Bucket ACL. On GCP console, Edit Permissions for bucket exposes IAM configurations only. Bucket ACLs are configured automatically as per need in order to implement/support User enforced Bucket IAM policy. In-case administrator changes bucket ACL using command-line(gsutils)/API bucket IAM also gets updated automatically.", + "References": "https://cloud.google.com/storage/docs/access-control/iam-reference:https://cloud.google.com/storage/docs/access-control/making-data-public:https://cloud.google.com/storage/docs/gsutil/commands/iam", + "DefaultValue": "By Default, Storage buckets are not publicly shared." + } + ] + }, + { + "Id": "5.2", + "Description": "Ensure That Cloud Storage Buckets Have Uniform Bucket-Level Access Enabled", + "Checks": [ + "cloudstorage_bucket_uniform_bucket_level_access" + ], + "Attributes": [ + { + "Section": "5 Storage", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "It is recommended that uniform bucket-level access is enabled on Cloud Storage buckets.", + "RationaleStatement": "It is recommended to use uniform bucket-level access to unify and simplify how you grant access to your Cloud Storage resources. Cloud Storage offers two systems for granting users permission to access your buckets and objects: Cloud Identity and Access Management (Cloud IAM) and Access Control Lists (ACLs). These systems act in parallel - in order for a user to access a Cloud Storage resource, only one of the systems needs to grant the user permission. Cloud IAM is used throughout Google Cloud and allows you to grant a variety of permissions at the bucket and project levels. ACLs are used only by Cloud Storage and have limited permission options, but they allow you to grant permissions on a per-object basis. In order to support a uniform permissioning system, Cloud Storage has uniform bucket-level access. Using this feature disables ACLs for all Cloud Storage resources: access to Cloud Storage resources then is granted exclusively through Cloud IAM. Enabling uniform bucket-level access guarantees that if a Storage bucket is not publicly accessible, no object in the bucket is publicly accessible either.", + "ImpactStatement": "If you enable uniform bucket-level access, you revoke access from users who gain their access solely through object ACLs. Certain Google Cloud services, such as Stackdriver, Cloud Audit Logs, and Datastore, cannot export to Cloud Storage buckets that have uniform bucket-level access enabled.", + "RemediationProcedure": "**From Google Cloud Console** 1. Open the Cloud Storage browser in the Google Cloud Console by visiting: [https://console.cloud.google.com/storage/browser](https://console.cloud.google.com/storage/browser) 2. In the list of buckets, click on the name of the desired bucket. 3. Select the `Permissions` tab near the top of the page. 4. In the text box that starts with `This bucket uses fine-grained access control...`, click `Edit`. 5. In the pop-up menu that appears, select `Uniform`. 6. Click `Save`. **From Google Cloud CLI** Use the on option in a uniformbucketlevelaccess set command: gsutil uniformbucketlevelaccess set on gs://BUCKET_NAME/ **Prevention** You can set up an Organization Policy to enforce that any new bucket has uniform bucket level access enabled. Learn more at: [https://cloud.google.com/storage/docs/setting-org-policies#uniform-bucket](https://cloud.google.com/storage/docs/setting-org-policies#uniform-bucket)", + "AuditProcedure": "**From Google Cloud Console** 1. Open the Cloud Storage browser in the Google Cloud Console by visiting: [https://console.cloud.google.com/storage/browser](https://console.cloud.google.com/storage/browser) 2. For each bucket, make sure that `Access control` column has the value `Uniform`. **From Google Cloud CLI** 1. List all buckets in a project gsutil ls 2. For each bucket, verify that uniform bucket-level access is enabled. gsutil uniformbucketlevelaccess get gs://BUCKET_NAME/ If uniform bucket-level access is enabled, the response looks like: Uniform bucket-level access setting for gs://BUCKET_NAME/: Enabled: True LockedTime: LOCK_DATE ", + "AdditionalInformation": "Uniform bucket-level access can no longer be disabled if it has been active on a bucket for 90 consecutive days.", + "References": "https://cloud.google.com/storage/docs/uniform-bucket-level-access:https://cloud.google.com/storage/docs/using-uniform-bucket-level-access:https://cloud.google.com/storage/docs/setting-org-policies#uniform-bucket", + "DefaultValue": "By default, Cloud Storage buckets do not have uniform bucket-level access enabled." + } + ] + }, + { + "Id": "6.4", + "Description": "Ensure That the Cloud SQL Database Instance Requires All Incoming Connections To Use SSL", + "Checks": [ + "cloudsql_instance_ssl_connections" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to enforce all incoming connections to SQL database instance to use SSL.", + "RationaleStatement": "SQL database connections if successfully trapped (MITM); can reveal sensitive data like credentials, database queries, query outputs etc. For security, it is recommended to always use SSL encryption when connecting to your instance. This recommendation is applicable for Postgresql, MySql generation 1, MySql generation 2 and SQL Server 2017 instances.", + "ImpactStatement": "After enforcing SSL requirement for connections, existing client will not be able to communicate with Cloud SQL database instance unless they use SSL encrypted connections to communicate to Cloud SQL database instance.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Click on an instance name to see its configuration overview. 3. In the left-side panel, select `Connections`. 3. In the `security` section, select SSL mode as `Allow only SSL connections`. 4. Under `Configure SSL server certificates` click `Create new certificate` and save the setting **From Google Cloud CLI** To enforce SSL encryption for an instance run the command: gcloud sql instances patch INSTANCE_NAME --ssl-mode= ENCRYPTED_ONLY Note: `RESTART` is required for type MySQL Generation 1 Instances (`backendType: FIRST_GEN`) to get this configuration in effect.", + "AuditProcedure": "**From Google Cloud Console** 1. Go to [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Click on an instance name to see its configuration overview. 3. In the left-side panel, select `Connections`. 3. In the `Security` section, ensure that `Allow only SSL connections` option is selected. **From Google Cloud CLI** 1. Get the detailed configuration for every SQL database instance using the following command: gcloud sql instances list --format=json Ensure that section `settings: ipConfiguration` has the parameter `sslMode` set to `ENCRYPTED_ONLY `.", + "AdditionalInformation": "By default `Settings: ipConfiguration` has no `authorizedNetworks` set/configured. In that case even if by default `sslMode` is not set, which is equivalent to `sslMode:ALLOW_UNENCRYPTED_AND_ENCRYPTED ` there is no risk as instance cannot be accessed outside of the network unless `authorizedNetworks` are configured. However, If default for `sslMode` is not updated to `ENCRYPTED_ONLY ` any `authorizedNetworks` created later on will not enforce SSL only connection.", + "References": "https://cloud.google.com/sql/docs/postgres/configure-ssl-instance/", + "DefaultValue": "By default parameter `settings: ipConfiguration: sslMode` is not set which is equivalent to `sslMode:ALLOW_UNENCRYPTED_AND_ENCRYPTED`." + } + ] + }, + { + "Id": "6.5", + "Description": "Ensure That Cloud SQL Database Instances Do Not Implicitly Whitelist All Public IP Addresses", + "Checks": [ + "cloudsql_instance_public_access" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Database Server should accept connections only from trusted Network(s)/IP(s) and restrict access from public IP addresses.", + "RationaleStatement": "To minimize attack surface on a Database server instance, only trusted/known and required IP(s) should be white-listed to connect to it. An authorized network should not have IPs/networks configured to `0.0.0.0/0` which will allow access to the instance from anywhere in the world. Note that authorized networks apply only to instances with public IPs.", + "ImpactStatement": "The Cloud SQL database instance would not be available to public IP addresses.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Click the instance name to open its `Instance details` page. 3. Under the `Configuration` section click `Edit configurations` 4. Under `Configuration options` expand the `Connectivity` section. 5. Click the `delete` icon for the authorized network `0.0.0.0/0`. 6. Click `Save` to update the instance. **From Google Cloud CLI** Update the authorized network list by dropping off any addresses. gcloud sql instances patch --authorized-networks=IP_ADDR1,IP_ADDR2... **Prevention:** To prevent new SQL instances from being configured to accept incoming connections from any IP addresses, set up a `Restrict Authorized Networks on Cloud SQL instances` Organization Policy at: [https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictAuthorizedNetworks](https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictAuthorizedNetworks).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Click the instance name to open its `Instance details` page. 3. Under the `Configuration` section click `Edit configurations` 4. Under `Configuration options` expand the `Connectivity` section. 5. Ensure that no authorized network is configured to allow `0.0.0.0/0`. **From Google Cloud CLI** 1. Get detailed configuration for every Cloud SQL database instance. gcloud sql instances list --format=json Ensure that the section `settings: ipConfiguration : authorizedNetworks` does not have any parameter `value` containing `0.0.0.0/0`.", + "AdditionalInformation": "There is no IPv6 configuration found for Google cloud SQL server services.", + "References": "https://cloud.google.com/sql/docs/mysql/configure-ip:https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictAuthorizedNetworks:https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints:https://cloud.google.com/sql/docs/mysql/connection-org-policy", + "DefaultValue": "By default, authorized networks are not configured. Remote connection to Cloud SQL database instance is not possible unless authorized networks are configured." + } + ] + }, + { + "Id": "6.6", + "Description": "Ensure That Cloud SQL Database Instances Do Not Have Public IPs", + "Checks": [ + "cloudsql_instance_public_access" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "It is recommended to configure Second Generation Sql instance to use private IPs instead of public IPs.", + "RationaleStatement": "To lower the organization's attack surface, Cloud SQL databases should not have public IPs. Private IPs provide improved network security and lower latency for your application.", + "ImpactStatement": "Removing the public IP address on SQL instances may break some applications that relied on it for database connectivity.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console: [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances) 2. Click the instance name to open its Instance details page. 3. Select the `Connections` tab. 4. Deselect the `Public IP` checkbox. 5. Click `Save` to update the instance. **From Google Cloud CLI** 1. For every instance remove its public IP and assign a private IP instead: gcloud sql instances patch --network= --no-assign-ip 2. Confirm the changes using the following command:: gcloud sql instances describe **Prevention:** To prevent new SQL instances from getting configured with public IP addresses, set up a `Restrict Public IP access on Cloud SQL instances` Organization policy at: [https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictPublicIp](https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictPublicIp).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console: [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances) 2. Ensure that every instance has a private IP address and no public IP address configured. **From Google Cloud CLI** 1. List all Cloud SQL database instances using the following command: gcloud sql instances list 2. For every instance of type `instanceType: CLOUD_SQL_INSTANCE` with `backendType: SECOND_GEN`, get detailed configuration. Ignore instances of type `READ_REPLICA_INSTANCE` because these instances inherit their settings from the primary instance. Also, note that first generation instances cannot be configured to have a private IP address. gcloud sql instances describe 3. Ensure that the setting `ipAddresses` has an IP address configured of `type: PRIVATE` and has no IP address of `type: PRIMARY`. `PRIMARY` IP addresses are public addresses. An instance can have both a private and public address at the same time. Note also that you cannot use private IP with First Generation instances.", + "AdditionalInformation": "Replicas inherit their private IP status from their primary instance. You cannot configure a private IP directly on a replica.", + "References": "https://cloud.google.com/sql/docs/mysql/configure-private-ip:https://cloud.google.com/sql/docs/mysql/private-ip:https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints:https://console.cloud.google.com/iam-admin/orgpolicies/sql-restrictPublicIp", + "DefaultValue": "By default, Cloud Sql instances have a public IP." + } + ] + }, + { + "Id": "6.7", + "Description": "Ensure That Cloud SQL Database Instances Are Configured With Automated Backups", + "Checks": [ + "cloudsql_instance_automated_backups" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to have all SQL database instances set to enable automated backups.", + "RationaleStatement": "Backups provide a way to restore a Cloud SQL instance to recover lost data or recover from a problem with that instance. Automated backups need to be set for any instance that contains data that should be protected from loss or damage. This recommendation is applicable for SQL Server, PostgreSql, MySql generation 1 and MySql generation 2 instances.", + "ImpactStatement": "Automated Backups will increase required size of storage and costs associated with it.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance where the backups need to be configured. 3. Click `Edit`. 4. In the `Backups` section, check `Enable automated backups', and choose a backup window. 5. Click `Save`. **From Google Cloud CLI** 1. List all Cloud SQL database instances using the following command: gcloud sql instances list --format=json | jq '. | map(select(.instanceType != READ_REPLICA_INSTANCE)) | .[].name' NOTE: gcloud command has been added with the filter to exclude read-replicas instances, as GCP do not provide Automated Backups for read-replica instances. 2. Enable `Automated backups` for every Cloud SQL database instance using the below command: gcloud sql instances patch --backup-start-time <[HH:MM]> The `backup-start-time` parameter is specified in 24-hour time, in the UTC±00 time zone, and specifies the start of a 4-hour backup window. Backups can start any time during the backup window.", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Click the instance name to open its instance details page. 3. Go to the `Backups` menu. 4. Ensure that `Automated backups` is set to `Enabled` and `Backup time` is mentioned. **From Google Cloud CLI** 1. List all Cloud SQL database instances using the following command: gcloud sql instances list --format=json | jq '. | map(select(.instanceType != READ_REPLICA_INSTANCE)) | .[].name' NOTE: gcloud command has been added with the filter to exclude read-replicas instances, as GCP do not provide Automated Backups for read-replica instances. 2. Ensure that the below command returns `True` for every Cloud SQL database instance. gcloud sql instances describe --format=value('Enabled':settings.backupConfiguration.enabled) ", + "AdditionalInformation": "", + "References": "https://cloud.google.com/sql/docs/mysql/backup-recovery/backups:https://cloud.google.com/sql/docs/postgres/backup-recovery/backups:https://cloud.google.com/sql/docs/sqlserver/backup-recovery/backups:https://cloud.google.com/sql/docs/mysql/backup-recovery/backing-up:https://cloud.google.com/sql/docs/postgres/backup-recovery/backing-up:https://cloud.google.com/sql/docs/sqlserver/backup-recovery/backing-up", + "DefaultValue": "By default, automated backups are not configured for Cloud SQL instances." + } + ] + }, + { + "Id": "6.1.1", + "Description": "Ensure That a MySQL Instance Does Not Allow Anyone To Connect With Administrative Privileges", + "Checks": [], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.1 MySQL Database", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "It is recommended to set a password for the administrative user (`root` by default) to prevent unauthorized access to the SQL database instances. This recommendation is applicable only for MySQL Instances. PostgreSQL does not offer any setting for No Password from the cloud console.", + "RationaleStatement": "At the time of MySQL Instance creation, not providing an administrative password allows anyone to connect to the SQL database instance with administrative privileges. The root password should be set to ensure only authorized users have these privileges.", + "ImpactStatement": "Connection strings for administrative clients need to be reconfigured to use a password.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Platform Console using `https://console.cloud.google.com/sql/` 2. Select the instance to open its Overview page. 3. Select `Access Control > Users`. 4. Click the `More actions icon` for the user to be updated. 5. Select `Change password`, specify a `New password`, and click `OK`. **From Google Cloud CLI** 1. Set a password to a MySql instance: gcloud sql users set-password root --host= --instance= --prompt-for-password 2. A prompt will appear, requiring the user to enter a password: Instance Password: 3. With a successful password configured, the following message should be seen: Updating Cloud SQL user...done. ", + "AuditProcedure": "**From Google Cloud CLI** 1. List All SQL database instances of type MySQL: gcloud sql instances list --filter='DATABASE_VERSION:MYSQL*' --project --format=(NAME,PRIMARY_ADDRESS) 2. For every MySQL instance try to connect using the `PRIMARY_ADDRESS`, if available: mysql -u root -h The command should return either an error message or a password prompt. Sample Error message: ERROR 1045 (28000): Access denied for user 'root'@'' (using password: NO) If a command produces the `mysql>` prompt, the MySQL instance allows anyone to connect with administrative privileges without needing a password. **Note:** The `No Password` setting is exposed only at the time of MySQL instance creation. Once the instance is created, the Google Cloud Platform Console does not expose the set to confirm whether a password for an administrative user is set to a MySQL instance.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/sql/docs/mysql/create-manage-users:https://cloud.google.com/sql/docs/mysql/create-instance", + "DefaultValue": "From the Google Cloud Platform Console, the `Create Instance` workflow enforces the rule to enter the root password unless the option `No Password` is selected explicitly." + } + ] + }, + { + "Id": "6.1.2", + "Description": "Ensure ‘Skip_show_database’ Database Flag for Cloud SQL MySQL Instance Is Set to ‘On’", + "Checks": [ + "cloudsql_instance_mysql_skip_show_database_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.1 MySQL Database", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to set `skip_show_database` database flag for Cloud SQL Mysql instance to `on`", + "RationaleStatement": "`skip_show_database` database flag prevents people from using the SHOW DATABASES statement if they do not have the SHOW DATABASES privilege. This can improve security if you have concerns about users being able to see databases belonging to other users. Its effect depends on the SHOW DATABASES privilege: If the variable value is ON, the SHOW DATABASES statement is permitted only to users who have the SHOW DATABASES privilege, and the statement displays all database names. If the value is OFF, SHOW DATABASES is permitted to all users, but displays the names of only those databases for which the user has the SHOW DATABASES or other privilege. This recommendation is applicable to Mysql database instances.", + "ImpactStatement": "", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the Mysql instance for which you want to enable to database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add a Database Flag`, choose the flag `skip_show_database` from the drop-down menu, and set its value to `on`. 6. Click `Save` to save your changes. 7. Confirm your changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. List all Cloud SQL database Instances gcloud sql instances list 2. Configure the `skip_show_database` database flag for every Cloud SQL Mysql database instance using the below command. gcloud sql instances patch --database-flags skip_show_database=on **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Ensure the database flag `skip_show_database` that has been set is listed under the `Database flags` section. **From Google Cloud CLI** 1. List all Cloud SQL database Instances gcloud sql instances list 2. Ensure the below command returns `on` for every Cloud SQL Mysql database instance gcloud sql instances describe --format=json | jq '.settings.databaseFlags[] | select(.name==skip_show_database)|.value' ", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/mysql/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability, and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag restarts the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/mysql/flags:https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_skip_show_database", + "DefaultValue": "" + } + ] + }, + { + "Id": "6.1.3", + "Description": "Ensure That the ‘Local_infile’ Database Flag for a Cloud SQL MySQL Instance Is Set to ‘Off’", + "Checks": [ + "cloudsql_instance_mysql_local_infile_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.1 MySQL Database", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to set the `local_infile` database flag for a Cloud SQL MySQL instance to `off`.", + "RationaleStatement": "The `local_infile` flag controls the server-side LOCAL capability for LOAD DATA statements. Depending on the `local_infile` setting, the server refuses or permits local data loading by clients that have LOCAL enabled on the client side. To explicitly cause the server to refuse LOAD DATA LOCAL statements (regardless of how client programs and libraries are configured at build time or runtime), start mysqld with local_infile disabled. local_infile can also be set at runtime. Due to security issues associated with the `local_infile` flag, it is recommended to disable it. This recommendation is applicable to MySQL database instances.", + "ImpactStatement": "Disabling `local_infile` makes the server refuse local data loading by clients that have LOCAL enabled on the client side.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the MySQL instance where the database flag needs to be enabled. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add a Database Flag`, choose the flag `local_infile` from the drop-down menu, and set its value to `off`. 6. Click `Save`. 7. Confirm the changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. List all Cloud SQL database instances using the following command: gcloud sql instances list 2. Configure the `local_infile` database flag for every Cloud SQL Mysql database instance using the below command: gcloud sql instances patch --database-flags local_infile=off **Note**: This command will overwrite all database flags that were previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Ensure the database flag `local_infile` that has been set is listed under the `Database flags` section. **From Google Cloud CLI** 1. List all Cloud SQL database instances: gcloud sql instances list 2. Ensure the below command returns `off` for every Cloud SQL MySQL database instance. gcloud sql instances describe --format=json | jq '.settings.databaseFlags[] | select(.name==local_infile)|.value' ", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require the instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/mysql/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability, and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines.", + "References": "https://cloud.google.com/sql/docs/mysql/flags:https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_local_infile:https://dev.mysql.com/doc/refman/5.7/en/load-data-local.html", + "DefaultValue": "By default `local_infile` is `on`." + } + ] + }, + { + "Id": "6.2.1", + "Description": "Ensure ‘Log_error_verbosity’ Database Flag for Cloud SQL PostgreSQL Instance Is Set to ‘DEFAULT’ or Stricter", + "Checks": [ + "cloudsql_instance_postgres_log_error_verbosity_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.2 PostgreSQL Database", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "The `log_error_verbosity` flag controls the verbosity/details of messages logged. Valid values are: - `TERSE` - `DEFAULT` - `VERBOSE` `TERSE` excludes the logging of `DETAIL`, `HINT`, `QUERY`, and `CONTEXT` error information. `VERBOSE` output includes the `SQLSTATE` error code, source code file name, function name, and line number that generated the error. Ensure an appropriate value is set to 'DEFAULT' or stricter.", + "RationaleStatement": "Auditing helps in troubleshooting operational problems and also permits forensic analysis. If `log_error_verbosity` is not set to the correct value, too many details or too few details may be logged. This flag should be configured with a value of 'DEFAULT' or stricter. This recommendation is applicable to PostgreSQL database instances.", + "ImpactStatement": "Turning on logging will increase the required storage over time. Mismanaged logs may cause your storage costs to increase. Setting custom flags via command line on certain instances will cause all omitted flags to be reset to defaults. This may cause you to lose custom flags and could result in unforeseen complications or instance restarts. Because of this, it is recommended you apply these flags changes during a period of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting https://console.cloud.google.com/sql/instances. 2. Select the PostgreSQL instance for which you want to enable the database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add a Database Flag`, choose the flag `log_error_verbosity` from the drop-down menu and set appropriate value. 6. Click `Save` to save your changes. 7. Confirm your changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. Configure the log_error_verbosity database flag for every Cloud SQL PosgreSQL database instance using the below command. gcloud sql instances patch INSTANCE_NAME --database-flags log_error_verbosity= **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Go to `Configuration` card 4. Under `Database flags`, check the value of `log_error_verbosity` flag is set to 'DEFAULT' or stricter. **From Google Cloud CLI** 1. Use the below command for every Cloud SQL PostgreSQL database instance to verify the value of `log_error_verbosity` gcloud sql instances describe [INSTANCE_NAME] --format=json | jq '.settings.databaseFlags[] | select(.name==log_error_verbosity)|.value' In the output, database flags are listed under the settings as the collection databaseFlags.", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/postgres/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag does not require restarting the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/postgres/flags:https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-ERROR-VERBOSITY", + "DefaultValue": "By default `log_error_verbosity` is `DEFAULT`." + } + ] + }, + { + "Id": "6.2.2", + "Description": "Ensure That the ‘Log_connections’ Database Flag for Cloud SQL PostgreSQL Instance Is Set to ‘On’", + "Checks": [ + "cloudsql_instance_postgres_log_connections_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.2 PostgreSQL Database", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Enabling the `log_connections` setting causes each attempted connection to the server to be logged, along with successful completion of client authentication. This parameter cannot be changed after the session starts.", + "RationaleStatement": "PostgreSQL does not log attempted connections by default. Enabling the `log_connections` setting will create log entries for each attempted connection as well as successful completion of client authentication which can be useful in troubleshooting issues and to determine any unusual connection attempts to the server. This recommendation is applicable to PostgreSQL database instances.", + "ImpactStatement": "Turning on logging will increase the required storage over time. Mismanaged logs may cause your storage costs to increase. Setting custom flags via command line on certain instances will cause all omitted flags to be reset to defaults. This may cause you to lose custom flags and could result in unforeseen complications or instance restarts. Because of this, it is recommended you apply these flags changes during a period of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting https://console.cloud.google.com/sql/instances. 2. Select the PostgreSQL instance for which you want to enable the database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add a Database Flag`, choose the flag `log_connections` from the drop-down menu and set the value as `on`. 6. Click `Save`. 7. Confirm the changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. Configure the `log_connections` database flag for every Cloud SQL PosgreSQL database instance using the below command. gcloud sql instances patch --database-flags log_connections=on **Note**: This command will overwrite all previously set database flags. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page. 3. Go to the `Configuration` card. 4. Under `Database flags`, check the value of `log_connections` flag to determine if it is configured as expected. **From Google Cloud CLI** 1. Ensure the below command returns `on` for every Cloud SQL PostgreSQL database instance: gcloud sql instances describe [INSTANCE_NAME] --format=json | jq '.settings.databaseFlags[] | select(.name==log_connections)|.value' In the output, database flags are listed under the `settings` as the collection `databaseFlags`.", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/postgres/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability and remove the instance from the Cloud SQL SLA. For information about these flags, see the Operational Guidelines. **Note**: Configuring the above flag does not require restarting the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/postgres/flags:https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-CONNECTIONS", + "DefaultValue": "By default `log_connections` is `off`." + } + ] + }, + { + "Id": "6.2.3", + "Description": "Ensure That the ‘Log_disconnections’ Database Flag for Cloud SQL PostgreSQL Instance Is Set to ‘On’", + "Checks": [ + "cloudsql_instance_postgres_log_disconnections_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.2 PostgreSQL Database", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Enabling the `log_disconnections` setting logs the end of each session, including the session duration.", + "RationaleStatement": "PostgreSQL does not log session details such as duration and session end by default. Enabling the `log_disconnections` setting will create log entries at the end of each session which can be useful in troubleshooting issues and determine any unusual activity across a time period. The `log_disconnections` and `log_connections` work hand in hand and generally, the pair would be enabled/disabled together. This recommendation is applicable to PostgreSQL database instances.", + "ImpactStatement": "Turning on logging will increase the required storage over time. Mismanaged logs may cause your storage costs to increase. Setting custom flags via command line on certain instances will cause all omitted flags to be reset to defaults. This may cause you to lose custom flags and could result in unforeseen complications or instance restarts. Because of this, it is recommended you apply these flags changes during a period of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the PostgreSQL instance where the database flag needs to be enabled. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add a Database Flag`, choose the flag `log_disconnections` from the drop-down menu and set the value as `on`. 6. Click `Save`. 7. Confirm the changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. Configure the `log_disconnections` database flag for every Cloud SQL PosgreSQL database instance using the below command: gcloud sql instances patch --database-flags log_disconnections=on **Note**: This command will overwrite all previously set database flags. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Go to the `Configuration` card. 4. Under `Database flags`, check the value of `log_disconnections` flag is configured as expected. **From Google Cloud CLI** 1. Ensure the below command returns `on` for every Cloud SQL PostgreSQL database instance: gcloud sql instances list --format=json | jq '.[].settings.databaseFlags[] | select(.name==log_disconnections)|.value' ", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/postgres/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag does not require restarting the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/postgres/flags:https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-DISCONNECTIONS", + "DefaultValue": "By default `log_disconnections` is off." + } + ] + }, + { + "Id": "6.2.4", + "Description": "Ensure ‘Log_statement’ Database Flag for Cloud SQL PostgreSQL Instance Is Set Appropriately", + "Checks": [ + "cloudsql_instance_postgres_log_statement_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.2 PostgreSQL Database", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "The value of `log_statement` flag determined the SQL statements that are logged. Valid values are: - `none` - `ddl` - `mod` - `all` The value `ddl` logs all data definition statements. The value `mod` logs all ddl statements, plus data-modifying statements. The statements are logged after a basic parsing is done and statement type is determined, thus this does not logs statements with errors. When using extended query protocol, logging occurs after an Execute message is received and values of the Bind parameters are included. A value of 'ddl' is recommended unless otherwise directed by your organization's logging policy.", + "RationaleStatement": "Auditing helps in forensic analysis. If `log_statement` is not set to the correct value, too many statements may be logged leading to issues in finding the relevant information from the logs, or too few statements may be logged with relevant information missing from the logs. Setting log_statement to align with your organization's security and logging policies facilitates later auditing and review of database activities. This recommendation is applicable to PostgreSQL database instances.", + "ImpactStatement": "Turning on logging will increase the required storage over time. Mismanaged logs may cause your storage costs to increase. Setting custom flags via command line on certain instances will cause all omitted flags to be reset to defaults. This may cause you to lose custom flags and could result in unforeseen complications or instance restarts. Because of this, it is recommended you apply these flags changes during a period of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the PostgreSQL instance for which you want to enable the database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add a Database Flag`, choose the flag `log_statement` from the drop-down menu and set appropriate value. 6. Click `Save` to save your changes. 7. Confirm your changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. Configure the `log_statement` database flag for every Cloud SQL PosgreSQL database instance using the below command. gcloud sql instances patch --database-flags log_statement= **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Go to `Configuration` card 4. Under `Database flags`, check the value of `log_statement` flag is set to appropriately. **From Google Cloud CLI** 1. Use the below command for every Cloud SQL PostgreSQL database instance to verify the value of `log_statement` gcloud sql instances list --format=json | jq '.[].settings.databaseFlags[] | select(.name==log_statement)|.value' ", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/postgres/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag does not require restarting the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/postgres/flags:https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-STATEMENT", + "DefaultValue": "`none`" + } + ] + }, + { + "Id": "6.2.5", + "Description": "Ensure that the ‘Log_min_messages’ Flag for a Cloud SQL PostgreSQL Instance is set at minimum to 'Warning'", + "Checks": [ + "cloudsql_instance_postgres_log_min_messages_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.2 PostgreSQL Database", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "The `log_min_messages` flag defines the minimum message severity level that is considered as an error statement. Messages for error statements are logged with the SQL statement. Valid values include (from lowest to highest severity) `DEBUG5`, `DEBUG4`, `DEBUG3`, `DEBUG2`, `DEBUG1`, `INFO`, `NOTICE`, `WARNING`, `ERROR`, `LOG`, `FATAL`, and `PANIC`. Each severity level includes the subsequent levels mentioned above. ERROR is considered the best practice setting. Changes should only be made in accordance with the organization's logging policy.", + "RationaleStatement": "Auditing helps in troubleshooting operational problems and also permits forensic analysis. If `log_min_messages` is not set to the correct value, messages may not be classified as error messages appropriately. Setting the threshold to 'Warning' will log messages for the most needed error messages. This recommendation is applicable to PostgreSQL database instances.", + "ImpactStatement": "Setting the threshold too low will might result in increased log storage size and length, making it difficult to find actual errors. Higher severity levels may cause errors needed to troubleshoot to not be logged. An organization will need to decide their own threshold for logging `log_min_messages` flag. **Note**: To effectively turn off logging failing statements, set this parameter to PANIC.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances) 2. Select the PostgreSQL instance for which you want to enable the database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add a Database Flag`, choose the flag `log_min_messages` from the drop-down menu and set appropriate value. 6. Click `Save` to save the changes. 7. Confirm the changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. Configure the `log_min_messages` database flag for every Cloud SQL PosgreSQL database instance using the below command. gcloud sql instances patch --database-flags log_min_messages= **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page. 3. Go to the `Configuration` card. 4. Under `Database flags`, check the value of `log_min_messages` flag is set to `warning` or higher (WARNING|ERROR|LOG|FATAL|PANIC). **From Google Cloud CLI** 1. Use the below command for every Cloud SQL PostgreSQL database instance to verify that the value of `log_min_messages` is set to `warning` or higher . gcloud sql instances describe [INSTANCE_NAME] --format=json | jq '.settings.databaseFlags[] | select(.name==log_min_messages)|.value' ", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/postgres/flags - to see if your instance will be restarted when this patch is submitted. **Note**: Some database flag settings can affect instance availability or stability and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag does not require restarting the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/postgres/flags:https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-MIN-MESSAGES", + "DefaultValue": "By default `log_min_messages` is `ERROR`." + } + ] + }, + { + "Id": "6.2.6", + "Description": "Ensure ‘Log_min_error_statement’ Database Flag for Cloud SQL PostgreSQL Instance Is Set to ‘Error’ or Stricter", + "Checks": [ + "cloudsql_instance_postgres_log_min_error_statement_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.2 PostgreSQL Database", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "The `log_min_error_statement` flag defines the minimum message severity level that are considered as an error statement. Messages for error statements are logged with the SQL statement. Valid values include (from lowest to highest severity) `DEBUG5`, `DEBUG4`, `DEBUG3`, `DEBUG2`, `DEBUG1`, `INFO`, `NOTICE`, `WARNING`, `ERROR`, `LOG`, `FATAL`, and `PANIC`. Each severity level includes the subsequent levels mentioned above. Ensure a value of `ERROR` or stricter is set.", + "RationaleStatement": "Auditing helps in troubleshooting operational problems and also permits forensic analysis. If `log_min_error_statement` is not set to the correct value, messages may not be classified as error messages appropriately. Considering general log messages as error messages would make is difficult to find actual errors and considering only stricter severity levels as error messages may skip actual errors to log their SQL statements. The `log_min_error_statement` flag should be set to `ERROR` or stricter. This recommendation is applicable to PostgreSQL database instances.", + "ImpactStatement": "Turning on logging will increase the required storage over time. Mismanaged logs may cause your storage costs to increase. Setting custom flags via command line on certain instances will cause all omitted flags to be reset to defaults. This may cause you to lose custom flags and could result in unforeseen complications or instance restarts. Because of this, it is recommended you apply these flags changes during a period of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the PostgreSQL instance for which you want to enable the database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add item`, choose the flag `log_min_error_statement` from the drop-down menu and set appropriate value. 6. Click `Save` to save your changes. 7. Confirm your changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. Configure the `log_min_error_statement` database flag for every Cloud SQL PosgreSQL database instance using the below command. gcloud sql instances patch --database-flags log_min_error_statement= **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Go to `Configuration` card 4. Under `Database flags`, check the value of `log_min_error_statement` flag is configured as to `ERROR` or stricter. **From Google Cloud CLI** 1. Use the below command for every Cloud SQL PostgreSQL database instance to verify the value of `log_min_error_statement` is set to `ERROR` or stricter. gcloud sql instances describe --format=json | jq '.[].settings.databaseFlags[] | select(.name==log_min_error_statement)|.value' In the output, database flags are listed under the `settings` as the collection `databaseFlags`.", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/postgres/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag does not require restarting the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/postgres/flags:https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-MIN-ERROR-STATEMENT", + "DefaultValue": "By default `log_min_error_statement` is `ERROR`." + } + ] + }, + { + "Id": "6.2.7", + "Description": "Ensure That the ‘Log_min_duration_statement’ Database Flag for Cloud SQL PostgreSQL Instance Is Set to '-1' (Disabled)", + "Checks": [ + "cloudsql_instance_postgres_log_min_duration_statement_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.2 PostgreSQL Database", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "The `log_min_duration_statement` flag defines the minimum amount of execution time of a statement in milliseconds where the total duration of the statement is logged. Ensure that `log_min_duration_statement` is disabled, i.e., a value of `-1` is set.", + "RationaleStatement": "Logging SQL statements may include sensitive information that should not be recorded in logs. This recommendation is applicable to PostgreSQL database instances.", + "ImpactStatement": "Turning on logging will increase the required storage over time. Mismanaged logs may cause your storage costs to increase. Setting custom flags via command line on certain instances will cause all omitted flags to be reset to defaults. This may cause you to lose custom flags and could result in unforeseen complications or instance restarts. Because of this, it is recommended you apply these flags changes during a period of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the PostgreSQL instance where the database flag needs to be enabled. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add item`, choose the flag `log_min_duration_statement` from the drop-down menu and set a value of `-1`. 6. Click `Save`. 7. Confirm the changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. List all Cloud SQL database instances using the following command: gcloud sql instances list 2. Configure the `log_min_duration_statement` flag for every Cloud SQL PosgreSQL database instance using the below command: gcloud sql instances patch --database-flags log_min_duration_statement=-1 **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page. 3. Go to the `Configuration` card. 4. Under `Database flags`, check that the value of `log_min_duration_statement` flag is set to `-1`. **From Google Cloud CLI** 1. Use the below command for every Cloud SQL PostgreSQL database instance to verify the value of `log_min_duration_statement` is set to `-1`. gcloud sql instances describe --format=json| jq '.settings.databaseFlags[] | select(.name==log_min_duration_statement)|.value' In the output, database flags are listed under the `settings` as the collection `databaseFlags`.", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/postgres/flags - to see if your instance will be restarted when this patch is submitted. **Note**: Some database flag settings can affect instance availability or stability and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag does not require restarting the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/postgres/flags:https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-MIN-DURATION-STATEMENT", + "DefaultValue": "By default `log_min_duration_statement` is `-1`." + } + ] + }, + { + "Id": "6.2.8", + "Description": "Ensure That 'cloudsql.enable_pgaudit' Database Flag for each Cloud Sql Postgresql Instance Is Set to 'on' For Centralized Logging", + "Checks": [ + "cloudsql_instance_postgres_enable_pgaudit_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.2 PostgreSQL Database", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Ensure `cloudsql.enable_pgaudit` database flag for Cloud SQL PostgreSQL instance is set to `on` to allow for centralized logging.", + "RationaleStatement": "As numerous other recommendations in this section consist of turning on flags for logging purposes, your organization will need a way to manage these logs. You may have a solution already in place. If you do not, consider installing and enabling the open source pgaudit extension within PostgreSQL and enabling its corresponding flag of `cloudsql.enable_pgaudit`. This flag and installing the extension enables database auditing in PostgreSQL through the open-source pgAudit extension. This extension provides detailed session and object logging to comply with government, financial, & ISO standards and provides auditing capabilities to mitigate threats by monitoring security events on the instance. Enabling the flag and settings later in this recommendation will send these logs to Google Logs Explorer so that you can access them in a central location. to This recommendation is applicable only to PostgreSQL database instances.", + "ImpactStatement": "Enabling the pgAudit extension can lead to increased data storage requirements and to ensure durability of pgAudit log records in the event of unexpected storage issues, it is recommended to enable the `Enable automatic storage increases` setting on the instance. Enabling flags via the command line will also overwrite all existing flags, so you should apply all needed flags in the CLI command. Also flags may require a restart of the server to be implemented or will break existing functionality so update your servers at a time of low usage.", + "RemediationProcedure": "**Initialize the pgAudit flag** **From Google Cloud Console** 1. Go to [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Overview` page. 3. Click `Edit`. 4. Scroll down and expand `Flags`. 5. To set a flag that has not been set on the instance before, click `Add item`. 6. Enter `cloudsql.enable_pgaudit` for the flag name and set the flag to `on`. 7. Click `Done`. 8. Click `Save` to update the configuration. 9. Confirm your changes under `Flags` on the `Overview` page. **From Google Cloud CLI** Run the below command by providing `` to enable `cloudsql.enable_pgaudit` flag. gcloud sql instances patch --database-flags cloudsql.enable_pgaudit=on Note: `RESTART` is required to get this configuration in effect. **Creating the extension** 1. Connect to the the server running PostgreSQL or through a SQL client of your choice. 2. Run the following command as a superuser. CREATE EXTENSION pgaudit; **Updating the previously created pgaudit.log flag for your Logging Needs** **From Console:** Note: there are multiple options here. This command will enable logging for all databases on a server. Please see the customizing database audit logging reference for more flag options. 1. Go to [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Overview` page. 3. Click `Edit`. 4. Scroll down and expand `Flags`. 5. To set a flag that has not been set on the instance before, click `Add item`. 6. Enter `pgaudit.log=all` for the flag name and set the flag to `on`. 7. Click `Done`. 8. Click `Save` to update the configuration. 9. Confirm your changes under `Flags` on the `Overview` page. **From Command Line:** Run the command gcloud sql instances patch --database-flags cloudsql.enable_pgaudit=on,pgaudit.log=all **Determine if logs are being sent to Logs Explorer** 1. From the Google Console home page, open the hamburger menu in the top left. 2. In the menu that pops open, scroll down to Logs Explorer under Operations. 3. In the query box, paste the following and search resource.type=cloudsql_database logName=projects//logs/cloudaudit.googleapis.com%2Fdata_access protoPayload.request.@type=type.googleapis.com/google.cloud.sql.audit.v1.PgAuditEntry If it returns any log sources, they are correctly setup.", + "AuditProcedure": "**Determining if the pgAudit Flag is set to 'on'** **From Google Cloud Console** 1. Go to [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Overview` page. 3. Click `Edit`. 4. Scroll down and expand `Flags`. 5. Ensure that `cloudsql.enable_pgaudit` flag is set to `on`. **From Google Cloud CLI** Run the command by providing ``. Ensure the value of the flag is `on`. gcloud sql instances describe --format=json | jq '.settings|.|.databaseFlags[]|select(.name==cloudsql.enable_pgaudit)|.value' **Determine if the pgAudit extension is installed** 1. Connect to the the server running PostgreSQL or through a SQL client of your choice. 2. Run the following command SELECT * FROM pg_extension; 3. If pgAudit is in this list. If so, it is installed. **Determine if Data Access Audit logs are enabled for your project and have sufficient privileges** 1. From the homepage open the hamburger menu in the top left. 2. Scroll down to `IAM & Admin`and hover over it. 3. In the menu that opens up, select `Audit Logs` 4. In the middle of the page, in the search box next to `filter` search for `Cloud Composer API` 5. Select it, and ensure that both 'Admin Read' and 'Data Read' are checked. **Determine if logs are being sent to Logs Explorer** 1. From the Google Console home page, open the hamburger menu in the top left. 2. In the menu that pops open, scroll down to Logs Explorer under Operations. 3. In the query box, paste the following and search resource.type=cloudsql_database logName=projects//logs/cloudaudit.googleapis.com%2Fdata_access protoPayload.request.@type=type.googleapis.com/google.cloud.sql.audit.v1.PgAuditEntry 4. If it returns any log sources, they are correctly setup.", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/postgres/flags - to see if your instance will be restarted when this patch is submitted. Note: Configuring the 'cloudsql.enable_pgaudit' database flag requires restarting the Cloud SQL PostgreSQL instance.", + "References": "https://cloud.google.com/sql/docs/postgres/flags#list-flags-postgres:https://cloud.google.com/sql/docs/postgres/pg-audit#enable-auditing-flag:https://cloud.google.com/sql/docs/postgres/pg-audit#customizing-database-audit-logging:https://cloud.google.com/logging/docs/audit/configure-data-access#config-console-enable", + "DefaultValue": "By default `cloudsql.enable_pgaudit` database flag is set to `off` and the extension is not enabled." + } + ] + }, + { + "Id": "6.3.1", + "Description": "Ensure 'external scripts enabled' Database Flag for Cloud SQL SQL Server Instance Is Set to 'off'", + "Checks": [ + "cloudsql_instance_sqlserver_external_scripts_enabled_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.3 SQL Server", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to set `external scripts enabled` database flag for Cloud SQL SQL Server instance to `off`", + "RationaleStatement": "`external scripts enabled` enable the execution of scripts with certain remote language extensions. This property is OFF by default. When Advanced Analytics Services is installed, setup can optionally set this property to true. As the External Scripts Enabled feature allows scripts external to SQL such as files located in an R library to be executed, which could adversely affect the security of the system, hence this should be disabled. This recommendation is applicable to SQL Server database instances.", + "ImpactStatement": "Setting custom flags via command line on certain instances will cause all omitted flags to be reset to defaults. This may cause you to lose custom flags and could result in unforeseen complications or instance restarts. Because of this, it is recommended you apply these flags changes during a period of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the SQL Server instance for which you want to enable to database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add item`, choose the flag `external scripts enabled` from the drop-down menu, and set its value to `off`. 6. Click `Save` to save your changes. 7. Confirm your changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. Configure the `external scripts enabled` database flag for every Cloud SQL SQL Server database instance using the below command. gcloud sql instances patch --database-flags external scripts enabled=off **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Ensure the database flag `external scripts enabled` that has been set is listed under the `Database flags` section. **From Google Cloud CLI** 1. Ensure the below command returns `off` for every Cloud SQL SQL Server database instance gcloud sql instances describe --format=json | jq '.settings.databaseFlags[] | select(.name==external scripts enabled)|.value' In the output, database flags are listed under the `settings` as the collection `databaseFlags`.", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/sqlserver/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability, and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag restarts the Cloud SQL instance.", + "References": "https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/external-scripts-enabled-server-configuration-option?view=sql-server-ver15:https://cloud.google.com/sql/docs/sqlserver/flags:https://docs.microsoft.com/en-us/sql/advanced-analytics/concepts/security?view=sql-server-ver15:https://www.stigviewer.com/stig/ms_sql_server_2016_instance/2018-03-09/finding/V-79347", + "DefaultValue": "By default `external scripts enabled` is `off`" + } + ] + }, + { + "Id": "6.3.2", + "Description": "Ensure 'cross db ownership chaining' Database Flag for Cloud SQL SQL Server Instance Is Set to 'off'", + "Checks": [ + "cloudsql_instance_sqlserver_cross_db_ownership_chaining_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.3 SQL Server", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to set `cross db ownership chaining` database flag for Cloud SQL SQL Server instance to `off`. This flag is deprecated for all SQL Server versions in CGP. Going forward, you can't set its value to on. However, if you have this flag enabled, we strongly recommend that you either remove the flag from your database or set it to off. For cross-database access, use the [Microsoft tutorial for signing stored procedures with a certificate](https://learn.microsoft.com/en-us/sql/relational-databases/tutorial-signing-stored-procedures-with-a-certificate?view=sql-server-ver16).", + "RationaleStatement": "Use the `cross db ownership` for chaining option to configure cross-database ownership chaining for an instance of Microsoft SQL Server. This server option allows you to control cross-database ownership chaining at the database level or to allow cross-database ownership chaining for all databases. Enabling `cross db ownership` is not recommended unless all of the databases hosted by the instance of SQL Server must participate in cross-database ownership chaining and you are aware of the security implications of this setting. This recommendation is applicable to SQL Server database instances.", + "ImpactStatement": "Updating flags may cause the database to restart. This may cause it to unavailable for a short amount of time, so this is best done at a time of low usage. You should also determine if the tables in your databases reference another table without using credentials for that database, as turning off cross database ownership will break this relationship.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the SQL Server instance for which you want to enable to database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add item`, choose the flag `cross db ownership chaining` from the drop-down menu, and set its value to `off`. 6. Click `Save`. 7. Confirm the changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. Configure the `cross db ownership chaining` database flag for every Cloud SQL SQL Server database instance using the below command: gcloud sql instances patch --database-flags cross db ownership chaining=off **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**NOTE:** This flag is deprecated for all SQL Server versions. Going forward, you can't set its value to on. However, if you have this flag enabled it should be removed from your database or set to off. **From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console. 2. Select the instance to open its `Instance Overview` page 3. Ensure the database flag `cross db ownership chaining` that has been set is listed under the `Database flags` section. **From Google Cloud CLI** 1. Ensure the below command returns `off` for every Cloud SQL SQL Server database instance: gcloud sql instances describe --format=json | jq '.settings.databaseFlags[] | select(.name==cross db ownership chaining)|.value' In the output, database flags are listed under the `settings` as the collection `databaseFlags`.", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/sqlserver/flags - to see if your instance will be restarted when this patch is submitted. **Note**: Some database flag settings can affect instance availability or stability, and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag does not restart the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/sqlserver/flags:https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/cross-db-ownership-chaining-server-configuration-option?view=sql-server-ver15", + "DefaultValue": "This flag is deprecated for all SQL Server versions. Going forward, you can't set its value to on." + } + ] + }, + { + "Id": "6.3.3", + "Description": "Ensure 'user Connections' Database Flag for Cloud SQL SQL Server Instance Is Set to a Non-limiting Value", + "Checks": [ + "cloudsql_instance_sqlserver_user_connections_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.3 SQL Server", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to check the `user connections` for a Cloud SQL SQL Server instance to ensure that it is not artificially limiting connections.", + "RationaleStatement": "The `user connections` option specifies the maximum number of simultaneous user connections that are allowed on an instance of SQL Server. The actual number of user connections allowed also depends on the version of SQL Server that you are using, and also the limits of your application or applications and hardware. SQL Server allows a maximum of 32,767 user connections. Because user connections is by default a self-configuring value, with SQL Server adjusting the maximum number of user connections automatically as needed, up to the maximum value allowable. For example, if only 10 users are logged in, 10 user connection objects are allocated. In most cases, you do not have to change the value for this option. The default is 0, which means that the maximum (32,767) user connections are allowed. However if there is a number defined here that limits connections, SQL Server will not allow anymore above this limit. If the connections are at the limit, any new requests will be dropped, potentially causing lost data or outages for those using the database.", + "ImpactStatement": "Setting custom flags via command line on certain instances will cause all omitted flags to be reset to defaults. This may cause you to lose custom flags and could result in unforeseen complications or instance restarts. Because of this, it is recommended you apply these flags changes during a period of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the SQL Server instance for which you want to enable to database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add item`, choose the flag `user connections` from the drop-down menu, and set its value to your organization recommended value. 6. Click `Save` to save your changes. 7. Confirm your changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. Configure the `user connections` database flag for every Cloud SQL SQL Server database instance using the below command. gcloud sql instances patch --database-flags user connections=[0-32,767] **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Ensure the database flag `user connections` listed under the `Database flags` section is 0. **From Google Cloud CLI** 1. Ensure the below command returns a value of 0, for every Cloud SQL SQL Server database instance. gcloud sql instances describe --format=json | jq '.settings.databaseFlags[] | select(.name==user connections)|.value' ", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/sqlserver/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability, and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag restarts the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/sqlserver/flags:https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-user-connections-server-configuration-option?view=sql-server-ver15:https://www.stigviewer.com/stig/ms_sql_server_2016_instance/2018-03-09/finding/V-79119", + "DefaultValue": "By default `user connections` is set to '0' which does not limit the number of connections, giving the server free reign to facilitate a max of 32,767 connections." + } + ] + }, + { + "Id": "6.3.4", + "Description": "Ensure 'user options' Database Flag for Cloud SQL SQL Server Instance Is Not Configured", + "Checks": [ + "cloudsql_instance_sqlserver_user_options_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.3 SQL Server", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "The `user options` option specifies global defaults for all users. A list of default query processing options is established for the duration of a user's work session. The user options option allows you to change the default values of the SET options (if the server's default settings are not appropriate). A user can override these defaults by using the SET statement. You can configure user options dynamically for new logins. After you change the setting of user options, new login sessions use the new setting; current login sessions are not affected. This recommendation is applicable to SQL Server database instances.", + "RationaleStatement": "It is recommended that, `user options` database flag for Cloud SQL SQL Server instance should not be configured. A user can override these defaults set with `user options` by using the SET statement. Some of these features/options could adversely affect the security of the system if enabled.", + "ImpactStatement": "Setting custom flags via command line on certain instances will cause all omitted flags to be reset to defaults. This may cause you to lose custom flags and could result in unforeseen complications or instance restarts. Because of this, it is recommended you apply these flags changes during a period of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the SQL Server instance for which you want to enable to database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. Click the X next `user options` flag shown 6. Click `Save` to save your changes. 7. Confirm your changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. List all Cloud SQL database Instances gcloud sql instances list 2. Clear the `user options` database flag for every Cloud SQL SQL Server database instance using either of the below commands. Clearing all flags to their default value gcloud sql instances patch --clear-database-flags OR To clear only `user options` database flag, configure the database flag by overriding the `user options`. Exclude `user options` flag and its value, and keep all other flags you want to configure. gcloud sql instances patch --database-flags [FLAG1=VALUE1,FLAG2=VALUE2] **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Ensure the database flag `user options` that has been set is not listed under the `Database flags` section. **From Google Cloud CLI** 1. Ensure the below command returns empty result for every Cloud SQL SQL Server database instance gcloud sql instances describe --format=json | jq '.settings.databaseFlags[] | select(.name==user options)|.value' In the output, database flags are listed under the `settings` as the collection `databaseFlags`.", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/sqlserver/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability, and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag does not restart the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/sqlserver/flags:https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-user-options-server-configuration-option?view=sql-server-ver15:https://www.stigviewer.com/stig/ms_sql_server_2016_instance/2018-03-09/finding/V-79335", + "DefaultValue": "By default 'user options' is not configured." + } + ] + }, + { + "Id": "6.3.5", + "Description": "Ensure 'remote access' Database Flag for Cloud SQL SQL Server Instance Is Set to 'off'", + "Checks": [ + "cloudsql_instance_sqlserver_remote_access_flag" + ], + "Attributes": [ + { + "Section": "6 Cloud SQL Database Services", + "SubSection": "6.3 SQL Server", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to set `remote access` database flag for Cloud SQL SQL Server instance to `off`.", + "RationaleStatement": "The `remote access` option controls the execution of stored procedures from local or remote servers on which instances of SQL Server are running. This default value for this option is 1. This grants permission to run local stored procedures from remote servers or remote stored procedures from the local server. To prevent local stored procedures from being run from a remote server or remote stored procedures from being run on the local server, this must be disabled. The Remote Access option controls the execution of local stored procedures on remote servers or remote stored procedures on local server. 'Remote access' functionality can be abused to launch a Denial-of-Service (DoS) attack on remote servers by off-loading query processing to a target, hence this should be disabled. This recommendation is applicable to SQL Server database instances.", + "ImpactStatement": "Setting custom flags via command line on certain instances will cause all omitted flags to be reset to defaults. This may cause you to lose custom flags and could result in unforeseen complications or instance restarts. Because of this, it is recommended you apply these flags changes during a period of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the SQL Server instance for which you want to enable to database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add item`, choose the flag `remote access` from the drop-down menu, and set its value to `off`. 6. Click `Save` to save your changes. 7. Confirm your changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. Configure the `remote access` database flag for every Cloud SQL SQL Server database instance using the below command gcloud sql instances patch --database-flags remote access=off **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Ensure the database flag `remote access` that has been set is listed under the `Database flags` section. **From Google Cloud CLI** 1. Ensure the below command returns `off` for every Cloud SQL SQL Server database instance gcloud sql instances describe --format=json | jq '.settings.databaseFlags[] | select(.name==remote access)|.value' In the output, database flags are listed under the `settings` as the collection `databaseFlags`.", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/sqlserver/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability, and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag restarts the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/sqlserver/flags:https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-remote-access-server-configuration-option?view=sql-server-ver15:https://www.stigviewer.com/stig/ms_sql_server_2016_instance/2018-03-09/finding/V-79337", + "DefaultValue": "By default 'remote access' is 'on'." + } + ] + }, + { + "Id": "6.3.6", + "Description": "Ensure '3625 (trace flag)' Database Flag for all Cloud SQL SQL Server Instances Is Set to 'on'", + "Checks": [ + "cloudsql_instance_sqlserver_trace_flag" + ], + "Attributes": [ + { + "Section": "6.3", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to set `3625 (trace flag)` database flag for Cloud SQL SQL Server instance to `on`.", + "RationaleStatement": "Microsoft SQL Trace Flags are frequently used to diagnose performance issues or to debug stored procedures or complex computer systems, but they may also be recommended by Microsoft Support to address behavior that is negatively impacting a specific workload. All documented trace flags and those recommended by Microsoft Support are fully supported in a production environment when used as directed. `3625(trace log)` Limits the amount of information returned to users who are not members of the sysadmin fixed server role, by masking the parameters of some error messages using '******'. Setting this in a Google Cloud flag for the instance allows for security through obscurity and prevents the disclosure of sensitive information, hence this is recommended to set this flag globally to on to prevent the flag having been left off, or changed by bad actors. This recommendation is applicable to SQL Server database instances.", + "ImpactStatement": "Changing flags on a database may cause it to be restarted. The best time to do this is at a time where there is low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the SQL Server instance for which you want to enable to database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. To set a flag that has not been set on the instance before, click `Add item`, choose the flag `3625` from the drop-down menu, and set its value to `on`. 6. Click `Save` to save your changes. 7. Confirm your changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. Configure the `3625` database flag for every Cloud SQL SQL Server database instance using the below command. gcloud sql instances patch --database-flags 3625=on **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags you want set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Ensure the database flag `3625` that has been set is listed under the `Database flags` section. **From Google Cloud CLI** 1. Ensure the below command returns `on` for every Cloud SQL SQL Server database instance gcloud sql instances describe --format=json | jq '.settings.databaseFlags[] | select(.name==3625)|.value' ", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/sqlserver/flags - to see if your instance will be restarted when this patch is submitted. **Note**: some database flag settings can affect instance availability or stability, and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag restarts the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/sqlserver/flags:https://docs.microsoft.com/en-us/sql/t-sql/database-console-commands/dbcc-traceon-trace-flags-transact-sql?view=sql-server-ver15#trace-flags:https://github.com/ktaranov/sqlserver-kit/blob/master/SQL%20Server%20Trace%20Flag.md", + "DefaultValue": "MS SQL Server implementations by default have trace flags, including the '3625' flag, turned off, as they are used for logging purposes." + } + ] + }, + { + "Id": "6.3.7", + "Description": "Ensure 'contained database authentication' Database Flag for Cloud SQL SQL Server Instance Is Set to 'off'", + "Checks": [ + "cloudsql_instance_sqlserver_contained_database_authentication_flag" + ], + "Attributes": [ + { + "Section": "6.3", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "A contained database includes all database settings and metadata required to define the database and has no configuration dependencies on the instance of the Database Engine where the database is installed. Users can connect to the database without authenticating a login at the Database Engine level. Isolating the database from the Database Engine makes it possible to easily move the database to another instance of SQL Server. Contained databases have some unique threats that should be understood and mitigated by SQL Server Database Engine administrators. Most of the threats are related to the USER WITH PASSWORD authentication process, which moves the authentication boundary from the Database Engine level to the database level, hence this is recommended not to enable this flag. This recommendation is applicable to SQL Server database instances.", + "RationaleStatement": "When contained databases are enabled, database users with the ALTER ANY USER permission, such as members of the db_owner and db_accessadmin database roles, can grant access to databases and by doing so, grant access to the instance of SQL Server. This means that control over access to the server is no longer limited to members of the sysadmin and securityadmin fixed server role, and logins with the server level CONTROL SERVER and ALTER ANY LOGIN permission. It is recommended to set `contained database authentication` database flag for Cloud SQL on the SQL Server instance to `off`.", + "ImpactStatement": "When `contained database authentication` is off (0) for the instance, contained databases cannot be created, or attached to the Database Engine. Setting custom flags via command line on certain instances will cause all omitted flags to be reset to defaults. This may cause you to lose custom flags and could result in unforeseen complications or instance restarts. Because of this, it is recommended you apply these flags changes during a period of low usage.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the SQL Server instance for which you want to enable to database flag. 3. Click `Edit`. 4. Scroll down to the `Flags` section. 5. If the flag `contained database authentication` is present and its value is set to 'on', then change it to 'off'. 6. Click `Save`. 7. Confirm the changes under `Flags` on the Overview page. **From Google Cloud CLI** 1. If any Cloud SQL for SQL Server instance has the database flag `contained database authentication` set to 'on', then change it to 'off' using the below command: gcloud sql instances patch --database-flags contained database authentication=off **Note**: This command will overwrite all database flags previously set. To keep those and add new ones, include the values for all flags to be set on the instance; any flag not specifically included is set to its default value. For flags that do not take a value, specify the flag name followed by an equals sign (=).", + "AuditProcedure": "**From Google Cloud Console** 1. Go to the Cloud SQL Instances page in the Google Cloud Console by visiting [https://console.cloud.google.com/sql/instances](https://console.cloud.google.com/sql/instances). 2. Select the instance to open its `Instance Overview` page 3. Under the 'Database flags' section, if the database flag `contained database authentication` is present, then ensure that it is set to 'off'. **From Google Cloud CLI** 1. Ensure the below command returns `off` for any Cloud SQL for SQL Server database instance. gcloud sql instances describe --format=json | jq '.settings.databaseFlags[] | select(.name==contained database authentication)|.value' ", + "AdditionalInformation": "WARNING: This patch modifies database flag values, which may require your instance to be restarted. Check the list of supported flags - https://cloud.google.com/sql/docs/sqlserver/flags - to see if your instance will be restarted when this patch is submitted. **Note**: Some database flag settings can affect instance availability or stability, and remove the instance from the Cloud SQL SLA. For information about these flags, see Operational Guidelines. **Note**: Configuring the above flag does not restart the Cloud SQL instance.", + "References": "https://cloud.google.com/sql/docs/sqlserver/flags:https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/contained-database-authentication-server-configuration-option?view=sql-server-ver15:https://docs.microsoft.com/en-us/sql/relational-databases/databases/security-best-practices-with-contained-databases?view=sql-server-ver15", + "DefaultValue": "" + } + ] + }, + { + "Id": "7.1", + "Description": "Ensure That BigQuery Datasets Are Not Anonymously or Publicly Accessible", + "Checks": [ + "bigquery_dataset_public_access" + ], + "Attributes": [ + { + "Section": "7 BigQuery", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended that the IAM policy on BigQuery datasets does not allow anonymous and/or public access.", + "RationaleStatement": "Granting permissions to `allUsers` or `allAuthenticatedUsers` allows anyone to access the dataset. Such access might not be desirable if sensitive data is being stored in the dataset. Therefore, ensure that anonymous and/or public access to a dataset is not allowed.", + "ImpactStatement": "The dataset is not publicly accessible. Explicit modification of IAM privileges would be necessary to make them publicly accessible.", + "RemediationProcedure": "**From Google Cloud Console** 1. Go to `BigQuery` by visiting: [https://console.cloud.google.com/bigquery](https://console.cloud.google.com/bigquery). 2. Select the dataset from 'Resources'. 3. Click `SHARING` near the right side of the window and select `Permissions`. 4. Review each attached role. 5. Click the delete icon for each member `allUsers` or `allAuthenticatedUsers`. On the popup click `Remove`. **From Google Cloud CLI** List the name of all datasets. bq ls Retrieve the data set details: bq show --format=prettyjson PROJECT_ID:DATASET_NAME > PATH_TO_FILE In the access section of the JSON file, update the dataset information to remove all roles containing `allUsers` or `allAuthenticatedUsers`. Update the dataset: bq update --source PATH_TO_FILE PROJECT_ID:DATASET_NAME **Prevention:** You can prevent Bigquery dataset from becoming publicly accessible by setting up the `Domain restricted sharing` organization policy at: https://console.cloud.google.com/iam-admin/orgpolicies/iam-allowedPolicyMemberDomains .", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `BigQuery` by visiting: [https://console.cloud.google.com/bigquery](https://console.cloud.google.com/bigquery). 2. Select a dataset from `Resources`. 3. Click `SHARING` near the right side of the window and select `Permissions`. 4. Validate that none of the attached roles contain `allUsers` or `allAuthenticatedUsers`. **From Google Cloud CLI** List the name of all datasets. bq ls Retrieve each dataset details using the following command: bq show PROJECT_ID:DATASET_NAME Ensure that `allUsers` and `allAuthenticatedUsers` have not been granted access to the dataset.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/bigquery/docs/dataset-access-controls", + "DefaultValue": "By default, BigQuery datasets are not publicly accessible." + } + ] + }, + { + "Id": "7.2", + "Description": "Ensure That All BigQuery Tables Are Encrypted With Customer-Managed Encryption Key (CMEK)", + "Checks": [ + "bigquery_table_cmk_encryption" + ], + "Attributes": [ + { + "Section": "7 BigQuery", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "BigQuery by default encrypts the data as rest by employing `Envelope Encryption` using Google managed cryptographic keys. The data is encrypted using the `data encryption keys` and data encryption keys themselves are further encrypted using `key encryption keys`. This is seamless and do not require any additional input from the user. However, if you want to have greater control, Customer-managed encryption keys (CMEK) can be used as encryption key management solution for BigQuery Data Sets. If CMEK is used, the CMEK is used to encrypt the data encryption keys instead of using google-managed encryption keys.", + "RationaleStatement": "BigQuery by default encrypts the data as rest by employing `Envelope Encryption` using Google managed cryptographic keys. This is seamless and does not require any additional input from the user. For greater control over the encryption, customer-managed encryption keys (CMEK) can be used as encryption key management solution for BigQuery tables. The CMEK is used to encrypt the data encryption keys instead of using google-managed encryption keys. BigQuery stores the table and CMEK association and the encryption/decryption is done automatically. Applying the Default Customer-managed keys on BigQuery data sets ensures that all the new tables created in the future will be encrypted using CMEK but existing tables need to be updated to use CMEK individually. Note: Google does not store your keys on its servers and cannot access your protected data unless you provide the key. This also means that if you forget or lose your key, there is no way for Google to recover the key or to recover any data encrypted with the lost key. ", + "ImpactStatement": "Using Customer-managed encryption keys (CMEK) will incur additional labor-hour investment to create, protect, and manage the keys.", + "RemediationProcedure": "**From Google Cloud CLI** Use the following command to copy the data. The source and the destination needs to be same in case copying to the original table. bq cp --destination_kms_key source_dataset.source_table destination_dataset.destination_table ", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `Analytics` 2. Go to `BigQuery` 3. Under `SQL Workspace`, select the project 4. Select Data Set, select the table 5. Go to `Details` tab 6. Under `Table info`, verify `Customer-managed key` is present. 7. Repeat for each table in all data sets for all projects. **From Google Cloud CLI** List all dataset names bq ls Use the following command to view the table details. Verify the `kmsKeyName` is present. bq show ", + "AdditionalInformation": "", + "References": "https://cloud.google.com/bigquery/docs/customer-managed-encryption", + "DefaultValue": "Google Managed keys are used as `key encryption keys`." + } + ] + }, + { + "Id": "7.3", + "Description": "Ensure That a Default Customer-Managed Encryption Key (CMEK) Is Specified for All BigQuery Data Sets", + "Checks": [ + "bigquery_dataset_cmk_encryption" + ], + "Attributes": [ + { + "Section": "7 BigQuery", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "BigQuery by default encrypts the data as rest by employing `Envelope Encryption` using Google managed cryptographic keys. The data is encrypted using the `data encryption keys` and data encryption keys themselves are further encrypted using `key encryption keys`. This is seamless and do not require any additional input from the user. However, if you want to have greater control, Customer-managed encryption keys (CMEK) can be used as encryption key management solution for BigQuery Data Sets.", + "RationaleStatement": "BigQuery by default encrypts the data as rest by employing `Envelope Encryption` using Google managed cryptographic keys. This is seamless and does not require any additional input from the user. For greater control over the encryption, customer-managed encryption keys (CMEK) can be used as encryption key management solution for BigQuery Data Sets. Setting a Default Customer-managed encryption key (CMEK) for a data set ensure any tables created in future will use the specified CMEK if none other is provided. Note: Google does not store your keys on its servers and cannot access your protected data unless you provide the key. This also means that if you forget or lose your key, there is no way for Google to recover the key or to recover any data encrypted with the lost key. ", + "ImpactStatement": "Using Customer-managed encryption keys (CMEK) will incur additional labor-hour investment to create, protect, and manage the keys.", + "RemediationProcedure": "**From Google Cloud CLI** The default CMEK for existing data sets can be updated by specifying the default key in the `EncryptionConfiguration.kmsKeyName` field when calling the `datasets.insert` or `datasets.patch` methods", + "AuditProcedure": "**From Google Cloud Console** 1. Go to `Analytics` 2. Go to `BigQuery` 3. Under `Analysis` click on `SQL Workspaces`, select the project 4. Select Data Set 5. Ensure `Customer-managed key` is present under `Dataset info` section. 6. Repeat for each data set in all projects. **From Google Cloud CLI** List all dataset names bq ls Use the following command to view each dataset details. bq show Verify the `kmsKeyName` is present.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/bigquery/docs/customer-managed-encryption", + "DefaultValue": "Google Managed keys are used as `key encryption keys`." + } + ] + }, + { + "Id": "7.4", + "Description": "Ensure all data in BigQuery has been classified", + "Checks": [], + "Attributes": [ + { + "Section": "7 BigQuery", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "BigQuery tables can contain sensitive data that for security purposes should be discovered, monitored, classified, and protected. Google Cloud's Sensitive Data Protection tools can automatically provide data classification of all BigQuery data across an organization.", + "RationaleStatement": "Using a cloud service or 3rd party software to continuously monitor and automate the process of data discovery and classification for BigQuery tables is an important part of protecting the data. Sensitive Data Protection is a fully managed data protection and data privacy platform that uses machine learning and pattern matching to discover and classify sensitive data in Google Cloud.", + "ImpactStatement": "There is a cost associated with using Sensitive Data Protection. There is also typically a cost associated with 3rd party tools that perform similar processes and protection.", + "RemediationProcedure": "**Enable profiling:** 1. Go to Cloud DLP by visiting https://console.cloud.google.com/dlp/landing/dataProfiles/configurations 1. Click Create Configuration 1. For projects follow https://cloud.google.com/dlp/docs/profile-project. For organizations or folders follow https://cloud.google.com/dlp/docs/profile-org-folder **Review findings:** - Columns or tables with high data risk have evidence of sensitive information without additional protections. To lower the data risk score, consider doing the following: - For columns containing sensitive data, apply a BigQuery policy tag to restrict access to accounts with specific access rights. - De-identify the raw sensitive data using de-identification techniques like masking and tokenization. **Incorporate findings into your security and governance operations:** - Enable sending findings into your security and posture services. You can publish data profiles to Security Command Center and Chronicle. - Automate remediation or enable alerting of new or changed data risk with Pub/Sub.", + "AuditProcedure": "1. Go to Cloud DLP by visiting https://console.cloud.google.com/dlp/landing/dataProfiles/configurations. 2. Verify there is a discovery scan configuration either for the organization or project.", + "AdditionalInformation": "", + "References": "https://cloud.google.com/dlp/docs/data-profiles:https://cloud.google.com/dlp/docs/analyze-data-profiles:https://cloud.google.com/dlp/docs/data-profiles-remediation:https://cloud.google.com/dlp/docs/send-profiles-to-scc:https://cloud.google.com/dlp/docs/profile-org-folder#chronicle:https://cloud.google.com/dlp/docs/profile-org-folder#publish-pubsub", + "DefaultValue": "" + } + ] + }, + { + "Id": "8.1", + "Description": "Ensure that Dataproc Cluster is encrypted using Customer-Managed Encryption Key", + "Checks": [ + "dataproc_encrypted_with_cmks_disabled" + ], + "Attributes": [ + { + "Section": "8 Dataproc", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "When you use Dataproc, cluster and job data is stored on Persistent Disks (PDs) associated with the Compute Engine VMs in your cluster and in a Cloud Storage staging bucket. This PD and bucket data is encrypted using a Google-generated data encryption key (DEK) and key encryption key (KEK). The CMEK feature allows you to create, use, and revoke the key encryption key (KEK). Google still controls the data encryption key (DEK).", + "RationaleStatement": "Cloud services offer the ability to protect data related to those services using encryption keys managed by the customer within Cloud KMS. These encryption keys are called customer-managed encryption keys (CMEK). When you protect data in Google Cloud services with CMEK, the CMEK key is within your control.", + "ImpactStatement": "Using Customer Managed Keys involves additional overhead in maintenance by administrators.", + "RemediationProcedure": "**From Google Cloud Console** 1. Login to the GCP Console and navigate to the Dataproc Cluster page by visiting https://console.cloud.google.com/dataproc/clusters. 1. Select the project from the projects dropdown list. 1. On the `Dataproc Cluster` page, click on the `Create Cluster` to create a new cluster with Customer managed encryption keys. 1. On `Create a cluster` page, perform below steps: - Inside `Set up cluster` section perform below steps: -In the `Name` textbox, provide a name for your cluster. - From `Location` select the location in which you want to deploy a cluster. - Configure other configurations as per your requirements. - Inside `Configure Nodes` and `Customize cluster` section configure the settings as per your requirements. - Inside `Manage security` section, perform below steps: - From `Encryption`, select `Customer-managed key`. - Select a customer-managed key from dropdown list. - Ensure that the selected KMS Key have Cloud KMS CryptoKey Encrypter/Decrypter role assign to Dataproc Cluster service account (serviceAccount:service-@compute-system.iam.gserviceaccount.com). - Click on `Create` to create a cluster. - Once the cluster is created migrate all your workloads from the older cluster to the new cluster and delete the old cluster by performing the below steps: - On the `Clusters` page, select the old cluster and click on `Delete cluster`. - On the `Confirm deletion` window, click on `Confirm` to delete the cluster. - Repeat step above for other Dataproc clusters available in the selected project. - Change the project from the project dropdown list and repeat the remediation procedure for other Dataproc clusters available in other projects. **From Google Cloud CLI** Before creating cluster ensure that the selected KMS Key have Cloud KMS CryptoKey Encrypter/Decrypter role assign to Dataproc Cluster service account (serviceAccount:service-@compute-system.iam.gserviceaccount.com). Run clusters create command to create new cluster with customer-managed key: gcloud dataproc clusters create --region=us-central1 --gce-pd-kms-key= The above command will create a new cluster in the selected region. Once the cluster is created migrate all your workloads from the older cluster to the new cluster and Run clusters delete command to delete cluster: gcloud dataproc clusters delete --region=us-central1 Repeat step no. 1 to create a new Dataproc cluster. Change the project by running the below command and repeat the remediation procedure for other projects: gcloud config set project ", + "AuditProcedure": "**From Google Cloud Console** 1. Login to the GCP Console and navigate to the Dataproc Cluster page by visiting https://console.cloud.google.com/dataproc/clusters. 1. Select the project from the project dropdown list. 1. On the `Dataproc Clusters` page, select the cluster and click on the Name attribute value that you want to examine. 1. On the `details` page, select the `Configurations` tab. 1. On the `Configurations` tab, check the `Encryption type` configuration attribute value. If the value is set to `Google-managed key`, then Dataproc Cluster is not encrypted with Customer managed encryption keys. Repeat step no. 3 - 5 for other Dataproc Clusters available in the selected project. 6. Change the project from the project dropdown list and repeat the audit procedure for other projects. **From Google Cloud CLI** 1. Run clusters list command to list all the Dataproc Clusters available in the region: gcloud dataproc clusters list --region='us-central1' 2. Run clusters describe command to get the key details of the selected cluster: gcloud dataproc clusters describe --region=us-central1 --flatten=config.encryptionConfig.gcePdKmsKeyName 3. If the above command output return null, then the selected cluster is not encrypted with Customer managed encryption keys. 4. Repeat step no. 2 and 3 for other Dataproc Clusters available in the selected region. Change the region by updating --region and repeat step no. 2 for other clusters available in the project. Change the project by running the below command and repeat the audit procedure for other Dataproc clusters available in other projects: gcloud config set project ", + "AdditionalInformation": "", + "References": "https://cloud.google.com/docs/security/encryption/default-encryption", + "DefaultValue": "" + } + ] + } + ] +} diff --git a/util/generate_compliance_json_from_csv_for_cis15.py b/util/generate_compliance_json_from_csv_for_cis15.py index d754323ee9..ed04202d66 100644 --- a/util/generate_compliance_json_from_csv_for_cis15.py +++ b/util/generate_compliance_json_from_csv_for_cis15.py @@ -4,7 +4,7 @@ import sys # Convert a CSV file following the CIS 1.5 AWS benchmark into a Prowler v3.0 Compliance JSON file # CSV fields: -# Id, Title,Checks,Attributes_Section,Attributes_Level,Attributes_AssessmentStatus,Attributes_Description,Attributes_RationalStatement,Attributes_ImpactStatement,Attributes_RemediationProcedure,Attributes_AuditProcedure,Attributes_AdditionalInformation,Attributes_References +# ID Title Check Section # SubSection Profile Assessment Status Description Rationale Statement Impact Statement Remediation Procedure Audit Procedure Additional Information References Default Value # get the CSV filename to convert from file_name = sys.argv[1] @@ -14,18 +14,36 @@ output = {"Framework": "CIS-AWS", "Version": "1.5", "Requirements": []} with open(file_name, newline="", encoding="utf-8") as f: reader = csv.reader(f, delimiter=",") for row in reader: - attribute = { - "Section": row[3], - "Profile": row[4], - "AssessmentStatus": row[5], - "Description": row[6], - "RationaleStatement": row[7], - "ImpactStatement": row[8], - "RemediationProcedure": row[9], - "AuditProcedure": row[10], - "AdditionalInformation": row[11], - "References": row[12], - } + if len(row[4]) > 0: + attribute = { + "Section": row[3], + "SubSection": row[4], + "Profile": row[5], + "AssessmentStatus": row[6], + "Description": row[7], + "RationaleStatement": row[8], + "ImpactStatement": row[9], + "RemediationProcedure": row[10], + "AuditProcedure": row[11], + "AdditionalInformation": row[12], + "References": row[13], + "DefaultValue": row[14], + } + else: + attribute = { + "Section": row[3], + "Profile": row[5], + "AssessmentStatus": row[6], + "Description": row[7], + "RationaleStatement": row[8], + "ImpactStatement": row[9], + "RemediationProcedure": row[10], + "AuditProcedure": row[11], + "AdditionalInformation": row[12], + "References": row[13], + "DefaultValue": row[14], + } + output["Requirements"].append( { "Id": row[0], From 118f3d163d7d3811b75fdcc9ce3da7055e7db5da Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 21 May 2025 12:39:48 +0200 Subject: [PATCH 17/25] docs: update changelog UI (#7808) --- ui/CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 39a1fbca22..595d678296 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -4,14 +4,18 @@ All notable changes to the **Prowler UI** are documented in this file. ## [v1.8.0] (Prowler v5.8.0) – Not released - ### 🚀 Added - New profile page with details about the user and their roles. [(#7780)](https://github.com/prowler-cloud/prowler/pull/7780) +--- + +## [v1.7.1] (Prowler v5.7.1) + ### 🐞 Fixes - Added validation to AWS IAM role. [(#7787)](https://github.com/prowler-cloud/prowler/pull/7787) +- Tweak some wording for consistency throughout the app. [(#7794)](https://github.com/prowler-cloud/prowler/pull/7794) - Retrieve more than 10 providers in /scans, /manage-groups and /findings pages. [(#7793)](https://github.com/prowler-cloud/prowler/pull/7793) --- From 6e184dae937b26323218d24e77149cbaa4aaf12c Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Wed, 21 May 2025 12:46:35 +0200 Subject: [PATCH 18/25] fix(admincenter): `admincenter_users_admins_reduced_license_footprint` logic (#7779) Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com> --- prowler/CHANGELOG.md | 1 + .../admincenter_users_admins_reduced_license_footprint.py | 4 ++-- ...admincenter_users_admins_reduced_license_footprint_test.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 75a7f1ec42..965cf81cf5 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### Fixed - Fix `m365_powershell test_credentials` to use sanitized credentials. [(#7761)](https://github.com/prowler-cloud/prowler/pull/7761) +- Fix `admincenter_users_admins_reduced_license_footprint` check logic to pass when admin user has no license. [(#7779)](https://github.com/prowler-cloud/prowler/pull/7779) --- diff --git a/prowler/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py b/prowler/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py index 7497feb305..e97f42cd80 100644 --- a/prowler/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py +++ b/prowler/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.py @@ -21,7 +21,7 @@ class admincenter_users_admins_reduced_license_footprint(Check): """Execute the check for users with administrative roles and their licenses. This method iterates over all users and checks if those with administrative roles - have an allowed license. If a user has a valid license (AAD_PREMIUM or AAD_PREMIUM_P2), + have an allowed license. If a user has a valid license (AAD_PREMIUM or AAD_PREMIUM_P2) or no license, the check passes; otherwise, it fails. Returns: @@ -45,7 +45,7 @@ class admincenter_users_admins_reduced_license_footprint(Check): resource_name=user.name, resource_id=user.id, ) - report.status = "FAIL" + report.status = "PASS" report.status_extended = f"User {user.name} has administrative roles {admin_roles} and does not have a license." if user.license: diff --git a/tests/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py b/tests/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py index 6b93429b3d..a3667af57a 100644 --- a/tests/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py +++ b/tests/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint_test.py @@ -210,7 +210,7 @@ class Test_admincenter_users_admins_reduced_license_footprint: check = admincenter_users_admins_reduced_license_footprint() result = check.execute() assert len(result) == 1 - assert result[0].status == "FAIL" + assert result[0].status == "PASS" assert ( result[0].status_extended == "User User1 has administrative roles Global Administrator and does not have a license." From 16cd0e4661478053e7e5b8907d5308bf6c01c827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 21 May 2025 12:46:47 +0200 Subject: [PATCH 19/25] feat(prowler_threatscore): add a level for accordion in dashboard (#7739) Co-authored-by: Sergio Garcia --- dashboard/common_methods.py | 350 ++++++++++++++++++ .../compliance/prowler_threatscore_aws.py | 10 +- .../compliance/prowler_threatscore_azure.py | 10 +- .../compliance/prowler_threatscore_gcp.py | 10 +- .../compliance/prowler_threatscore_m365.py | 10 +- prowler/CHANGELOG.md | 7 +- 6 files changed, 382 insertions(+), 15 deletions(-) diff --git a/dashboard/common_methods.py b/dashboard/common_methods.py index 338b6e6473..a2f9ffe89b 100644 --- a/dashboard/common_methods.py +++ b/dashboard/common_methods.py @@ -2569,6 +2569,356 @@ def get_section_containers_3_levels(data, section_1, section_2, section_3): return html.Div(section_containers, className="compliance-data-layout") +def get_section_containers_threatscore(data, section_1, section_2, section_3): + data["STATUS"] = data["STATUS"].apply(map_status_to_icon) + findings_counts_marco = ( + data.groupby([section_1, "STATUS"]).size().unstack(fill_value=0) + ) + section_containers = [] + data[section_1] = data[section_1].astype(str) + data[section_2] = data[section_2].astype(str) + data[section_3] = data[section_3].astype(str) + + data.sort_values( + by=section_3, + key=lambda x: x.map(extract_numeric_values), + ascending=True, + inplace=True, + ) + + for marco in data[section_1].unique(): + success_marco = findings_counts_marco.loc[marco].get(pass_emoji, 0) + failed_marco = findings_counts_marco.loc[marco].get(fail_emoji, 0) + + fig_name = go.Figure( + [ + go.Bar( + name="Failed", + x=[failed_marco], + y=[""], + orientation="h", + marker=dict(color="#e77676"), + width=[0.8], + ), + go.Bar( + name="Success", + x=[success_marco], + y=[""], + orientation="h", + marker=dict(color="#45cc6e"), + width=[0.8], + ), + ] + ) + fig_name.update_layout( + barmode="stack", + margin=dict(l=10, r=10, t=10, b=10), + paper_bgcolor="rgba(0,0,0,0)", + plot_bgcolor="rgba(0,0,0,0)", + showlegend=False, + width=350, + height=30, + xaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + yaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + annotations=[ + dict( + x=success_marco + failed_marco, + y=0, + xref="x", + yref="y", + text=str(success_marco), + showarrow=False, + font=dict(color="#45cc6e", size=14), + xanchor="left", + yanchor="middle", + ), + dict( + x=0, + y=0, + xref="x", + yref="y", + text=str(failed_marco), + showarrow=False, + font=dict(color="#e77676", size=14), + xanchor="right", + yanchor="middle", + ), + ], + ) + fig_name.add_annotation( + x=failed_marco, + y=0.3, + text="|", + showarrow=False, + font=dict(size=20), + xanchor="center", + yanchor="middle", + ) + + graph_div = html.Div( + dcc.Graph( + figure=fig_name, config={"staticPlot": True}, className="info-bar" + ), + className="graph-section", + ) + direct_internal_items = [] + + for categoria in data[data[section_1] == marco][section_2].unique(): + specific_data = data[ + (data[section_1] == marco) & (data[section_2] == categoria) + ] + findings_counts_categoria = ( + specific_data.groupby([section_2, "STATUS"]) + .size() + .unstack(fill_value=0) + ) + success_categoria = findings_counts_categoria.loc[categoria].get( + pass_emoji, 0 + ) + failed_categoria = findings_counts_categoria.loc[categoria].get( + fail_emoji, 0 + ) + + fig_section = go.Figure( + [ + go.Bar( + name="Failed", + x=[failed_categoria], + y=[""], + orientation="h", + marker=dict(color="#e77676"), + width=[0.8], + ), + go.Bar( + name="Success", + x=[success_categoria], + y=[""], + orientation="h", + marker=dict(color="#45cc6e"), + width=[0.8], + ), + ] + ) + fig_section.update_layout( + barmode="stack", + margin=dict(l=10, r=10, t=10, b=10), + paper_bgcolor="rgba(0,0,0,0)", + plot_bgcolor="rgba(0,0,0,0)", + showlegend=False, + width=350, + height=30, + xaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + yaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + annotations=[ + dict( + x=success_categoria + failed_categoria, + y=0, + xref="x", + yref="y", + text=str(success_categoria), + showarrow=False, + font=dict(color="#45cc6e", size=14), + xanchor="left", + yanchor="middle", + ), + dict( + x=0, + y=0, + xref="x", + yref="y", + text=str(failed_categoria), + showarrow=False, + font=dict(color="#e77676", size=14), + xanchor="right", + yanchor="middle", + ), + ], + ) + fig_section.add_annotation( + x=failed_categoria, + y=0.3, + text="|", + showarrow=False, + font=dict(size=20), + xanchor="center", + yanchor="middle", + ) + + graph_div_section = html.Div( + dcc.Graph( + figure=fig_section, + config={"staticPlot": True}, + className="info-bar-child", + ), + className="graph-section-req", + ) + direct_internal_items_idgrupocontrol = [] + + for idgrupocontrol in specific_data[section_3].unique(): + specific_data2 = specific_data[ + specific_data[section_3] == idgrupocontrol + ] + findings_counts_idgrupocontrol = ( + specific_data2.groupby([section_3, "STATUS"]) + .size() + .unstack(fill_value=0) + ) + success_idgrupocontrol = findings_counts_idgrupocontrol.loc[ + idgrupocontrol + ].get(pass_emoji, 0) + failed_idgrupocontrol = findings_counts_idgrupocontrol.loc[ + idgrupocontrol + ].get(fail_emoji, 0) + + fig_idgrupocontrol = go.Figure( + [ + go.Bar( + name="Failed", + x=[failed_idgrupocontrol], + y=[""], + orientation="h", + marker=dict(color="#e77676"), + width=[0.8], + ), + go.Bar( + name="Success", + x=[success_idgrupocontrol], + y=[""], + orientation="h", + marker=dict(color="#45cc6e"), + width=[0.8], + ), + ] + ) + fig_idgrupocontrol.update_layout( + barmode="stack", + margin=dict(l=10, r=10, t=10, b=10), + paper_bgcolor="rgba(0,0,0,0)", + plot_bgcolor="rgba(0,0,0,0)", + showlegend=False, + width=350, + height=30, + xaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + yaxis=dict(showticklabels=False, showgrid=False, zeroline=False), + annotations=[ + dict( + x=success_idgrupocontrol + failed_idgrupocontrol, + y=0, + xref="x", + yref="y", + text=str(success_idgrupocontrol), + showarrow=False, + font=dict(color="#45cc6e", size=14), + xanchor="left", + yanchor="middle", + ), + dict( + x=0, + y=0, + xref="x", + yref="y", + text=str(failed_idgrupocontrol), + showarrow=False, + font=dict(color="#e77676", size=14), + xanchor="right", + yanchor="middle", + ), + ], + ) + fig_idgrupocontrol.add_annotation( + x=failed_idgrupocontrol, + y=0.3, + text="|", + showarrow=False, + font=dict(size=20), + xanchor="center", + yanchor="middle", + ) + + graph_div_idgrupocontrol = html.Div( + dcc.Graph( + figure=fig_idgrupocontrol, + config={"staticPlot": True}, + className="info-bar-child", + ), + className="graph-section-req", + ) + + data_table = dash_table.DataTable( + data=specific_data2.to_dict("records"), + columns=[ + {"name": i, "id": i} + for i in [ + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ], + style_table={"overflowX": "auto"}, + style_as_list_view=True, + style_cell={"textAlign": "left", "padding": "5px"}, + ) + + title_internal = f"{idgrupocontrol} - {specific_data2['REQUIREMENTS_DESCRIPTION'].iloc[0]}" + + # Cut the title if it's too long + title_internal = ( + title_internal[:130] + " ..." + if len(title_internal) > 130 + else title_internal + ) + + internal_accordion_item_2 = dbc.AccordionItem( + title=title_internal, + children=[ + graph_div_idgrupocontrol, + html.Div([data_table], className="inner-accordion-content"), + ], + ) + direct_internal_items_idgrupocontrol.append( + html.Div( + [ + graph_div_idgrupocontrol, + dbc.Accordion( + [internal_accordion_item_2], + start_collapsed=True, + flush=True, + ), + ], + className="accordion-inner--child", + ) + ) + + internal_accordion_item = dbc.AccordionItem( + title=categoria, + children=direct_internal_items_idgrupocontrol, + ) + internal_section_container = html.Div( + [ + graph_div_section, + dbc.Accordion( + [internal_accordion_item], start_collapsed=True, flush=True + ), + ], + className="accordion-inner--child", + ) + direct_internal_items.append(internal_section_container) + + accordion_item = dbc.AccordionItem(title=marco, children=direct_internal_items) + section_container = html.Div( + [ + graph_div, + dbc.Accordion([accordion_item], start_collapsed=True, flush=True), + ], + className="accordion-inner", + ) + section_containers.append(section_container) + + return html.Div(section_containers, className="compliance-data-layout") + + # This function extracts and compares up to two numeric values, ensuring correct sorting for version-like strings. def extract_numeric_values(value): numbers = re.findall(r"\d+", str(value)) diff --git a/dashboard/compliance/prowler_threatscore_aws.py b/dashboard/compliance/prowler_threatscore_aws.py index 94558f33ad..d86a13fd01 100644 --- a/dashboard/compliance/prowler_threatscore_aws.py +++ b/dashboard/compliance/prowler_threatscore_aws.py @@ -1,6 +1,6 @@ import warnings -from dashboard.common_methods import get_section_containers_cis +from dashboard.common_methods import get_section_containers_threatscore warnings.filterwarnings("ignore") @@ -11,6 +11,7 @@ def get_table(data): "REQUIREMENTS_ID", "REQUIREMENTS_DESCRIPTION", "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", "CHECKID", "STATUS", "REGION", @@ -19,6 +20,9 @@ def get_table(data): ] ].copy() - return get_section_containers_cis( - aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION" + return get_section_containers_threatscore( + aux, + "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", + "REQUIREMENTS_ID", ) diff --git a/dashboard/compliance/prowler_threatscore_azure.py b/dashboard/compliance/prowler_threatscore_azure.py index 94558f33ad..d86a13fd01 100644 --- a/dashboard/compliance/prowler_threatscore_azure.py +++ b/dashboard/compliance/prowler_threatscore_azure.py @@ -1,6 +1,6 @@ import warnings -from dashboard.common_methods import get_section_containers_cis +from dashboard.common_methods import get_section_containers_threatscore warnings.filterwarnings("ignore") @@ -11,6 +11,7 @@ def get_table(data): "REQUIREMENTS_ID", "REQUIREMENTS_DESCRIPTION", "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", "CHECKID", "STATUS", "REGION", @@ -19,6 +20,9 @@ def get_table(data): ] ].copy() - return get_section_containers_cis( - aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION" + return get_section_containers_threatscore( + aux, + "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", + "REQUIREMENTS_ID", ) diff --git a/dashboard/compliance/prowler_threatscore_gcp.py b/dashboard/compliance/prowler_threatscore_gcp.py index 94558f33ad..d86a13fd01 100644 --- a/dashboard/compliance/prowler_threatscore_gcp.py +++ b/dashboard/compliance/prowler_threatscore_gcp.py @@ -1,6 +1,6 @@ import warnings -from dashboard.common_methods import get_section_containers_cis +from dashboard.common_methods import get_section_containers_threatscore warnings.filterwarnings("ignore") @@ -11,6 +11,7 @@ def get_table(data): "REQUIREMENTS_ID", "REQUIREMENTS_DESCRIPTION", "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", "CHECKID", "STATUS", "REGION", @@ -19,6 +20,9 @@ def get_table(data): ] ].copy() - return get_section_containers_cis( - aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION" + return get_section_containers_threatscore( + aux, + "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", + "REQUIREMENTS_ID", ) diff --git a/dashboard/compliance/prowler_threatscore_m365.py b/dashboard/compliance/prowler_threatscore_m365.py index 94558f33ad..d86a13fd01 100644 --- a/dashboard/compliance/prowler_threatscore_m365.py +++ b/dashboard/compliance/prowler_threatscore_m365.py @@ -1,6 +1,6 @@ import warnings -from dashboard.common_methods import get_section_containers_cis +from dashboard.common_methods import get_section_containers_threatscore warnings.filterwarnings("ignore") @@ -11,6 +11,7 @@ def get_table(data): "REQUIREMENTS_ID", "REQUIREMENTS_DESCRIPTION", "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", "CHECKID", "STATUS", "REGION", @@ -19,6 +20,9 @@ def get_table(data): ] ].copy() - return get_section_containers_cis( - aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION" + return get_section_containers_threatscore( + aux, + "REQUIREMENTS_ATTRIBUTES_SECTION", + "REQUIREMENTS_ATTRIBUTES_SUBSECTION", + "REQUIREMENTS_ID", ) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 965cf81cf5..0f67503e13 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -7,9 +7,10 @@ All notable changes to the **Prowler SDK** are documented in this file. ### Added - Add CIS 1.11 compliance framework for Kubernetes. [(#7790)](https://github.com/prowler-cloud/prowler/pull/7790) - Support `HTTPS_PROXY` and `K8S_SKIP_TLS_VERIFY` in Kubernetes. [(#7720)](https://github.com/prowler-cloud/prowler/pull/7720) -- Add new check `entra_users_mfa_capable`. [(#7734)](https://github.com/prowler-cloud/prowler/pull/7734) -- Add new check `admincenter_organization_customer_lockbox_enabled`. [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732) -- Add new check `admincenter_external_calendar_sharing_disabled`. [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733) +- Add new check `entra_users_mfa_capable` for M365 provider. [(#7734)](https://github.com/prowler-cloud/prowler/pull/7734) +- Add new check `admincenter_organization_customer_lockbox_enabled` for M365 provider. [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732) +- Add new check `admincenter_external_calendar_sharing_disabled` for M365 provider. [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733) +- Add a level for Prowler ThreatScore in the accordion in Dashboard. [(#7739)](https://github.com/prowler-cloud/prowler/pull/7739) - Add CIS 4.0 compliance framework for GCP. [(7785)](https://github.com/prowler-cloud/prowler/pull/7785) ### Fixed From 65d3fcee4c366c8a9655856301fe4c21fe25fc49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 21 May 2025 12:57:10 +0200 Subject: [PATCH 20/25] feat(prowler-threatscore): add Weight field inside req (#7795) Co-authored-by: Sergio Garcia --- dashboard/pages/compliance.py | 145 +++++--- prowler/CHANGELOG.md | 1 + .../aws/prowler_threatscore_aws.json | 318 ++++++++++++------ .../azure/prowler_threatscore_azure.json | 231 ++++++++----- .../gcp/prowler_threatscore_gcp.json | 171 ++++++---- .../m365/prowler_threatscore_m365.json | 189 +++++++---- prowler/lib/check/compliance_models.py | 1 + .../compliance/prowler_threatscore/models.py | 4 + .../prowler_threatscore.py | 40 ++- .../prowler_threatscore_aws.py | 2 + .../prowler_threatscore_azure.py | 2 + .../prowler_threatscore_gcp.py | 2 + .../prowler_threatscore_m365.py | 2 + prowler_threatscore_aws | 0 prowler_threatscore_azure | 0 prowler_threatscore_gcp | 0 prowler_threatscore_m365 | 0 tests/lib/outputs/compliance/fixtures.py | 50 +++ .../prowler_threatscore_aws_test.py | 10 +- .../prowler_threatscore_azure_test.py | 10 +- .../prowler_threatscore_gcp_test.py | 10 +- .../prowler_threatscore_m365_test.py | 157 +++++++++ 22 files changed, 980 insertions(+), 365 deletions(-) create mode 100644 prowler_threatscore_aws create mode 100644 prowler_threatscore_azure create mode 100644 prowler_threatscore_gcp create mode 100644 prowler_threatscore_m365 create mode 100644 tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365_test.py diff --git a/dashboard/pages/compliance.py b/dashboard/pages/compliance.py index 0eed0ace3f..93c0eeb2c1 100644 --- a/dashboard/pages/compliance.py +++ b/dashboard/pages/compliance.py @@ -651,58 +651,114 @@ def get_table(current_compliance, table): def get_threatscore_mean_by_pillar(df): - modified_df = df[df["STATUS"] == "FAIL"] + score_per_pillar = {} + max_score_per_pillar = {} - modified_df["REQUIREMENTS_ATTRIBUTES_LEVELOFRISK"] = pd.to_numeric( - modified_df["REQUIREMENTS_ATTRIBUTES_LEVELOFRISK"], errors="coerce" - ) + for _, row in df.iterrows(): + pillar = ( + row["REQUIREMENTS_ATTRIBUTES_SECTION"].split(" - ")[0] + if isinstance(row["REQUIREMENTS_ATTRIBUTES_SECTION"], str) + else "Unknown" + ) - pillar_means = ( - modified_df.groupby("REQUIREMENTS_ATTRIBUTES_SECTION")[ - "REQUIREMENTS_ATTRIBUTES_LEVELOFRISK" - ] - .mean() - .round(2) - ) + if pillar not in score_per_pillar: + score_per_pillar[pillar] = 0 + max_score_per_pillar[pillar] = 0 + + level_of_risk = pd.to_numeric( + row["REQUIREMENTS_ATTRIBUTES_LEVELOFRISK"], errors="coerce" + ) + level_of_risk = 1 if pd.isna(level_of_risk) else level_of_risk + + weight = 1 + if "REQUIREMENTS_ATTRIBUTES_WEIGHT" in row and not pd.isna( + row["REQUIREMENTS_ATTRIBUTES_WEIGHT"] + ): + weight = pd.to_numeric( + row["REQUIREMENTS_ATTRIBUTES_WEIGHT"], errors="coerce" + ) + weight = 1 if pd.isna(weight) else weight + + max_score_per_pillar[pillar] += level_of_risk * weight + + if row["STATUS"] == "PASS": + score_per_pillar[pillar] += level_of_risk * weight output = [] - for pillar, mean in pillar_means.items(): - output.append(f"{pillar} - [{mean}]") + for pillar in max_score_per_pillar: + risk_score = 0 + if max_score_per_pillar[pillar] > 0: + risk_score = (score_per_pillar[pillar] / max_score_per_pillar[pillar]) * 100 + + output.append(f"{pillar} - [{risk_score:.1f}%]") for value in output: - if value.split(" - ")[0] in df["REQUIREMENTS_ATTRIBUTES_SECTION"].values: + base_pillar = value.split(" - ")[0] + if base_pillar in df["REQUIREMENTS_ATTRIBUTES_SECTION"].values: df.loc[ - df["REQUIREMENTS_ATTRIBUTES_SECTION"] == value.split(" - ")[0], + df["REQUIREMENTS_ATTRIBUTES_SECTION"] == base_pillar, "REQUIREMENTS_ATTRIBUTES_SECTION", ] = value + return df def get_table_prowler_threatscore(df): - df = df[df["STATUS"] == "FAIL"] + score_per_pillar = {} + max_score_per_pillar = {} + pillars = {} - # Delete " - " from the column REQUIREMENTS_ATTRIBUTES_SECTION - df["REQUIREMENTS_ATTRIBUTES_SECTION"] = ( - df["REQUIREMENTS_ATTRIBUTES_SECTION"].str.split(" - ").str[0] - ) + df_copy = df.copy() - df["REQUIREMENTS_ATTRIBUTES_LEVELOFRISK"] = pd.to_numeric( - df["REQUIREMENTS_ATTRIBUTES_LEVELOFRISK"], errors="coerce" - ) - - score_df = ( - df.groupby("REQUIREMENTS_ATTRIBUTES_SECTION")[ - "REQUIREMENTS_ATTRIBUTES_LEVELOFRISK" - ] - .mean() - .reset_index() - .rename( - columns={ - "REQUIREMENTS_ATTRIBUTES_SECTION": "Pillar", - "REQUIREMENTS_ATTRIBUTES_LEVELOFRISK": "Score", - } + for _, row in df_copy.iterrows(): + pillar = ( + row["REQUIREMENTS_ATTRIBUTES_SECTION"].split(" - ")[0] + if isinstance(row["REQUIREMENTS_ATTRIBUTES_SECTION"], str) + else "Unknown" ) - ) + + if pillar not in pillars: + pillars[pillar] = {"FAIL": 0, "PASS": 0, "MUTED": 0} + score_per_pillar[pillar] = 0 + max_score_per_pillar[pillar] = 0 + + level_of_risk = pd.to_numeric( + row["REQUIREMENTS_ATTRIBUTES_LEVELOFRISK"], errors="coerce" + ) + level_of_risk = 1 if pd.isna(level_of_risk) else level_of_risk + + weight = 1 + if "REQUIREMENTS_ATTRIBUTES_WEIGHT" in row and not pd.isna( + row["REQUIREMENTS_ATTRIBUTES_WEIGHT"] + ): + weight = pd.to_numeric( + row["REQUIREMENTS_ATTRIBUTES_WEIGHT"], errors="coerce" + ) + weight = 1 if pd.isna(weight) else weight + + max_score_per_pillar[pillar] += level_of_risk * weight + + if row["STATUS"] == "PASS": + pillars[pillar]["PASS"] += 1 + score_per_pillar[pillar] += level_of_risk * weight + elif row["STATUS"] == "FAIL": + pillars[pillar]["FAIL"] += 1 + + if "MUTED" in row and row["MUTED"] == "True": + pillars[pillar]["MUTED"] += 1 + + result_df = [] + + for pillar in pillars.keys(): + risk_score = 0 + if max_score_per_pillar[pillar] > 0: + risk_score = (score_per_pillar[pillar] / max_score_per_pillar[pillar]) * 100 + + result_df.append({"Pillar": pillar, "Score": risk_score}) + + score_df = pd.DataFrame(result_df) + + score_df = score_df.sort_values("Score", ascending=True) fig = px.bar( score_df, @@ -710,22 +766,25 @@ def get_table_prowler_threatscore(df): y="Score", color="Score", color_continuous_scale=[ - "#45cc6e", - "#f4d44d", "#e77676", - ], # verde → amarillo → rojo - hover_data={"Score": True, "Pillar": True}, - labels={"Score": "Average Risk Score", "Pillar": "Section"}, + "#f4d44d", + "#45cc6e", + ], + labels={"Score": "Risk Score (%)", "Pillar": "Section"}, height=400, + text="Score", ) + fig.update_traces(texttemplate="%{text:.1f}%", textposition="outside") + fig.update_layout( xaxis_title="Pillar", - yaxis_title="Level of Risk", + yaxis_title="Risk Score (%)", margin=dict(l=20, r=20, t=30, b=20), plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)", - coloraxis_colorbar=dict(title="Risk"), + coloraxis_colorbar=dict(title="Risk %"), + yaxis=dict(range=[0, 110]), ) return dcc.Graph( diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 0f67503e13..7836182c3a 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### Added - Add CIS 1.11 compliance framework for Kubernetes. [(#7790)](https://github.com/prowler-cloud/prowler/pull/7790) - Support `HTTPS_PROXY` and `K8S_SKIP_TLS_VERIFY` in Kubernetes. [(#7720)](https://github.com/prowler-cloud/prowler/pull/7720) +- Add Weight for Prowler ThreatScore scoring. [(7795)](https://github.com/prowler-cloud/prowler/pull/7795) - Add new check `entra_users_mfa_capable` for M365 provider. [(#7734)](https://github.com/prowler-cloud/prowler/pull/7734) - Add new check `admincenter_organization_customer_lockbox_enabled` for M365 provider. [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732) - Add new check `admincenter_external_calendar_sharing_disabled` for M365 provider. [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733) diff --git a/prowler/compliance/aws/prowler_threatscore_aws.json b/prowler/compliance/aws/prowler_threatscore_aws.json index c53b7e1590..8ef5d24b93 100644 --- a/prowler/compliance/aws/prowler_threatscore_aws.json +++ b/prowler/compliance/aws/prowler_threatscore_aws.json @@ -17,7 +17,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", "AdditionalInformation": "Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -34,7 +35,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", "AdditionalInformation": "A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -51,7 +53,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "To enhance security and reduce the risk of unauthorized access, Multi-Factor Authentication (MFA) should be enabled for all IAM users who have access to the AWS Management Console.", "AdditionalInformation": "Without Multi-Factor Authentication (MFA), a compromised password alone is enough to allow an attacker to access the console, gaining full visibility and control over AWS resources.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -68,7 +71,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Password policies help enforce password complexity requirements to strengthen account security. In AWS IAM, password policies can be configured to ensure that user passwords meet specific criteria, including a minimum length requirement. It is recommended to enforce a minimum password length of 14 characters to enhance security.", "AdditionalInformation": "Requiring longer and more complex passwords reduces the risk of compromise from brute force attacks, credential stuffing, and other password-based threats. A 14-character minimum makes it significantly harder for attackers to guess or crack passwords, improving overall account security and resilience.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -85,7 +89,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "IAM password policies can be configured to prevent users from reusing previous passwords. This ensures that users create new, unique passwords instead of cycling through old ones. It is recommended to enforce password history restrictions to enhance security.", "AdditionalInformation": "Blocking password reuse helps mitigate the risk of credential-based attacks, such as brute force and credential stuffing. It prevents users from reverting to previously compromised passwords, reducing the likelihood of unauthorized access.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -102,7 +107,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Password policies help enforce password complexity requirements to strengthen account security. In AWS IAM, password policies can be configured to ensure that user passwords meet specific criteria, including using at least number as requirement. It is recommended to enforce the usage of one number to enhance security.", "AdditionalInformation": "Requiring more complex passwords reduces the risk of compromise from brute force attacks, credential stuffing, and other password-based threats. Using a number at least makes it significantly harder for attackers to guess or crack passwords, improving overall account security and resilience.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -119,7 +125,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "IAM password policies can be configured to enforce the use of at least one special character (symbol) in user passwords. Special characters (e.g., @, #, $, %) add complexity, making passwords harder to guess or crack. It is recommended to require at least one symbol in IAM passwords to enhance security.", "AdditionalInformation": "Requiring a symbol in passwords increases entropy, making brute-force and dictionary attacks more difficult. Attackers often rely on common or predictable password patterns, and enforcing special characters helps reduce the effectiveness of such attacks. This policy strengthens overall password security and aligns with industry best practices.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -136,7 +143,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "IAM password policies can be configured to enforce the use of at least one lowercase letter in user passwords. Including lowercase letters increases password complexity, making them more resistant to brute-force and dictionary attacks. It is recommended to require at least one lowercase letter in IAM passwords to strengthen security.", "AdditionalInformation": "Requiring at least one lowercase letter ensures that passwords are not composed solely of numbers or uppercase letters, which are easier to guess. Attackers often use wordlists and predictable patterns when attempting to crack passwords. By enforcing lowercase letters, password complexity improves, reducing the likelihood of unauthorized access.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -153,7 +161,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "IAM password policies can be configured to enforce the use of at least one uppercase letter in user passwords. Including uppercase letters increases password complexity, making them more resilient to brute-force and dictionary attacks. It is recommended to require at least one uppercase letter in IAM passwords to enhance security.", "AdditionalInformation": "Requiring at least one uppercase letter ensures that passwords are not composed solely of lowercase letters or numbers, which are more predictable and easier to crack. Attackers often rely on common word variations in password attacks, and enforcing uppercase letters adds an additional layer of complexity, reducing the risk of unauthorized access.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -171,7 +180,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "AWS IAM users can authenticate and access AWS resources using various types of credentials, including passwords and access keys. To minimize security risks, it is recommended to deactivate or remove any credentials that have been unused for 45 days or more.", "AdditionalInformation": "Disabling or removing inactive credentials reduces the attack surface and prevents unauthorized access through compromised or forgotten credentials. Unused credentials pose a security risk, as attackers may exploit them if they remain active without regular monitoring. Regularly auditing and revoking stale credentials enhances overall account security.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -188,7 +198,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Access keys consist of an access key ID and a secret access key, which are used to authenticate and sign programmatic requests made to AWS. These keys allow users and applications to interact with AWS services via the AWS Command Line Interface (CLI), AWS SDKs, PowerShell tools, or direct API calls. To maintain security, it is recommended that all access keys be rotated regularly to minimize the risk of unauthorized access.", "AdditionalInformation": "Regularly rotating access keys reduces the risk of compromised credentials being exploited. If an access key is leaked, cracked, or stolen, rotating it limits the window of opportunity for malicious use. Additionally, rotating keys ensures that inactive or outdated credentials cannot be used for unauthorized access, enhancing overall security and compliance.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -205,7 +216,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "IAM password policies can be configured to enforce password expiration after a defined period. It is recommended that passwords be set to expire within 90 days or less to ensure users regularly update their credentials. This helps mitigate security risks associated with stale or compromised passwords that remain active for extended periods.", "AdditionalInformation": "Requiring password expiration within 90 days or less reduces the risk of credential-based attacks, such as brute-force attacks and credential stuffing, by ensuring that old passwords cannot be used indefinitely. If a password has been exposed or compromised without detection, regular expiration limits the window of opportunity for an attacker to exploit it. This policy enforces stronger access control and aligns with industry security best practices.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -222,7 +234,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "The root account in AWS has unrestricted administrative privileges and should be protected with the highest security measures. Access keys provide programmatic access to AWS, but when linked to the root account, they pose a significant security risk. It is recommended that no access keys be associated with the root account, ensuring that all programmatic access is managed through IAM roles and users with least privilege access.", "AdditionalInformation": "The root account holds the highest level of privileges in an AWS environment. AWS Access Keys enable programmatic access to AWS resources, but when associated with the root account, they pose a significant security risk. It is recommended to remove all access keys linked to the root account to minimize potential attack vectors. Eliminating root access keys reduces the risk of unauthorized access and enforces the use of role-based IAM accounts with least privilege, promoting a more secure and controlled access management approach.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -239,7 +252,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "IAM policies define permissions that control access to AWS resources. To ensure scalability, security, and manageability, it is recommended that IAM policies be attached only to groups or roles rather than individual users. By assigning permissions at the group or role level, organizations can apply consistent security policies and avoid permission sprawl.", "AdditionalInformation": "Attaching policies to groups or roles simplifies access control, reduces security risks, and improves compliance tracking. This approach prevents overprivileged accounts and ensures a structured, scalable IAM policy framework.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -256,7 +270,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "IAM users gain access to AWS services, functions, and data through IAM policies. There are four ways to assign policies to a user: 1.Inline (User-Specific) Policy – Editing the policy directly within the user’s profile.2.Directly Attached Policy – Assigning a standalone policy to a user.3.Group-Based Policy (Recommended) – Adding the user to an IAM group with an attached policy. 4.Group with Inline Policy – Assigning an inline policy to a group that includes the user.Among these methods, only the third approach (group-based policies) is recommended for security and manageability.", "AdditionalInformation": "Managing IAM permissions exclusively through groups ensures consistent, scalable, and role-based access control. This approach reduces the risk of excessive privileges, simplifies auditing, and aligns user permissions with organizational roles.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -274,7 +289,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "IAM policies define permissions for users, groups, and roles, controlling access to AWS resources. Following the principle of least privilege, users should be granted only the permissions necessary to perform their tasks. Instead of assigning broad administrative privileges, permissions should be carefully crafted to allow only the required actions.", "AdditionalInformation": "Starting with minimal permissions and granting additional access as needed is significantly more secure than providing excessive permissions and attempting to restrict them later. Assigning full administrative privileges increases the risk of unauthorized or accidental actions that could compromise AWS resources. IAM policies containing Effect: Allow, Action: , Resource: should be removed to prevent unrestricted access and enforce security best practices.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -291,7 +307,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "AWS offers a Support Center for incident notification, response, technical support, and customer service assistance. To ensure secure and controlled access, an IAM role should be created with a properly assigned policy, allowing only authorized users to manage incidents with AWS Support.", "AdditionalInformation": "Implementing least privilege access control ensures that only designated users can interact with AWS Support. Assigning an IAM role with a specific policy limits access to only necessary actions, reducing the risk of unauthorized modifications or exposure of sensitive account information.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -308,7 +325,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "AWS instances can access AWS resources either by embedding access keys in API calls or by assigning an IAM role with the necessary permissions. Using IAM roles ensures secure, controlled access without hardcoding credentials.", "AdditionalInformation": "IAM roles eliminate the risks associated with hardcoded credentials, reducing exposure to external threats. Unlike access keys, which can be used outside AWS if compromised, IAM roles require an attacker to maintain control of an instance to exploit privileges. Additionally, IAM roles simplify credential management by ensuring permissions are automatically updated without the need for manual key rotation.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -325,7 +343,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "To enable HTTPS connections for applications and websites hosted on AWS, an SSL/TLS server certificate is required. AWS provides two options for managing certificates: AWS Certificate Manager (ACM) – The preferred method for managing SSL/TLS certificates, automating renewals and deployment. IAM Certificate Storage – Used only when deploying SSL/TLS certificates in regions not supported by ACM. IAM securely encrypts private keys and stores them, but certificates must be obtained from an external provider. ACM certificates cannot be uploaded to IAM, and IAM certificates cannot be managed from the IAM Console.", "AdditionalInformation": "Removing expired SSL/TLS certificates prevents the accidental deployment of invalid certificates, which could cause service disruptions, security warnings, and loss of credibility for applications using AWS services like Elastic Load Balancer (ELB). As a best practice, expired certificates should be deleted to maintain a secure and trusted application environment.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -342,7 +361,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "The root account in AWS has unrestricted administrative privileges and should be used only for initial account setup and emergency scenarios. Regular operations should be performed using IAM users or roles with least privilege access to minimize security risks.", "AdditionalInformation": "Using the root account increases the risk of unauthorized access, accidental misconfigurations, and privilege misuse. By restricting root account usage and delegating tasks to IAM users or roles, organizations can enforce better access control, auditing, and security best practices.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -359,7 +379,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Enable IAM Access Analyzer for all AWS regions to monitor IAM policies and identify resources with unintended external access. IAM Access Analyzer, introduced at AWS re:Invent 2019, scans resource-based policies and provides visibility into which resources—such as KMS keys, IAM roles, S3 buckets, Lambda functions, and SQS queues—are accessible by external accounts or federated users. This allows administrators to enforce least privilege access and mitigate unauthorized access risks. IAM Access Analyzer operates within the same AWS region as the resources being analyzed.", "AdditionalInformation": "IAM Access Analyzer enhances security visibility by detecting AWS resources shared with external entities, helping organizations identify potential security risks and ensure compliance with least privilege principles. It continuously evaluates resource-based policies using logic-based analysis, allowing teams to promptly remediate misconfigurations that could lead to unauthorized access or data exposure.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -376,7 +397,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "In multi-account AWS environments, centralizing IAM user management improves control, security, and access management efficiency. Instead of creating separate IAM users in each account, access should be managed through role assumption. This can be achieved using AWS Organizations or federation with an external identity provider (e.g., AWS IAM Identity Center, Okta, or Active Directory).", "AdditionalInformation": "Centralizing IAM user management into a single identity store simplifies administration, reduces the risk of access misconfigurations, and enforces consistent security policies across all accounts. This approach enhances security, scalability, and compliance while minimizing user duplication and permission errors.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -393,7 +415,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "AWS CloudShell provides a managed command-line interface (CLI) for interacting with AWS services. The AWSCloudShellFullAccess IAM policy grants full access to CloudShell, including file upload and download capabilities between a user’s local system and the CloudShell environment. Within CloudShell, users have sudo privileges and unrestricted internet access, making it possible to install software—such as file transfer tools—that could facilitate data movement to external servers.", "AdditionalInformation": "Access to AWSCloudShellFullAccess should be restricted, as it can serve as a potential data exfiltration vector for malicious or compromised cloud administrators. Granting full permissions to CloudShell increases the risk of unauthorized data transfers outside the AWS environment. AWS provides guidance on creating more restrictive IAM policies to limit file transfer capabilities, reducing security risks.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -410,7 +433,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "IAM inline policies define permissions directly attached to users, groups, or roles, rather than being managed as standalone policies. If improperly configured, these policies can grant actions that enable privilege escalation, allowing users to elevate their access beyond intended permissions. Privilege escalation can occur through misconfigured IAM roles, excessive permissions, or indirect access paths, potentially leading to unauthorized control over AWS resources.", "AdditionalInformation": "Users with some IAM permissions are allowed to elevate their privileges up to administrator rights.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -427,7 +451,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "Amazon S3 bucket permissions can be configured using a bucket policy to enforce access restrictions. To enhance security, objects within the bucket should be made accessible only via HTTPS, ensuring encrypted data transmission.", "AdditionalInformation": "By default, Amazon S3 accepts both HTTP and HTTPS requests, which can expose data to interception. To enforce secure access, HTTP requests should be explicitly denied in the bucket policy. Simply allowing HTTPS without blocking HTTP does not fully comply with security best practices, as unencrypted requests may still be accepted.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -444,7 +469,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "AWS EC2 instances allow users to choose between Instance Metadata Service Version 1 (IMDSv1), which uses a request/response model, or Instance Metadata Service Version 2 (IMDSv2), which uses a session-based approach for enhanced security", "AdditionalInformation": "Instance metadata refers to the data about an EC2 instance, such as host names, events, and security groups, that is used for managing and configuring the instance. When enabling the Metadata Service, users can opt for either IMDSv1, which operates via a simple request/response model, or IMDSv2, which implements session authentication for additional security. With IMDSv2, each request is secured by session-based authentication, ensuring that all interactions with the instance's metadata and credentials are protected. IMDSv1, on the other hand, may expose instances to Server-Side Request Forgery (SSRF) attacks. To improve security, Amazon recommends using IMDSv2", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -461,7 +487,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Enabling MFA Delete on a sensitive or classified Amazon S3 bucket adds an extra layer of protection by requiring two-factor authentication for critical actions, such as deleting object versions or changing the bucket’s versioning state.", "AdditionalInformation": "MFA Delete helps prevent accidental or malicious deletions by requiring an additional authentication step. This mitigates the risk of data loss due to compromised credentials or unauthorized access, ensuring that critical objects remain protected.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -478,7 +505,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon S3 buckets may store sensitive data that needs to be discovered, classified, monitored, and protected to maintain security and compliance. Amazon Macie, along with third-party tools, can automatically inventory S3 buckets and identify sensitive data at scale.", "AdditionalInformation": "Using automated data discovery and classification tools, such as Amazon Macie, enhances security by continuously monitoring S3 buckets for sensitive information. Macie leverages machine learning and pattern matching to detect and protect critical data, reducing the risk of data leaks and unauthorized access.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -495,7 +523,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Each Amazon VPC includes a default security group that initially denies all inbound traffic, allows all outbound traffic, and permits unrestricted communication between instances within the group. If no security group is specified when launching an instance, it is automatically assigned to this default security group. Since security groups control stateful ingress and egress traffic, it is recommended to restrict all inbound and outbound traffic in the default security group.", "AdditionalInformation": "Restricting all traffic in the default security group enforces least privilege access by ensuring that AWS resources are explicitly assigned to well-defined security groups. This approach reduces unintended exposure, improves network segmentation, and promotes secure resource placement within AWS environments.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -512,7 +541,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "After establishing a VPC peering connection, routing tables must be updated to enable communication between the peered VPCs. Routes can be configured with granular specificity, allowing connections to be restricted to a single host or a specific subnet within the peered VPC.", "AdditionalInformation": "Defining highly specific routes in VPC peering connections enhances security by limiting access to only the necessary resources. This minimizes the potential impact of a security breach, ensuring that resources outside the defined routes remain inaccessible, reducing the risk of lateral movement within the network.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -531,7 +561,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Network Access Control Lists (NACLs) provide stateless filtering of ingress and egress traffic to AWS resources. It is recommended that NACLs do not allow unrestricted inbound access to remote administration ports, such as SSH (port 22) and RDP (port 3389), over TCP (6), UDP (17), or ALL (-1) protocols to prevent unauthorized access.", "AdditionalInformation": "Exposing remote server administration ports (e.g., SSH on 22 and RDP on 3389) to the public internet increases the attack surface, making resources more vulnerable to brute-force attacks and unauthorized access. Restricting inbound access to these ports helps reduce security risks and limit potential exploitation.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -550,7 +581,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Security groups enforce stateful filtering of ingress and egress traffic to AWS resources. To enhance security, no security group should allow unrestricted inbound access to remote administration ports, such as SSH (port 22) and RDP (port 3389), over TCP (6), UDP (17), or ALL (-1) protocols.", "AdditionalInformation": "Exposing remote administration ports to the public internet significantly increases the attack surface, making resources more vulnerable to brute-force attacks, exploitation, and unauthorized access. Restricting ingress traffic to these ports helps reduce security risks and prevent potential system compromises.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -567,7 +599,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon Elastic Block Store (EBS) snapshots contain backups of EC2 volumes, which may include sensitive data such as credentials, application configurations, or customer information. EBS snapshots should never be publicly accessible to prevent unauthorized access and data exposure. By default, snapshots are private, but they can be manually shared with other AWS accounts or made public, which poses a significant security risk if misconfigured.", "AdditionalInformation": "Exposing EBS snapshots publicly increases the risk of data breaches, unauthorized access, and compliance violations. Attackers can scan for publicly accessible snapshots and extract sensitive information. To prevent data leaks, snapshots should be restricted to specific AWS accounts or kept private unless explicitly needed for sharing. Implementing proper access controls helps protect critical data and maintain compliance with security best practices.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -584,7 +617,8 @@ "SubSection": "4.2 At-Rest", "AttributeDescription": "Amazon Elastic Compute Cloud (EC2) supports encryption at rest for Elastic Block Store (EBS) volumes, ensuring that stored data remains protected. While EBS encryption is disabled by default, organizations can enforce automatic encryption of newly created volumes to enhance data security and compliance.", "AdditionalInformation": "Enforcing EBS volume encryption reduces the risk of data exposure, unauthorized access, and compliance violations. If encryption remains intact, even if storage is compromised, data remains unreadable to unauthorized users. Encrypting data at rest ensures that sensitive information is protected against accidental disclosure, insider threats, and external attacks.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -601,7 +635,8 @@ "SubSection": "4.2 At-Rest", "AttributeDescription": "Amazon Relational Database Service (RDS) supports encryption at rest using the industry-standard AES-256 encryption algorithm to secure database instances and their associated storage. Once enabled, RDS encryption automatically handles access authentication and decryption, ensuring secure data storage with minimal performance impact.", "AdditionalInformation": "Databases often contain sensitive and business-critical information, making encryption essential to protect against unauthorized access and data breaches. Enabling RDS encryption ensures that underlying storage, automated backups, read replicas, and snapshots are all encrypted, preventing accidental or malicious data exposure while maintaining compliance with security best practices.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -618,7 +653,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon Simple Notification Service (SNS) topics enable messaging between AWS services, applications, and users. By default, SNS topics should be restricted to trusted AWS accounts or IAM roles to prevent unauthorized access. Allowing global send (sns:Publish) or subscribe (sns:Subscribe) permissions means any AWS account or unauthenticated entity could send messages or subscribe to the topic, potentially leading to spam, data leaks, or misuse of notifications.", "AdditionalInformation": "SNS topics with global send or subscribe permissions expose AWS environments to unauthorized message injection, data exfiltration, and Denial-of-Service (DoS) attacks. An attacker could flood an SNS topic with malicious or fraudulent messages, leading to unexpected charges or service disruptions. Restricting access ensures that only authorized AWS accounts, applications, or IAM roles can send and receive messages, reducing security risks and protecting system integrity.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -635,7 +671,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon Relational Database Service (RDS) snapshots store backups of database instances, potentially containing sensitive data such as customer records, credentials, and application configurations. By default, RDS snapshots are private, but they can be shared with other AWS accounts or made public, which can lead to data exposure if misconfigured. To prevent unauthorized access, RDS snapshots should never be publicly accessible unless explicitly required and secured.", "AdditionalInformation": "Publicly accessible RDS snapshots create a serious security risk, as anyone can copy the snapshot and restore the database, exposing sensitive information. Attackers actively scan for publicly available snapshots to extract credentials, personally identifiable information (PII), or business-critical data. To prevent unauthorized access and data leaks, RDS snapshots should remain private or restricted to trusted AWS accounts following the principle of least privilege.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -652,7 +689,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "AWS CloudTrail logs record account activity, including API calls, user actions, and resource modifications, making them critical for security monitoring and compliance auditing. These logs are typically stored in an Amazon S3 bucket for long-term retention and analysis. To protect sensitive security data, the S3 bucket storing CloudTrail logs should never be publicly accessible.", "AdditionalInformation": "If the S3 bucket containing CloudTrail logs is publicly accessible, unauthorized users could access sensitive security information, including API calls, IAM activity, and infrastructure changes. Exposing CloudTrail logs can help attackers reconstruct system activity, identify vulnerabilities, and plan targeted attacks. To prevent data leaks and unauthorized access, CloudTrail log buckets should be restricted using IAM policies, bucket policies, and S3 Block Public Access settings.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -669,7 +707,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon Redshift clusters store and process large-scale data for analytics and business intelligence workloads. By default, Redshift clusters can be configured with a public endpoint, making them accessible from the internet. To minimize security risks, Redshift clusters should be restricted to private networks and should not have a public endpoint unless absolutely necessary and properly secured.", "AdditionalInformation": "Exposing a Redshift cluster to the public internet increases the risk of unauthorized access, data breaches, and cyberattacks. Attackers could attempt brute-force login attempts, exploit misconfigurations, or access sensitive business data. Keeping Redshift clusters within private subnets and restricting access via security groups, VPC settings, and IAM policies ensures that only trusted networks and users can connect, reducing the attack surface and enhancing data security.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -686,7 +725,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "AWS API Gateway allows developers to create, deploy, and manage APIs that connect applications to backend services. By default, API Gateway endpoints can be publicly accessible, meaning they can be invoked from anywhere on the internet. To enhance security, API Gateway endpoints should be restricted to private networks using VPC links, private API settings, or access control mechanisms to ensure that only authorized entities can interact with the API.", "AdditionalInformation": "Publicly accessible API Gateway endpoints can expose backend services to unauthorized access, data leaks, and potential exploitation. Attackers may attempt brute-force authentication, injection attacks, or abuse API functionality if access is not properly restricted. To reduce the attack surface, API Gateway endpoints should be limited to internal use or protected with authentication, IAM permissions, WAF rules, or private VPC access to ensure only trusted users and systems can invoke the API.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -703,7 +743,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon EC2 instances launched via Auto Scaling groups can automatically scale workloads based on demand. By default, instances can be assigned public IP addresses, making them accessible from the internet. To enhance security, EC2 instances in Auto Scaling group launch configurations should not have public IP addresses, ensuring they remain within a private network and are only accessible through secure channels such as bastion hosts or VPN connections.", "AdditionalInformation": "Assigning public IP addresses to Auto Scaling group instances increases the risk of unauthorized access, brute-force attacks, and potential exploitation. Publicly accessible instances can become targets for malicious actors, leading to data breaches or service disruptions. By restricting public IP addresses, organizations can enforce network segmentation, ensuring that EC2 instances are accessed securely via private networks, VPNs, or load balancers.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -720,7 +761,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "AWS Lambda functions allow running code without managing servers. Lambda supports resource-based policies that define who can invoke the function. If a Lambda function’s resource-based policy allows public access, it can be triggered by anyone on the internet, posing a significant security risk. To prevent unauthorized execution, Lambda functions should not be publicly accessible unless explicitly required and properly secured.", "AdditionalInformation": "Publicly accessible Lambda functions can be abused for unauthorized execution, leading to service disruptions, data exfiltration, or increased AWS costs due to excessive invocations. Attackers could exploit misconfigured functions to perform malicious actions, extract sensitive data, or abuse compute resources. To reduce security risks, Lambda functions should only be accessible to specific IAM roles, AWS services, or trusted accounts, enforcing least privilege access and maintaining secure function execution.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -737,7 +779,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "AWS Lambda function URLs provide a built-in HTTPS endpoint that allows functions to be invoked directly via HTTP requests. By default, Lambda function URLs can be publicly accessible, meaning anyone on the internet can invoke the function if proper access controls are not enforced. To minimize security risks, Lambda function URLs should not be publicly accessible unless explicitly required and properly restricted.", "AdditionalInformation": "Exposing Lambda function URLs to the public internet increases the risk of unauthorized access, API abuse, and potential exploitation. Attackers may invoke functions maliciously, leading to data leaks, unauthorized operations, increased costs, or denial-of-service (DoS) attacks. To enhance security, Lambda function URLs should be restricted to specific IAM roles, AWS services, or trusted clients, ensuring that only authorized users can trigger the function.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -754,7 +797,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "AWS Database Migration Service (DMS) instances facilitate data migration between databases across on-premises and cloud environments. By default, DMS instances can be configured with publicly accessible endpoints, making them reachable from the internet. To enhance security and prevent unauthorized access, DMS instances should not be publicly accessible unless explicitly required and properly secured.", "AdditionalInformation": "Publicly accessible DMS instances increase the risk of unauthorized access, data interception, and potential exploitation. Attackers could target exposed instances to steal or manipulate sensitive data during migration. Restricting public access ensures data migrations remain secure, limiting access to trusted networks, private VPCs, and authorized IAM roles, thereby reducing the attack surface and ensuring compliance with security best practices.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -771,7 +815,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "AWS DocumentDB manual cluster snapshots store backups of DocumentDB clusters, containing sensitive database information such as application data, configurations, and credentials. By default, snapshots are private, but they can be manually shared or made public, which poses a significant security risk. To prevent unauthorized access, DocumentDB manual cluster snapshots should never be publicly accessible unless explicitly required and properly secured.", "AdditionalInformation": "Publicly accessible DocumentDB snapshots expose critical database information, increasing the risk of data breaches, unauthorized access, and compliance violations. Attackers could restore the snapshot in their own AWS account and gain full access to the database content. To protect sensitive data, DocumentDB snapshots should only be shared with specific AWS accounts or remain private, following least privilege principles and AWS security best practices.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -788,7 +833,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon EC2 Amazon Machine Images (AMIs) contain pre-configured operating system and application environments that can be used to launch new EC2 instances. By default, AMIs are private, but they can be manually shared or made public, which poses a security risk if sensitive data or proprietary configurations are exposed. To prevent unauthorized access and data leaks, EC2 AMIs should not be set as public unless explicitly required and properly secured.", "AdditionalInformation": "Publicly accessible EC2 AMIs increase the risk of data exposure, unauthorized access, and compliance violations. Attackers could copy, analyze, or exploit public AMIs to extract sensitive credentials, misconfigurations, or proprietary software. Keeping AMIs private or shared only with specific AWS accounts ensures that only trusted users or teams can access and launch instances from them, reducing security risks and preventing unintended data exposure.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -805,7 +851,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon Elastic Block Store (EBS) snapshots are backups of EC2 volumes that may contain sensitive data, such as credentials, application configurations, and customer records. By default, EBS snapshots are private, but they can be manually shared or made public, allowing anyone to copy or restore them. To prevent unauthorized access and data exposure, public access to EBS snapshots should always be disabled.", "AdditionalInformation": "Publicly accessible EBS snapshots pose a significant security risk, as attackers can restore and extract sensitive data if a snapshot is exposed. Misconfigured public snapshots have led to data breaches and compliance violations in the past. To mitigate this risk, EBS snapshots should be kept private or explicitly shared only with trusted AWS accounts, following least privilege principles to protect critical data and maintain security compliance.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -838,7 +885,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Amazon EC2 instances can run various services that communicate over common ports such as 22 (SSH), 3389 (RDP), 80 (HTTP), and 443 (HTTPS) (and more). If these ports are open to the internet, attackers can attempt unauthorized access, brute-force attacks, or exploit known vulnerabilities. To reduce security risks, EC2 instances should be configured so that common ports are not exposed to the public internet, unless explicitly required and properly secured.", "AdditionalInformation": "Exposing common ports directly to the internet increases the attack surface and risks unauthorized access or system compromise. Attackers frequently scan for open ports to target misconfigured or unpatched services. To enhance security, access to EC2 common ports should be restricted using security groups, network ACLs, and VPC configurations, ensuring that only trusted networks and users can connect.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -869,7 +917,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Amazon EC2 security groups act as virtual firewalls, controlling inbound and outbound traffic to instances. If a security group allows ingress (incoming traffic) from the internet (0.0.0.0/0 or ::/0) to common ports such as 22 (SSH), 3389 (RDP), 80 (HTTP), or 443 (HTTPS) (and more), it creates a significant security risk. To minimize exposure, security groups should be configured to restrict ingress access to these ports to only trusted IP addresses or internal networks.", "AdditionalInformation": "Allowing unrestricted inbound traffic to common ports increases the risk of brute-force attacks, unauthorized access, and exploitation of vulnerabilities. Attackers actively scan for open ports on public-facing EC2 instances to gain unauthorized control. To reduce security risks, ingress rules should be restricted using least privilege principles, IP whitelisting, VPN access, or bastion hosts, ensuring that only authorized users and networks can connect.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -886,7 +935,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon Elastic Container Registry (ECR) repositories store and manage container images for deployment in AWS services. By default, ECR repositories are private, but they can be manually configured as public, allowing anyone to pull container images. To prevent unauthorized access and potential security risks, ECR repositories should not be set as public unless explicitly required and properly secured.", "AdditionalInformation": "Publicly accessible ECR repositories expose container images to unauthorized users, increasing the risk of intellectual property theft, malware injection, or unauthorized use of containerized applications. Attackers could analyze public images for vulnerabilities or use misconfigured images for malicious purposes. To mitigate this risk, ECR repositories should remain private or be explicitly shared with trusted AWS accounts, ensuring secure access and compliance with best practices.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -903,7 +953,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon Elastic Container Service (ECS) allows running containerized applications on AWS. By default, ECS services can be configured to assign public IP addresses to tasks or services, making them directly accessible from the internet. To enhance security, ECS services should be configured not to automatically assign public IPs, ensuring they remain within a private network and are accessed securely through internal load balancers, VPC peering, or private endpoints.", "AdditionalInformation": "Automatically assigning public IPs to ECS services exposes them to the internet, increasing the risk of unauthorized access, brute-force attacks, and data breaches. Attackers could target publicly exposed containers, exploit vulnerabilities, or disrupt services. To mitigate these risks, ECS services should be restricted to private subnets and accessed through secure networking configurations, such as AWS PrivateLink, VPNs, or internal ALBs.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -920,7 +971,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon Elastic Container Service (ECS) task sets manage multiple versions of a service during deployments. By default, ECS task sets can be configured to automatically assign public IP addresses, making them directly accessible from the internet. To enhance security, ECS task sets should be restricted to private subnets and should not automatically receive public IP addresses unless explicitly required and properly secured.", "AdditionalInformation": "Automatically assigning public IPs to ECS task sets increases the risk of unauthorized access, cyberattacks, and data exposure. Publicly exposed tasks can be targeted by attackers, leading to service disruptions or exploitation of vulnerabilities. To mitigate these risks, ECS task sets should be restricted to private networking environments, accessed only through internal load balancers, VPC endpoints, or secure VPN connections, ensuring controlled and secure communication.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -937,7 +989,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon Elastic File System (EFS) provides scalable, shared file storage for AWS services. EFS mount targets allow instances to connect to the file system within a VPC. By default, EFS mount targets can be configured with public accessibility, making them reachable from the internet. To enhance security, EFS mount targets should be restricted to private networks and should not be publicly accessible unless explicitly required and properly secured.", "AdditionalInformation": "Publicly accessible EFS mount targets expose stored data to unauthorized access, cyberattacks, and data breaches. Attackers could exploit misconfigured security groups or network ACLs to access or modify files. To reduce security risks, EFS mount targets should be restricted to private subnets, with access limited to trusted VPCs, security groups, and IAM roles, ensuring secure file storage and controlled access.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -954,7 +1007,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon Elastic File System (EFS) provides shared storage that can be accessed by multiple EC2 instances and services within a VPC. EFS access is controlled through resource-based policies that define which clients can connect. If an EFS policy allows access to any client within the VPC, it increases the risk of unauthorized access and data exposure. To enhance security, EFS policies should be restricted to specific IAM roles, security groups, or trusted resources instead of granting broad access to all VPC clients.", "AdditionalInformation": "Allowing any client within a VPC to access an EFS file system increases the risk of data leaks, accidental modifications, or unauthorized access by compromised instances or misconfigured services. To minimize exposure, EFS policies should enforce least privilege access, restricting permissions to specific instances, roles, or users that require access, ensuring secure file storage and controlled data access.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -971,7 +1025,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "A Network Policy defines how network traffic is controlled and restricted between workloads within a cloud environment. Enforcing network policies ensures that only authorized communication occurs between services, reducing the risk of unauthorized access and lateral movement. It is recommended to enable Network Policies and configure them appropriately to enforce least privilege access and secure communication between workloads.", "AdditionalInformation": "Without properly configured Network Policies, workloads may be exposed to unnecessary or unauthorized network traffic, increasing the risk of data leaks, exploitation, or lateral movement by attackers. By enabling and enforcing Network Policies, organizations can limit communication between workloads, ensuring that only approved and necessary network interactions are allowed, minimizing the attack surface and enhancing overall security.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -988,7 +1043,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Amazon Elastic Kubernetes Service (EKS) clusters manage containerized applications and can be configured with either private or public access. If an EKS cluster is publicly accessible, it means that the Kubernetes API endpoint can be reached from the internet, increasing the risk of unauthorized access and attacks. To enhance security, EKS clusters should be restricted to private networks and accessed only through secure VPNs, VPC peering, or AWS PrivateLink.", "AdditionalInformation": "Exposing an EKS cluster to the public internet increases the risk of brute-force attacks, credential theft, and unauthorized access to Kubernetes workloads. Attackers could exploit misconfigured RBAC policies or API vulnerabilities to gain control over the cluster. To reduce security risks, EKS clusters should be configured with private endpoints, ensuring that only trusted networks and IAM-authenticated users can manage Kubernetes resources.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1005,7 +1061,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Amazon Elastic Kubernetes Service (EKS) clusters run workloads on worker nodes, which can be either public or private. If EKS clusters are created with public nodes, these nodes are assigned public IP addresses, making them accessible from the internet, which increases the risk of unauthorized access and potential attacks. To enhance security, EKS clusters should be created with private nodes that operate within private subnets and are only accessible through secured networking configurations such as VPNs, VPC peering, or AWS PrivateLink.", "AdditionalInformation": "Using public nodes in EKS exposes Kubernetes workloads to the internet, increasing the risk of unauthorized access, lateral movement, and potential exploitation. Attackers can target misconfigured workloads, open services, or unsecured API endpoints. By creating EKS clusters with private nodes, organizations can restrict access, limit exposure to public threats, and enforce network segmentation, ensuring that workloads remain secure and isolated within a private VPC environment.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1022,7 +1079,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon ElastiCache provides in-memory caching services using Redis and Memcached. By default, ElastiCache clusters can be deployed in either public or private subnets. If an ElastiCache cluster is placed in a public subnet, it becomes accessible from the internet, which significantly increases the risk of unauthorized access and data breaches. To enhance security, ElastiCache clusters should only be deployed in private subnets, ensuring restricted access within a VPC.", "AdditionalInformation": "Deploying an ElastiCache cluster in a public subnet exposes it to external threats, such as unauthorized access, brute-force attacks, and potential data exfiltration. Attackers could exploit misconfigurations to access cached data or disrupt services. By restricting ElastiCache clusters to private subnets, organizations can limit access to trusted resources, enforce VPC security controls, and reduce the attack surface, ensuring secure and efficient caching operations.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1039,7 +1097,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon Elastic Load Balancers (ELBs) distribute incoming traffic across multiple targets, such as EC2 instances, containers, and Lambda functions. By default, ELBs can be configured as either internet-facing or internal (private). If an ELB is publicly accessible, it exposes backend services to the internet, increasing the risk of unauthorized access and attacks. To enhance security, ELBs should be restricted to private networks unless explicitly required and properly secured.", "AdditionalInformation": "Publicly accessible Elastic Load Balancers can serve as entry points for unauthorized traffic, brute-force attacks, and potential data breaches. Attackers may exploit misconfigured security groups, open ports, or exposed application endpoints behind the load balancer. To reduce security risks, ELBs should be configured as internal (private), allowing access only from trusted networks, VPNs, or specific VPCs, ensuring that backend services remain protected and isolated from external threats.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1056,7 +1115,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon Elastic MapReduce (EMR) is a managed big data processing service that can access S3, EC2, and other AWS resources. The EMR Account Public Access Block setting helps prevent public access to EMR resources, such as data stored in S3 buckets. If this setting is not enabled, there is a risk that EMR-related data and configurations could be exposed to the public, leading to unauthorized access or data breaches. To enhance security, the Public Access Block should be enabled for the EMR account.", "AdditionalInformation": "Allowing public access to EMR resources increases the risk of data leaks, unauthorized access, and compliance violations. Attackers could exploit misconfigured policies or publicly accessible S3 buckets to access sensitive data processed by EMR. Enabling EMR Account Public Access Block ensures that S3 data and other EMR-related resources cannot be accessed publicly, reducing exposure and maintaining strong access controls in AWS.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1073,7 +1133,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon Elastic MapReduce (EMR) is a managed service for processing big data workloads using Apache Spark, Hadoop, and other frameworks. By default, EMR clusters can be configured with public or private access. If an EMR cluster is publicly accessible, it exposes data processing nodes and services to the internet, increasing the risk of unauthorized access and potential exploitation. To enhance security, EMR clusters should only be deployed in private subnets and restricted to trusted networks.", "AdditionalInformation": "Publicly accessible EMR clusters increase the risk of data breaches, unauthorized access, and attacks on running workloads. Malicious actors could exploit misconfigured security groups, open ports, or weak authentication settings to compromise the cluster. To reduce exposure, EMR clusters should be placed in private subnets, restricted using VPC security controls, IAM permissions, and firewall rules, ensuring secure data processing and access management.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1090,7 +1151,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "AWS EventBridge is a serverless event bus service that enables communication between AWS services, third-party applications, and custom event sources. By default, EventBridge event buses can be configured to allow events from any AWS account or external source. If an event bus is exposed to everyone, unauthorized entities could send events to your environment, potentially leading to security risks, data injection attacks, or service disruptions. To enhance security, event buses should be restricted to specific AWS accounts, services, or trusted IAM roles.", "AdditionalInformation": "Allowing unrestricted access to an EventBridge event bus increases the risk of malicious event injection, unauthorized access, and data manipulation. Attackers could flood the event bus with malicious events, leading to unexpected behavior, security breaches, or excessive AWS costs. To reduce exposure, event buses should be secured using IAM policies and resource-based permissions, ensuring that only trusted AWS services and accounts can send or receive events.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1107,7 +1169,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon S3 Glacier provides low-cost, long-term storage for archival data. Glacier vaults can be configured with resource-based policies that control access. If a Glacier vault policy allows access to everyone, unauthorized users could retrieve or delete archived data, leading to data exposure or loss. To enhance security, Glacier vault policies should be restricted to specific AWS accounts, IAM roles, or trusted entities, ensuring only authorized users can access or manage archived data.", "AdditionalInformation": "Allowing public access to S3 Glacier vaults poses a significant security risk, increasing the chance of data breaches, unauthorized deletions, or compliance violations. Attackers could restore and download sensitive archived data if the vault is misconfigured. To prevent unauthorized access, Glacier vaults should have strict access controls, using IAM policies, encryption, and resource-based permissions, ensuring that only trusted users and systems can interact with archived data.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -1124,7 +1187,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "AWS Glue Data Catalog is a centralized metadata repository used to store and manage schema information for data lakes and analytics workflows. By default, Glue Data Catalogs can be configured to allow public access, which poses a significant security risk if sensitive metadata is exposed. To enhance security, Glue Data Catalogs should be restricted to specific AWS accounts, IAM roles, or trusted services, ensuring that only authorized users can access or modify catalog information.", "AdditionalInformation": "Allowing public access to Glue Data Catalogs increases the risk of unauthorized access, data leaks, and compliance violations. Attackers could gain insights into an organization’s data structure or modify catalog entries, leading to potential data corruption or unauthorized data exposure. To reduce security risks, Glue Data Catalogs should be secured using IAM policies, resource-based permissions, and AWS Lake Formation, ensuring that only trusted accounts and services can interact with metadata.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1141,7 +1205,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon Managed Streaming for Apache Kafka (MSK) allows organizations to build and manage real-time data streaming applications. If a Kafka cluster is publicly accessible, it exposes data streams, configurations, and messaging topics to the internet, increasing the risk of unauthorized access, data interception, and service disruptions. To enhance security, Kafka clusters should be restricted to private networks, ensuring that only trusted AWS resources, VPCs, and IAM-authenticated users can interact with the service.", "AdditionalInformation": "Exposing a Kafka cluster to the public internet creates significant security risks, including unauthorized data ingestion, data leaks, and message tampering. Attackers could consume, modify, or inject malicious data into Kafka topics, disrupting real-time analytics and application workflows. To mitigate these risks, Kafka clusters should be deployed in private subnets, with access restricted via VPC security groups, IAM policies, and AWS PrivateLink, ensuring secure and controlled data streaming.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1158,7 +1223,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "AWS Key Management Service (KMS) provides secure encryption key management for data encryption and cryptographic operations. If KMS keys are exposed to the internet, unauthorized entities could potentially use, modify, or compromise encryption keys, leading to data breaches and security vulnerabilities. To enhance security, KMS keys should be restricted to trusted AWS accounts, IAM roles, and specific AWS services, ensuring that only authorized users and systems can access and manage them.", "AdditionalInformation": "Exposing KMS keys to the public poses a critical security risk, as compromised keys can lead to unauthorized data decryption, loss of data integrity, and compliance violations. Attackers could potentially use public KMS keys to encrypt or decrypt sensitive data, undermining security controls. To prevent unauthorized access, KMS key policies should enforce strict access control using IAM permissions, VPC endpoint policies, and AWS PrivateLink, ensuring that encryption operations remain fully secured and isolated from the public internet.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1175,7 +1241,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "AWS Lightsail Databases provide managed database solutions for applications. If a Lightsail database is set to public mode, it is directly accessible from the internet, increasing the risk of unauthorized access and data breaches. To enhance security, Lightsail databases should be configured in private mode, ensuring they are accessible only from trusted instances, private networks, or VPN connections.", "AdditionalInformation": "Publicly accessible Lightsail databases expose sensitive data to unauthorized access, brute-force attacks, and potential exploitation. Attackers can attempt to compromise credentials, inject malicious queries, or exfiltrate data. To mitigate these risks, Lightsail databases should remain private, with access controlled through firewalls, IAM authentication, and private networking configurations, ensuring secure database connectivity and data protection.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1192,7 +1259,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "AWS Lightsail instances provide a simple way to deploy and manage cloud-based virtual machines. If a Lightsail instance is publicly accessible, it can be directly reached from the internet, increasing the risk of unauthorized access, attacks, and data breaches. To enhance security, Lightsail instances should be restricted to private access, ensuring they are reachable only through secure connections, such as VPNs, bastion hosts, or private networking configurations.", "AdditionalInformation": "Publicly exposed Lightsail instances create a larger attack surface, making them vulnerable to brute-force attacks, unauthorized access, and exploitation of software vulnerabilities. Attackers could compromise credentials, gain control over the instance, or disrupt services. To mitigate these risks, Lightsail instances should be secured using firewalls, private IP configurations, security group restrictions, and IAM-based access controls, ensuring that only trusted users and networks can connect.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1209,7 +1277,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "AWS MQ brokers manage message queues for applications, facilitating secure and reliable communication between distributed services. If an MQ broker is publicly accessible, it can be reached from the internet, increasing the risk of unauthorized access, message interception, and data breaches. To enhance security, MQ brokers should be restricted to private networks, ensuring they are accessible only from trusted VPCs, private endpoints, or secure VPN connections.", "AdditionalInformation": "Publicly exposed MQ brokers pose a significant security risk, as attackers can attempt to intercept messages, inject malicious data, or disrupt message delivery. This could lead to data manipulation, unauthorized access to sensitive information, and system-wide outages. To mitigate these risks, MQ brokers should be configured within private subnets, with access restricted using security groups, IAM policies, and VPC endpoint controls, ensuring secure and controlled message queue operations.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1226,7 +1295,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon NeptuneDB manual cluster snapshots store backups of graph database clusters, containing sensitive data such as relationships, metadata, and application configurations. By default, NeptuneDB snapshots are private, but they can be manually shared or made public, which can expose critical database information. To enhance security, NeptuneDB manual snapshots should never be publicly accessible, ensuring they are only shared with trusted AWS accounts when necessary.", "AdditionalInformation": "Publicly accessible NeptuneDB snapshots pose a significant security risk, as attackers could restore the snapshot in their own AWS account and gain full access to the database contents. This could lead to data leaks, compliance violations, and unauthorized access to sensitive business information. To prevent data exposure, NeptuneDB snapshots should be restricted using IAM policies and AWS resource-based permissions, ensuring that only authorized users and services can access and manage database backups securely.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -1243,7 +1313,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon Neptune clusters provide a fully managed graph database service designed for applications requiring complex relationship queries. By default, Neptune clusters can be deployed in either public or private subnets. If a Neptune cluster is placed in a public subnet, it becomes accessible from the internet, significantly increasing the risk of unauthorized access and data breaches. To enhance security, Neptune clusters should only be deployed in private subnets, ensuring access is restricted to trusted VPCs, IAM roles, and security group configurations.", "AdditionalInformation": "Deploying a Neptune cluster in a public subnet exposes the database endpoints to external threats, making them vulnerable to brute-force attacks, unauthorized queries, and data exfiltration. Attackers could exploit misconfigurations to gain access to sensitive graph data, leading to potential compliance violations and security incidents. To reduce exposure, Neptune clusters should be restricted to private subnets, with access controlled through VPC security groups, IAM authentication, and private endpoint configurations, ensuring secure database operations and protected data access.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1260,7 +1331,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon OpenSearch (formerly Elasticsearch) domains provide search, analytics, and log management capabilities for applications. If an OpenSearch/Elasticsearch domain is publicly accessible, it can be reached from the internet, exposing sensitive data and administrative controls to unauthorized users. To enhance security, OpenSearch domains should be restricted to private networks, ensuring access is limited to trusted VPCs, IAM roles, or specific security group rules.", "AdditionalInformation": "Publicly accessible OpenSearch/Elasticsearch domains pose a significant security risk, as attackers could execute unauthorized queries, modify data, or gain administrative control over the cluster. This could lead to data breaches, service disruptions, and compliance violations. To mitigate these risks, OpenSearch domains should be deployed in private subnets, with access controlled using VPC restrictions, fine-grained access control (FGAC), IAM policies, and security group rules, ensuring secure and isolated search and analytics operations.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -1277,7 +1349,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon S3 buckets store and manage data, files, and application assets. Bucket policies control access permissions, and if an S3 bucket has a policy that allows WRITE access to everyone, unauthorized users can upload, modify, or delete objects, leading to data tampering, security breaches, or service disruptions. To enhance security, S3 bucket policies should be restricted to specific AWS accounts, IAM roles, or trusted services, ensuring only authorized users have WRITE permissions.", "AdditionalInformation": "Allowing unrestricted WRITE access to an S3 bucket increases the risk of unauthorized modifications, data injection attacks, and accidental data loss. Attackers could upload malicious files, delete critical data, or overwrite important configurations. To prevent unauthorized changes, S3 bucket policies should explicitly deny public WRITE access, enforce least privilege access control, and use AWS Block Public Access settings to ensure secure and controlled data storage.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1294,7 +1367,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon S3 buckets store sensitive data and should have restricted access permissions. If an S3 bucket is listable by Everyone or Any AWS customer, unauthorized users can enumerate the objects within the bucket, potentially exposing sensitive information such as filenames, metadata, or even public datasets. To enhance security, S3 bucket permissions should be configured to restrict LIST access to only authorized IAM roles, AWS accounts, or specific services.", "AdditionalInformation": "Allowing public or AWS-wide LIST access increases the risk of data enumeration, unauthorized access, and information leaks. Attackers or unauthorized users could identify and analyze stored files, extract metadata, or infer sensitive data. To mitigate this risk, S3 bucket policies should explicitly deny public LIST access, enforce least privilege permissions, and use AWS Block Public Access settings to prevent unintended data exposure.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1311,7 +1385,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Amazon S3 buckets should have strict access controls to prevent unauthorized modifications. If an S3 bucket is writable by Everyone or Any AWS customer, it allows unauthorized users to upload, modify, or delete objects, leading to data corruption, security breaches, and compliance risks. To enhance security, S3 bucket permissions should be restricted to trusted IAM roles, AWS accounts, or specific services.", "AdditionalInformation": "Allowing public or AWS-wide WRITE access creates a significant security risk, as attackers can inject malicious files, overwrite critical data, or delete essential objects. This could lead to data loss, malware distribution, or unauthorized system modifications. To prevent unauthorized changes, S3 bucket policies should explicitly deny public WRITE access, enforce least privilege access, and use AWS Block Public Access settings to secure data integrity and prevent unauthorized modifications.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1328,7 +1403,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon SageMaker Notebook instances provide an interactive environment for machine learning development and data analysis. By default, these instances can be configured with direct internet access, which increases the risk of unauthorized access, data leaks, and exposure to malicious external threats. To enhance security, SageMaker Notebook instances should be restricted to private networks, ensuring they are accessed only through secure VPC connections, IAM authentication, or VPNs.", "AdditionalInformation": "Allowing direct internet access to SageMaker Notebook instances poses a significant security risk, as attackers could exploit misconfigurations, exfiltrate data, or inject malicious code. Publicly accessible notebooks can lead to data breaches, intellectual property theft, or compromised model training workflows. To mitigate these risks, SageMaker Notebook instances should be configured within private subnets, with internet access disabled, and restricted using security groups, IAM policies, and VPC endpoint configurations to ensure secure and controlled machine learning operations.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1345,7 +1421,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "AWS Secrets Manager is used to securely store and manage sensitive information, such as API keys, database credentials, and encryption keys. By default, Secrets Manager secrets should be restricted to authorized IAM roles and AWS services. If a secret is publicly accessible, it can be exposed to unauthorized users, leading to data leaks, security breaches, and potential exploitation of sensitive credentials. To enhance security, Secrets Manager secrets should be strictly controlled using IAM policies and resource-based permissions.", "AdditionalInformation": "Allowing public access to Secrets Manager secrets creates a critical security vulnerability, as attackers could retrieve, misuse, or exfiltrate sensitive information. Compromised secrets could lead to unauthorized access to databases, applications, or cloud services, resulting in data breaches, financial loss, or compliance violations. To mitigate this risk, Secrets Manager secrets should be restricted using least privilege IAM permissions, encrypted with AWS KMS, and accessed only by trusted AWS services and roles, ensuring secure and controlled secret management.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1362,7 +1439,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon Simple Email Service (SES) identities (such as email addresses or domains) are used to send and receive emails through AWS. By default, SES identities should be restricted to authorized AWS accounts and IAM roles. If an SES identity is publicly accessible, unauthorized users could send emails using the identity, leading to email spoofing, phishing attacks, or misuse of the domain for malicious purposes. To enhance security, SES identities should be properly restricted using IAM policies and verified senders.", "AdditionalInformation": "Allowing public access to SES identities creates a security and reputational risk, as attackers could impersonate the identity, send spam, or launch phishing campaigns. This could lead to domain blacklisting, compliance violations, and damage to the organization’s email reputation. To mitigate these risks, SES identities should be restricted to trusted AWS accounts and IAM roles, ensuring that only authorized services and users can send emails, protecting the integrity and security of email communications.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1379,7 +1457,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Amazon Simple Queue Service (SQS) queues enable asynchronous message processing between distributed systems. By default, SQS queues should be restricted to authorized AWS accounts and IAM roles. If an SQS queue has a public policy, it allows anyone on the internet to send, receive, or delete messages, leading to data leaks, unauthorized message injection, and potential denial-of-service (DoS) attacks. To enhance security, SQS queue policies should be configured to allow access only to trusted AWS accounts, IAM roles, or specific AWS services.", "AdditionalInformation": "Publicly accessible SQS queues pose a significant security risk, as attackers could inject malicious messages, disrupt processing workflows, or delete critical messages, leading to system failures and data integrity issues. To prevent unauthorized access, SQS policies should explicitly deny public access, enforce least privilege access control, and use IAM policies and VPC endpoint restrictions to ensure secure and controlled messaging operations.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -1396,7 +1475,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "AWS Systems Manager (SSM) Documents define configuration, automation, and maintenance tasks for AWS resources. By default, SSM documents should be restricted to specific AWS accounts, IAM roles, or AWS services. If an SSM document is set as public, unauthorized users could access, modify, or execute automation tasks on AWS infrastructure, leading to misconfigurations, security breaches, or unintended system modifications. To enhance security, SSM documents should be kept private and assigned only to trusted AWS entities.", "AdditionalInformation": "Publicly accessible SSM documents pose a significant security risk, as attackers could execute malicious commands, modify system configurations, or disrupt AWS operations. This could lead to unauthorized access, data leaks, compliance violations, or system downtime. To prevent security threats, SSM documents should explicitly deny public access, enforce least privilege permissions, and use IAM policies and resource-based access controls to ensure only trusted users and systems can manage AWS resources.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1413,7 +1493,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "AWS CloudTrail is a service that records and monitors AWS API calls across an account, providing detailed logs of who performed what action, when, and from where. CloudTrail captures API activity from the AWS Management Console, SDKs, CLI, and AWS services such as CloudFormation. The logs include key details such as the identity of the API caller, timestamp, source IP address, request parameters, and response elements.", "AdditionalInformation": "CloudTrail enhances security, auditing, and compliance by providing a complete history of API activities in an AWS account. Enabling a multi-region trail ensures: Detection of unauthorized activity in rarely used AWS regions. Global Service Logging is automatically enabled, capturing API calls from global services such as IAM and AWS Organizations. Tracking of all management events, ensuring that both read and write operations across AWS resources are recorded for improved security monitoring and compliance.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1430,7 +1511,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "AWS CloudTrail log file validation generates digitally signed digest files containing cryptographic hashes of each log file stored in Amazon S3. These digest files allow users to verify whether logs have been altered, deleted, or remain unchanged after being delivered by CloudTrail. Enabling log file validation ensures data integrity and auditability for security and compliance purposes.", "AdditionalInformation": "Enabling log file validation enhances security by ensuring the integrity of CloudTrail logs, preventing tampering or unauthorized modifications. This helps: Detect log file alterations, ensuring logs remain trustworthy for audits and investigations. Improve compliance with frameworks that require log integrity, such as PCI DSS, SOC 2, and ISO 27001. Strengthen forensic capabilities, allowing security teams to verify log authenticity in case of a security incident.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1447,7 +1529,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "AWS Config is a service that continuously monitors, records, and evaluates configuration changes in AWS resources within an account. It tracks configuration items, relationships between resources, and changes over time, delivering logs for security analysis, change management, and compliance auditing. To ensure comprehensive monitoring, AWS Config should be enabled in all regions.", "AdditionalInformation": "Enabling AWS Config in all regions improves security, visibility, and compliance by: Tracking resource changes, allowing for quick identification of misconfigurations. Supporting security audits and forensic investigations by maintaining a historical record of configurations.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1464,7 +1547,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Server access logging provides detailed records of requests made to an S3 bucket, including request type, accessed resources, timestamp, and requester details. Enabling server access logging on the CloudTrail S3 bucket ensures that all interactions with CloudTrail logs are recorded, improving security visibility and auditability", "AdditionalInformation": "Enabling server access logging on CloudTrail S3 buckets enhances security monitoring, incident response, and compliance by: Capturing all events affecting CloudTrail logs, helping detect unauthorized access or modifications. Providing an audit trail for forensic investigations and compliance reporting. Enhancing security workflows by storing access logs in a separate, dedicated logging bucket for improved log integrity and analysis.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1481,7 +1565,8 @@ "SubSection": "4.2 At-Rest", "AttributeDescription": "AWS CloudTrail records API activity across an AWS account, and its logs contain sensitive security and operational data. AWS Key Management Service (KMS) provides encryption key management using customer-managed keys (CMKs) and Hardware Security Modules (HSMs) to ensure secure key storage and usage. CloudTrail logs can be encrypted using Server-Side Encryption (SSE) with KMS (SSE-KMS) to add an extra layer of protection and access control", "AdditionalInformation": "Using SSE-KMS encryption for CloudTrail logs enhances security by adding an extra layer of access control. This ensures that only authorized users with both S3 read permissions and KMS decryption rights can access log data, protecting sensitive security information from unauthorized access or tampering. It also helps maintain compliance with security and regulatory standards by enforcing strict encryption controls.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1498,7 +1583,8 @@ "SubSection": "3.2 Retention", "AttributeDescription": "AWS Key Management Service (KMS) allows users to manage encryption keys securely. Key rotation enables the automatic replacement of the backing key (the cryptographic material tied to a customer-managed key (CMK)), ensuring continuous security without disrupting access to previously encrypted data. AWS automatically retains previous backing keys to allow seamless decryption of older data while using a newly generated key for encryption. It is recommended to enable key rotation for symmetric CMKs, as asymmetric keys do not support this feature.", "AdditionalInformation": "Regularly rotating encryption keys minimizes the risk associated with key compromise by ensuring that newly encrypted data is protected with a fresh key, reducing the potential impact of an exposed key. Since AWS KMS retains prior backing keys for seamless decryption, rotation does not disrupt access to previously encrypted data. Implementing key rotation enhances security by limiting the exposure window of any single encryption key and aligning with best practices for cryptographic hygiene.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1515,7 +1601,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "VPC Flow Logs capture and record IP traffic information for network interfaces within a VPC, allowing administrators to monitor and analyze network activity. These logs are stored in Amazon CloudWatch Logs for retrieval and analysis. It is recommended to enable VPC Flow Logs for rejected packets to track unauthorized access attempts, misconfigurations, or potential security threats within the VPC.", "AdditionalInformation": "Enabling VPC Flow Logs for rejected traffic enhances network visibility and security monitoring by detecting suspicious activity, failed connection attempts, and potential threats. These logs help identify anomalous traffic patterns, troubleshoot connectivity issues, and support incident response workflows, improving overall security posture.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1532,7 +1619,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "S3 object-level API operations, such as GetObject, PutObject, and DeleteObject, are classified as data events in AWS CloudTrail. By default, CloudTrail does not log data events, meaning detailed tracking of individual object interactions is not enabled. To enhance visibility and security, it is recommended to enable object-level logging for S3 buckets to monitor access and modification activities.", "AdditionalInformation": "Enabling object-level logging helps organizations meet compliance requirements, enhance security monitoring, and detect unauthorized access. It allows administrators to analyze user behavior, track modifications to critical data, and respond to security incidents in real time using Amazon CloudWatch Events, ensuring greater control over S3 bucket activity.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -1549,7 +1637,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "S3 object-level API operations, such as GetObject, PutObject, and DeleteObject, are classified as data events in AWS CloudTrail. By default, CloudTrail does not log data events, meaning individual object interactions are not tracked unless explicitly enabled. To improve security monitoring and compliance, it is recommended to enable object-level logging for S3 buckets.", "AdditionalInformation": "Object-level logging enhances data security, compliance, and operational visibility by providing detailed tracking of who accessed, modified, or deleted objects within S3 buckets. This enables organizations to monitor user behavior, detect unauthorized access, and quickly respond to potential security incidents using Amazon CloudWatch Events.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -1566,7 +1655,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of API calls can be achieved by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can automatically detect and respond to unauthorized API calls, improving security visibility. It is recommended to establish a metric filter and alarm for unauthorized API calls to enhance threat detection and incident response.", "AdditionalInformation": "Monitoring unauthorized API calls helps identify potential security incidents faster, reducing the time attackers have to exploit vulnerabilities. CloudWatch provides real-time monitoring and alerting, while SIEM solutions offer centralized security event analysis. Detecting unauthorized API calls early allows organizations to take immediate action, investigate potential threats, and strengthen overall AWS security posture.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1583,7 +1673,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By setting up metric filters and alarms, organizations can detect and respond to security risks effectively. It is recommended to establish a metric filter and alarm for AWS console logins that are not protected by multi-factor authentication (MFA) to enhance security monitoring.", "AdditionalInformation": "Monitoring console logins without MFA improves visibility into accounts that lack strong authentication controls. Accounts without MFA are more vulnerable to credential theft, brute-force attacks, and unauthorized access. By detecting these login attempts in real-time, organizations can identify security gaps, enforce MFA policies, and reduce the risk of account compromise.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1600,7 +1691,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be achieved by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. Setting up metric filters and alarms helps detect potential security threats. It is recommended to establish a metric filter and alarm for root account login attempts to identify unauthorized access or improper use of the highly privileged root account.", "AdditionalInformation": "Monitoring root account logins enhances visibility into the usage of the most privileged AWS account, which should be used only in exceptional cases. Frequent or unauthorized root logins increase security risks by exposing critical administrative controls. Detecting root login attempts in real time enables organizations to identify potential security incidents, enforce least privilege principles, and limit unnecessary use of the root account.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1617,7 +1709,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can detect critical security events. It is recommended to establish a metric filter and alarm for changes to Identity and Access Management (IAM) policies to track modifications that could affect authentication and authorization controls.", "AdditionalInformation": "Monitoring IAM policy changes helps ensure that access controls remain secure and intact. Unauthorized or unintended modifications to IAM policies can lead to privilege escalation, misconfigurations, and security breaches. Detecting these changes in real-time allows organizations to respond quickly to potential threats, enforce least privilege principles, and maintain a strong security posture.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1634,7 +1727,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can track critical security events. It is recommended to establish a metric filter and alarm to detect changes to CloudTrail configurations, ensuring that logging remains active and tamper-proof.", "AdditionalInformation": "Monitoring CloudTrail configuration changes helps maintain continuous visibility into AWS account activity. Unauthorized modifications to CloudTrail settings could disable or alter logging, potentially allowing malicious activity to go undetected. Detecting these changes in real time enables organizations to quickly respond to threats, enforce security best practices, and ensure compliance with auditing requirements.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1651,7 +1745,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By setting up metric filters and alarms, organizations can detect potential security threats. It is recommended to establish a metric filter and alarm for failed console authentication attempts to identify potential unauthorized access attempts or brute-force attacks.", "AdditionalInformation": "Monitoring failed console logins helps detect brute-force attempts and unauthorized access attempts early. Repeated failed authentication attempts can indicate malicious activity, and tracking them allows security teams to identify suspicious IP addresses, correlate with other security events, and take proactive measures to protect AWS accounts.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1668,7 +1763,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be achieved by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can detect security-critical changes. It is recommended to set up a metric filter and alarm for customer-managed KMS keys (CMKs) that are disabled or scheduled for deletion to prevent unintended encryption key loss.", "AdditionalInformation": "Disabling or deleting a customer-managed CMK can render encrypted data permanently inaccessible, leading to data loss and service disruptions. Monitoring CMK state changes helps detect unauthorized or accidental modifications, ensuring encryption keys remain available and aligned with security policies. Detecting such changes in real time allows organizations to prevent data loss, maintain compliance, and take corrective action if needed.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1685,7 +1781,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can track critical security-related changes. It is recommended to set up a metric filter and alarm for modifications to S3 bucket policies to detect potential misconfigurations or unauthorized access changes.", "AdditionalInformation": "Monitoring S3 bucket policy changes helps detect and respond to overly permissive configurations that could expose sensitive data. Unauthorized or accidental modifications may grant public access or excessive permissions, increasing the risk of data breaches and compliance violations. Real-time alerts allow security teams to quickly identify, investigate, and correct risky policy changes, reducing exposure and strengthening data security.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1702,7 +1799,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by directing CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. By configuring metric filters and alarms, organizations can detect critical configuration changes. It is recommended to establish a metric filter and alarm for modifications to AWS Config’s configurations to ensure continuous monitoring and compliance.", "AdditionalInformation": "Monitoring AWS Config configuration changes helps maintain visibility and control over resource configurations. Unauthorized or accidental modifications to AWS Config settings may result in gaps in security monitoring, misconfigurations going undetected, and compliance violations. Real-time alerts allow security teams to quickly detect, investigate, and respond to changes, ensuring the integrity of configuration tracking across the AWS environment.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1719,7 +1817,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. Security groups act as stateful packet filters that control inbound and outbound traffic within a VPC. It is recommended to establish a metric filter and alarm to detect changes to security groups to prevent unauthorized modifications that could expose resources to security threats.", "AdditionalInformation": "Monitoring security group changes helps ensure that network access controls remain secure and that AWS resources are not unintentionally exposed. Unauthorized or accidental modifications to security groups can create security gaps, increasing the risk of data breaches and unauthorized access. Real-time alerts enable security teams to detect, investigate, and respond to security group changes quickly, maintaining a strong network security posture.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1736,7 +1835,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. Network Access Control Lists (NACLs) act as stateless packet filters that control inbound and outbound traffic for subnets within a VPC. It is recommended to establish a metric filter and alarm to detect changes to NACLs to prevent unauthorized modifications that could compromise network security.", "AdditionalInformation": "Monitoring NACL changes helps ensure that network traffic controls remain properly configured and that AWS resources are not unintentionally exposed. Unauthorized or accidental modifications to NACL rules can lead to misconfigured security policies, increased attack surfaces, and potential data breaches. Real-time alerts enable security teams to detect, investigate, and respond to NACL modifications quickly, maintaining strong network security controls.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1753,7 +1853,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. Network gateways serve as the primary route for traffic entering and leaving a VPC, facilitating communication with external networks. It is recommended to establish a metric filter and alarm for changes to network gateways to ensure that network traffic is securely routed through controlled paths.", "AdditionalInformation": "Monitoring network gateway changes helps maintain secure ingress and egress traffic flows within a VPC. Unauthorized or accidental modifications to network gateways can disrupt connectivity, introduce security vulnerabilities, or expose AWS resources to external threats. Real-time alerts enable security teams to detect, investigate, and respond to changes quickly, ensuring that all traffic follows a controlled and secure routing policy.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1770,7 +1871,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be achieved by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. Route tables determine how network traffic is directed between subnets and network gateways within a VPC. It is recommended to establish a metric filter and alarm for changes to route tables to detect unauthorized or accidental modifications that could impact network security and connectivity.", "AdditionalInformation": "Monitoring route table changes ensures that VPC traffic follows the intended and secure routing paths. Unauthorized modifications can result in misrouted traffic, exposure of sensitive resources, or connectivity disruptions. Real-time alerts enable security teams to detect, investigate, and respond to route table changes promptly, preventing potential security risks and maintaining a controlled and secure network environment.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1787,7 +1889,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be implemented by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. AWS accounts can contain multiple Virtual Private Clouds (VPCs), and VPC peering connections allow network traffic to flow between them. It is recommended to establish a metric filter and alarm for changes made to VPC configurations to detect unauthorized modifications that could impact network security and connectivity.", "AdditionalInformation": "Monitoring VPC configuration changes helps ensure network integrity, security, and proper traffic flow within AWS environments. Unauthorized or accidental modifications can result in misconfigured routing, unintended internet exposure, or connectivity disruptions between resources. Real-time alerts enable security teams to detect, investigate, and respond to VPC changes promptly, preventing security risks and ensuring consistent network accessibility and isolation.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1804,7 +1907,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Real-time monitoring of AWS API calls can be achieved by forwarding CloudTrail logs to Amazon CloudWatch Logs or an external Security Information and Event Management (SIEM) system. AWS Organizations allows centralized management of multiple AWS accounts, and modifications to its configuration can significantly impact access control, account governance, and security policies. It is recommended to establish a metric filter and alarm for changes made to AWS Organizations in the master AWS account to detect unauthorized or unintended modifications.", "AdditionalInformation": "Monitoring AWS Organizations configuration changes helps prevent unwanted, accidental, or malicious modifications that could lead to unauthorized access, policy misconfigurations, or security breaches. Detecting changes in real time ensures that unexpected modifications can be investigated and remediated quickly, reducing the risk of compromised governance structures and ensuring compliance with organizational security policies.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1821,7 +1925,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "AWS Security Hub centralizes security data from multiple AWS services and third-party security tools, allowing for real-time threat detection, risk assessment, and compliance monitoring. When enabled, Security Hub aggregates, organizes, and prioritizes security findings from services such as Amazon GuardDuty, Amazon Inspector, and Amazon Macie, as well as integrated third-party security products. This provides organizations with a unified security management platform to enhance threat visibility.", "AdditionalInformation": "Enabling AWS Security Hub provides a comprehensive view of your security posture, helping to identify vulnerabilities, detect threats, and enforce security best practices. It allows organizations to monitor security trends, benchmark environments against industry standards, and quickly respond to high-priority security issues, strengthening overall AWS security governance and compliance.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1838,7 +1943,8 @@ "SubSection": "3.2 Retention", "AttributeDescription": "AWS CloudWatch Log Groups store logs from various AWS services and applications, enabling monitoring, debugging, and security auditing. By default, CloudWatch logs are retained indefinitely, which can lead to unnecessary data storage costs and compliance risks. To manage log lifecycle effectively, it is recommended to set a retention policy for CloudWatch Log Groups, ensuring logs are retained only for a specific number of days based on operational and compliance requirements.", "AdditionalInformation": "Setting a retention policy for CloudWatch logs helps balance cost management, compliance, and security. Retaining logs for too long increases storage costs and potential exposure to sensitive data, while keeping them for too short a duration can limit forensic investigations and compliance reporting. By defining a specific retention period, organizations can ensure logs are available for troubleshooting and audits while adhering to data retention best practices and regulatory requirements.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] } diff --git a/prowler/compliance/azure/prowler_threatscore_azure.json b/prowler/compliance/azure/prowler_threatscore_azure.json index 12ad818a27..c6bc06b7e4 100644 --- a/prowler/compliance/azure/prowler_threatscore_azure.json +++ b/prowler/compliance/azure/prowler_threatscore_azure.json @@ -17,7 +17,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Microsoft Entra ID Security Defaults offer preconfigured security settings designed to protect organizations from common identity attacks at no additional cost. These settings enforce basic security measures such as MFA registration, risk-based authentication prompts, and blocking legacy authentication clients that do not support MFA. Security defaults are available to all organizations and can be enabled via the Azure portal to strengthen authentication security.", "AdditionalInformation": "Security defaults provide built-in protections to reduce the risk of unauthorized access until organizations configure their own identity security policies. By requiring MFA, blocking weak authentication methods, and adapting authentication challenges based on risk factors, these settings create a stronger security foundation without additional licensing requirements.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -34,7 +35,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "[IMPORTANT] If your organization has Microsoft Entra ID licensing (included in Microsoft 365 E3, E5, F5, or EM&S E3/E5) and can use Conditional Access, you may skip this section and proceed to Conditional Access. Enable multi-factor authentication (MFA) for all users, roles, and groups with write access to Azure resources. This includes both custom-created roles and built-in roles such as: Service Co-Administrators, Subscription Owners, Contributors", "AdditionalInformation": "MFA enhances security by requiring two or more authentication factors, ensuring that only authorized users gain access. It significantly reduces the risk of unauthorized access, credential theft, and privilege escalation. By implementing MFA, attackers must compromise multiple authentication mechanisms, making breaches far more difficult and improving overall Azure resource security.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -51,7 +53,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "[IMPORTANT] If your organization has Microsoft Entra ID licensing (included in Microsoft 365 E3, E5, F5, or EM&S E3/E5) and can use Conditional Access, you may skip this section and proceed to Conditional Access. Enable multi-factor authentication (MFA) for all non-privileged users to enhance security and prevent unauthorized access.", "AdditionalInformation": "MFA strengthens authentication by requiring at least two verification factors, making it significantly harder for attackers to gain access using stolen credentials. Even if one authentication factor is compromised, an additional layer of security reduces the risk of unauthorized account access, enhancing overall identity and access management security.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -68,7 +71,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "This recommendation ensures that users accessing the Windows Azure Service Management API (e.g., Azure PowerShell, Azure CLI, Azure Resource Manager API) are required to authenticate using multi-factor authentication (MFA) before accessing resources.", "AdditionalInformation": "Administrative access to the Azure Service Management API should be secured with enhanced authentication measures to prevent unauthorized changes. Enforcing MFA helps mitigate the risk of credential compromise, privilege abuse, and unauthorized modifications to administrative settings. IMPORTANT: While exceptions for specific users or groups may be configured, they should be carefully tracked and periodically reviewed through an Access Review process. The policy should apply to all users by default, ensuring that only explicitly exempted accounts bypass MFA.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -85,7 +89,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "This recommendation ensures that users accessing Microsoft Admin Portals (such as Microsoft 365 Admin, Microsoft 365 Defender, Exchange Admin Center, and Azure Portal) must authenticate using multi-factor authentication (MFA) before logging in.", "AdditionalInformation": "Microsoft Admin Portals provide privileged access to critical settings and resources, requiring enhanced security measures. Enforcing MFA reduces the risk of unauthorized access, credential compromise, and administrative abuse, preventing intruders from making unauthorized changes to administrative settings.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -102,7 +107,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Ensure that privileged virtual machines do not allow logins from identities without multi-factor authentication (MFA) and that access is restricted to necessary permissions only. Unauthorized access can enable attackers to move laterally and misuse the virtual machine’s managed identity for further privilege escalation or unauthorized operations.", "AdditionalInformation": "Requiring MFA for privileged VM access reduces the risk of credential compromise, lateral movement, and unauthorized access to cloud resources. Attackers can exploit valid accounts to access virtual machines and escalate privileges using the managed identity. Enforcing MFA and least privilege access helps mitigate these risks by preventing unauthorized logins and restricting administrative permissions to only what is necessary.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -119,7 +125,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Restrict the ability to create new Microsoft Entra ID or Azure AD B2C tenants to administrators or appropriately delegated users.", "AdditionalInformation": "Limiting tenant creation to authorized administrators prevents unauthorized users from creating new tenants, reducing the risk of uncontrolled environments, misconfigurations, and potential security gaps. This ensures that only approved users can establish and manage tenant resources.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -136,7 +143,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "To maintain security and operational efficiency, it is recommended to have a minimum of two and a maximum of four users assigned the Global Administrator role in Microsoft Entra ID. This ensures redundancy while minimizing the risk of excessive privileged access.", "AdditionalInformation": "The Global Administrator role holds broad privileges across Microsoft Entra ID services and should not be used for daily tasks. Administrators should have separate accounts for regular activities and privileged actions. Limiting the number of Global Administrators reduces the risk of unauthorized access, human error, and privilege misuse, while ensuring at least two administrators are available to prevent disruptions in case of unavailability. This approach aligns with the principle of least privilege and strengthens the security posture of an Azure tenant.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -153,7 +161,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Restrict guest user permissions to prevent unauthorized access to directory resources and administrative roles.", "AdditionalInformation": "Limiting guest access ensures that guest accounts cannot enumerate users, groups, or directory objects and prevents them from being assigned administrative roles. Guest access has three levels of restriction, with the most secure option being: “Guest user access is restricted to their own directory object” (most restrictive). This setting minimizes the risk of unauthorized access and ensures guests only interact with their assigned resources.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -170,7 +179,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Require administrators to provide consent before applications can access Microsoft Entra ID resources, preventing unauthorized or malicious app integrations.", "AdditionalInformation": "Allowing users to grant application permissions without restrictions increases the risk of data exfiltration and privilege abuse by malicious applications. Restricting app consent to administrators ensures that only verified and approved applications gain access, protecting sensitive data and privileged accounts from unauthorized use.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -187,7 +197,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Allow users to grant consent only for selected permissions when the request comes from a verified publisher, ensuring tighter control over third-party application access.", "AdditionalInformation": "Restricting app consent to verified publishers helps mitigate the risk of unauthorized data access and privilege abuse. Malicious applications may attempt to exploit user-granted permissions, so ensuring only trusted, verified applications can request access enhances security while maintaining flexibility for approved integrations.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -204,7 +215,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Microsoft Entra ID Conditional Access allows organizations to define Named Locations and categorize them as trusted or untrusted. These locations can be based on geographical regions, specific IP addresses, or IP ranges to enhance access control policies.", "AdditionalInformation": "Defining trusted IP addresses or ranges enables organizations to enforce Conditional Access policies based on user location. Users authenticating from trusted locations may receive fewer access restrictions, while those from untrusted sources can be subject to stricter security controls, reducing the risk of unauthorized access.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -221,7 +233,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "Limit the ability to create security groups to administrators only, preventing unauthorized users from managing group memberships.", "AdditionalInformation": "Allowing all users to create and manage security groups can lead to uncontrolled access, misconfigurations, and potential security risks. Unless business requirements justify broader delegation, restricting security group creation to administrators ensures that group permissions and memberships are properly managed, reducing the risk of unauthorized access.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -238,7 +251,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "Restrict the ability to register third-party applications to administrators or appropriately delegated users, ensuring better security control over app integrations.", "AdditionalInformation": "Allowing unrestricted application registration can expose Microsoft Entra ID data to security risks. Requiring administrator approval ensures that custom-developed applications undergo a formal security review before being granted access. Organizations may delegate permissions to developers or high-request users as needed, but policies should be reviewed to align with security best practices and operational needs.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -255,7 +269,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "Limit the ability to send invitations to users with specific administrative roles, preventing unauthorized guest access to cloud resources.", "AdditionalInformation": "Restricting guest invitations to designated administrators helps enforce “Need to Know” permissions, reducing the risk of unintended data exposure. By default, anyone in the organization—including guests and non-admins—can invite external users, which can lead to unauthorized access. Restricting this capability ensures that only approved accounts can extend access to the tenant, strengthening security and access control.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -272,7 +287,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "Limit Microsoft 365 group creation to administrators only, ensuring that group management remains under centralized control.", "AdditionalInformation": "Restricting group creation to administrators prevents unauthorized or unnecessary group creation, ensuring that only appropriate groups are created and managed. This reduces the risk of misconfigurations, access issues, and uncontrolled resource sharing within the organization.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -289,7 +305,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Regularly review Network Security Groups (NSGs) for misconfigured or overly permissive rules, especially those exposing ports and protocols to the public internet. Any unnecessary exposure should be restricted to reduce security risks.", "AdditionalInformation": "Exposing Remote Desktop Protocol (RDP) or other critical services to the internet increases the risk of brute-force attacks, which could lead to unauthorized access to Azure Virtual Machines. Compromised VMs can be used to launch attacks on other networked resources, both inside and outside Azure. Periodic NSG evaluations help minimize attack surfaces and ensure that only explicitly required access is permitted.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -306,7 +323,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Regularly review Network Security Groups (NSGs) for misconfigured or overly permissive rules, particularly those exposing SSH (port 22) or other critical services to the internet. Any unnecessary exposure should be restricted to reduce security risks.", "AdditionalInformation": "Exposing SSH over the internet increases the risk of brute-force attacks, allowing attackers to gain unauthorized access to Azure Virtual Machines. Once compromised, a VM can be used as a pivot point to attack other machines within the Azure Virtual Network or even external systems. Periodic NSG evaluations help minimize attack surfaces and ensure that only explicitly required access is permitted.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -323,7 +341,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Regularly review Network Security Groups (NSGs) for misconfigured or overly permissive UDP rules, especially those exposing UDP-based services to the internet. Any unnecessary exposure should be restricted to prevent security risks.", "AdditionalInformation": "Exposing UDP services to the internet increases the risk of Distributed Denial-of-Service (DDoS) amplification attacks, where attackers can reflect and amplify spoofed traffic from Azure Virtual Machines. Commonly exploited UDP services include DNS, NTP, SSDP, SNMP, and CLDAP, which can be used to disrupt services or launch attacks against other networked systems inside and outside Azure. Regular NSG evaluations help minimize attack surfaces and prevent VMs from being leveraged in DDoS attacks.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -340,7 +359,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Regularly review Network Security Groups (NSGs) for misconfigured or overly permissive HTTP(S) rules, ensuring that exposure to the internet is necessary and narrowly configured. Restrict access where it is not explicitly required.", "AdditionalInformation": "Exposing HTTP(S) services to the internet increases the risk of brute-force attacks, credential stuffing, and exploitation of vulnerabilities in web applications or services. If compromised, an attacker can use the affected resource as a pivot point to escalate privileges, move laterally, and compromise other Azure resources within the tenant. Periodic NSG evaluations help reduce attack surfaces and enforce least privilege access.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -357,7 +377,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Enable Azure Bastion to securely access Azure Virtual Machines (VMs) over the internet without exposing RDP (3389/TCP) or SSH (22/TCP) ports directly to the public network. Azure Bastion provides remote access through TLS (443/TCP) while integrating with Azure Active Directory (AAD) security policies.", "AdditionalInformation": "Using Azure Bastion eliminates the need to assign public IP addresses to VMs, reducing exposure to brute-force attacks and unauthorized access. It enhances security by enabling browser-based RDP/SSH access, leveraging Azure Active Directory controls, and supporting Multi-Factor Authentication (MFA), Conditional Access Policies, and other hardening measures for a centralized and secure access solution.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -374,7 +395,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Use Microsoft Entra authentication for SQL Database to centralize identity and credential management, enhancing security and simplifying access control. By leveraging Microsoft Entra ID, organizations can manage database users, permissions, and authentication policies in a single location, reducing complexity and improving security.", "AdditionalInformation": "Microsoft Entra authentication eliminates the need for separate SQL authentication, preventing identity sprawl and simplifying password management. It enables centralized permission management, supports token-based authentication, integrates with Active Directory Federation Services (ADFS), and enhances security with Multi-Factor Authentication (MFA). Using Entra ID authentication also eliminates the need to store passwords, enabling secure, modern authentication methods while ensuring seamless access control for users and applications.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -391,7 +413,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Disable access from Azure services to PostgreSQL flexible servers to restrict inbound connections and enhance security.", "AdditionalInformation": "Allowing access from all Azure services bypasses firewall restrictions and permits connections from any Azure resource, including those outside your subscription. This can expose the database to unauthorized access and security risks. Instead, configure firewall rules or Virtual Network (VNet) rules to allow only specific, trusted network ranges, ensuring tighter access control.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -408,7 +431,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Restrict Cosmos DB access to whitelisted networks to minimize exposure and reduce potential attack vectors.", "AdditionalInformation": "Limiting Cosmos DB communication to specific trusted networks prevents unauthorized access from unapproved sources, including the public internet. This enhances security by reducing the attack surface and ensuring only designated networks can interact with the database.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -425,7 +449,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Use private endpoints for Cosmos DB to restrict network traffic to approved sources, ensuring secure and private communication.", "AdditionalInformation": "For sensitive data, private endpoints provide granular control over which services can access Cosmos DB, preventing exposure to unauthorized networks. This setup ensures that all traffic remains within a private network, reducing security risks and enhancing data protection.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -442,7 +467,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Use Microsoft Entra ID for Cosmos DB client authentication, leveraging Azure RBAC for enhanced security, centralized management, and MFA support.", "AdditionalInformation": "Entra ID authentication is significantly more secure than token-based authentication, as tokens must be stored persistently on the client, increasing the risk of compromise. Entra ID eliminates this risk by securely handling credentials, integrating seamlessly with Azure RBAC, and supporting multi-factor authentication (MFA) for stronger access control.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -459,7 +485,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Disable public network access for Azure Storage accounts, ensuring that access settings for individual containers cannot override this restriction. This applies to Azure Resource Manager deployment model storage accounts, as classic deployment model accounts will be retired by August 31, 2024.", "AdditionalInformation": "By default, Azure Storage accounts allow users with appropriate permissions to enable public access to containers and blobs, granting read-only access without requiring authentication. To minimize the risk of unauthorized data exposure, public access should be restricted unless explicitly required. Instead, use Azure AD RBAC or shared access signatures (SAS) to grant controlled and time-limited access to storage resources.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -476,7 +503,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Restricting default network access enhances security by preventing unrestricted connections to Azure Storage accounts. By default, storage accounts accept connections from any network, so access should be limited to selected networks by modifying the default configuration.", "AdditionalInformation": "Storage accounts should deny access from all networks by default, except for explicitly allowed Azure Virtual Networks or specific IP address ranges. This approach creates a secure network boundary, ensuring only authorized applications can connect. Even when network rules allow access, proper authentication (via access keys or SAS tokens) is still required, adding an extra layer of security.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -493,7 +521,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "This recommendation assumes that public network access is set to “Enabled from selected virtual networks and IP addresses” and that the default network access rule for storage accounts is set to deny. Some Azure services require access to storage accounts from networks that cannot be explicitly granted permissions through network rules. To allow these services to function properly, enable the trusted Azure services exception, which allows selected Azure services to bypass network restrictions while still enforcing strong authentication.", "AdditionalInformation": "Enabling firewall rules on a storage account blocks access to all incoming requests, including those from other Azure services. Allowing access to trusted Azure services ensures that essential Azure functions, such as Azure Backup, Event Grid, Monitor, and Site Recovery, can interact with storage accounts securely without exposing them to broader public access.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -510,7 +539,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Use private endpoints for Azure Storage accounts to enable secure, encrypted access over a Private Link. Private endpoints assign an IP address from the Virtual Network (VNet) for each service, ensuring that network traffic remains isolated and encrypted within the VNet. This configuration helps segment network traffic, preventing external access while allowing secure communication between services. Additionally, VNets can extend addressing spaces or act as secure tunnels over public networks, connecting remote infrastructures securely.", "AdditionalInformation": "Encrypting traffic between services protects sensitive data from interception and unauthorized access. Using private endpoints ensures that data remains within a controlled network environment, reducing exposure to external threats and enhancing overall security posture.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -527,7 +557,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Ensure that Azure SQL Databases do not allow ingress from 0.0.0.0/0 (ANY IP), as this would expose them to the public internet. Azure SQL Server includes a firewall that blocks unauthorized connections by default, but it allows defining more granular IP address rules to restrict access. Allowing 0.0.0.0/0 effectively grants open access, increasing security risks.", "AdditionalInformation": "Leaving Azure SQL firewall rules open to ANY IP significantly increases the attack surface, making databases vulnerable to brute-force attacks and unauthorized access. Instead, firewall rules should be restricted to specific, trusted IP ranges from known datacenters or internal resources. Additionally, enabling Allow Azure services and resources to access this server could allow access from outside your organization’s subscription or region, potentially bypassing SQL Server’s network ACLs and exposing the database to malicious activity.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -544,7 +575,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Enable Azure App Service Authentication to restrict anonymous HTTP requests and enforce authentication using identity providers before requests reach the application.", "AdditionalInformation": "Enabling App Service Authentication ensures that all incoming HTTP requests are validated and authenticated before being processed by the application. It integrates with Microsoft Entra ID, Google, Facebook, Microsoft Accounts, and Twitter to manage authentication, token validation, and session handling. Additionally, disabling HTTP Basic Authentication helps mitigate risks from legacy authentication methods, strengthening application security.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -561,7 +593,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Ensure that FTP access is disabled for Azure App Services, or if required, enforce FTPS (FTP over SSL/TLS) for secure authentication and data transmission.", "AdditionalInformation": "FTP transmits data, including credentials, in plaintext, making it vulnerable to interception, credential theft, and data exfiltration. Requiring FTPS ensures that all FTP connections are encrypted, reducing the risk of unauthorized access, persistence, and lateral movement within the environment. If FTP is not needed, disabling it entirely enhances security by eliminating an unnecessary attack vector.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -578,7 +611,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Enable log_checkpoints on PostgreSQL flexible servers to log checkpoint activity, generating query and error logs that aid in monitoring and troubleshooting database performance.", "AdditionalInformation": "Logging checkpoints helps track database activity, errors, and performance issues, providing valuable insights for troubleshooting and optimization. While transaction logs remain inaccessible, query and error logs allow identification and resolution of configuration errors and inefficiencies, improving database reliability and performance.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -595,7 +629,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Enable connection throttling on PostgreSQL flexible servers to regulate concurrent connections and generate logs for monitoring and troubleshooting.", "AdditionalInformation": "Connection throttling helps prevent Denial of Service (DoS) attacks by limiting excessive connections that could exhaust resources. It also protects against system failures or degraded performance caused by high user loads. Query and error logs provide insights into connection issues, enabling faster troubleshooting and performance optimization.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -612,7 +647,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Enable log_connections on PostgreSQL Single Servers to record connection attempts and authentication events for security monitoring and auditing. (Note: This recommendation applies only to Single Server, as Azure PostgreSQL Single Server is planned for retirement.)", "AdditionalInformation": "Logging connection attempts helps detect unauthorized access, failed authentication attempts, and potential security threats. These logs provide valuable insights for troubleshooting, auditing, and identifying suspicious activities, enhancing overall database security and monitoring.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -629,7 +665,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Enable log_disconnections on PostgreSQL Servers to record session termination events, including session duration. (Note: This recommendation applies only to Single Server, as Azure PostgreSQL Single Server is planned for retirement.)", "AdditionalInformation": "Logging disconnections provides visibility into session activity and duration, helping to analyze user behavior, detect anomalies, and troubleshoot performance issues. These logs contribute to audit trails, security monitoring, and database performance optimization.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -646,7 +683,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Enable audit_log_enabled on MySQL flexible servers to capture logs for connection attempts, DDL/DML activity, and other database events for security and monitoring purposes.", "AdditionalInformation": "Logging database activity helps detect unauthorized access, troubleshoot issues, and analyze performance bottlenecks. Enabling audit logs enhances security visibility, compliance monitoring, and incident response by providing valuable insights into database operations.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -663,7 +701,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Configure audit_log_events on MySQL flexible servers to include CONNECTION events, ensuring that successful and failed connection attempts are logged.", "AdditionalInformation": "Logging CONNECTION events provides visibility into authentication attempts, helping to detect unauthorized access, troubleshoot issues, and optimize database performance. These logs are essential for security monitoring, compliance, and identifying potential misconfigurations or attack attempts.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -680,7 +719,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Enable AuditEvent logging for Azure Key Vault to track and log interactions with keys, certificates, and other sensitive information.", "AdditionalInformation": "Logging Key Vault access events provides an audit trail that helps monitor who accessed the vault, when, and how. This enhances security, compliance, and incident response by ensuring logs are stored in a designated Azure Storage account or Log Analytics workspace, allowing centralized monitoring across multiple Key Vaults.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -697,7 +737,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Ensure that network flow logs are collected and sent to a central Log Analytics workspace for monitoring and analysis.", "AdditionalInformation": "Capturing network flow logs provides visibility into traffic patterns across your network, helping detect anomalies, potential lateral movement, and security threats. These logs integrate with Azure Monitor and Azure Sentinel, enabling advanced analytics and visualization for improved network security and incident response.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -714,7 +755,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Enable AppServiceHTTPLogs for Azure App Service instances to capture and centrally log all HTTP requests.", "AdditionalInformation": "Logging web requests provides critical data for security monitoring and incident response. Captured logs can be ingested into a SIEM or central log aggregation system, helping security analysts detect anomalies, investigate threats, and enhance application security.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -731,7 +773,8 @@ "SubSection": "3.2 Retention", "AttributeDescription": "Configure SQL Server Audit Retention to retain logs for more than 90 days to ensure long-term visibility into database activity and security events.", "AdditionalInformation": "Maintaining audit logs for over 90 days helps detect anomalies, security breaches, and unauthorized access. Longer retention periods allow organizations to analyze historical data, support compliance requirements, and strengthen forensic investigations.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -748,7 +791,8 @@ "SubSection": "3.2 Retention", "AttributeDescription": "Enable Network Security Group (NSG) Flow Logs and configure the retention period to at least 90 days to capture and store IP traffic data for security monitoring and analysis.", "AdditionalInformation": "NSG Flow Logs provide visibility into network traffic, helping detect anomalies, unauthorized access, and potential security breaches. Retaining logs for at least 90 days ensures that historical data is available for incident investigation, compliance, and forensic analysis, strengthening overall network security monitoring.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -765,7 +809,8 @@ "SubSection": "3.2 Retention", "AttributeDescription": "Ensure that logfiles.retention_days on PostgreSQL flexible servers is set to an appropriate value to maintain access to historical logs for monitoring and troubleshooting.", "AdditionalInformation": "Setting an appropriate log retention period ensures that query and error logs are available for diagnosing configuration issues, troubleshooting errors, and optimizing performance. Retaining logs for a sufficient duration supports security analysis, compliance requirements, and operational debugging while preventing unnecessary storage consumption.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -782,7 +827,8 @@ "SubSection": "3.2 Retention", "AttributeDescription": "Enable Diagnostic Settings to export activity logs for all appropriate resources within a subscription, ensuring long-term visibility into security and operational events.", "AdditionalInformation": "By default, logs are retained for only 90 days. Configuring diagnostic settings allows logs to be exported and stored for extended periods, enabling security analysis, compliance monitoring, and forensic investigations within an Azure subscription.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -799,7 +845,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Enable auditing on Azure SQL Servers to track and log database events for security and compliance purposes.", "AdditionalInformation": "Auditing at the server level ensures that all existing and newly created databases inherit auditing policies, providing consistent monitoring across the SQL environment. It does not override database-level auditing policies but complements them. Audit logs are stored in Azure Storage, helping organizations maintain regulatory compliance, monitor database activity, and detect anomalies that may indicate security threats or operational concerns.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -816,7 +863,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Create an Activity Log Alert for the Delete SQL Server Firewall Rule event to monitor and track firewall rule removals in SQL Server.", "AdditionalInformation": "Monitoring firewall rule deletions helps detect unauthorized or accidental changes that could expose the database to security risks. Timely alerts ensure quick detection and response to prevent unauthorized network access and maintain a secure SQL environment.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -833,7 +881,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Create an Activity Log Alert for the Create or Update Public IP Address event to monitor changes in public IP configurations.", "AdditionalInformation": "Tracking public IP address modifications provides visibility into network access changes, helping detect unauthorized or misconfigured public exposure. Timely alerts enable faster detection and response to potential security risks, ensuring a controlled and secure network environment.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -850,7 +899,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Create an Activity Log Alert for the Delete Public IP Address event to monitor and track the removal of public IP addresses.", "AdditionalInformation": "Monitoring public IP deletions helps detect unauthorized or accidental changes that could impact network accessibility or security. Timely alerts enable quick investigation and response, ensuring that critical network configurations remain intact and secure.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -867,7 +917,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Application Insights in Azure serves as an Application Performance Monitoring (APM) solution, providing valuable insights into application performance, telemetry data, and trace logs. It helps organizations monitor application health and troubleshoot incidents efficiently.", "AdditionalInformation": "Enabling Application Insights enhances security monitoring and performance optimization by collecting metrics, telemetry, and trace logs. These logs aid in proactive performance tuning to reduce costs and support incident response by helping identify the root cause of potential security or operational issues. Integrating Application Insights into a broader logging and monitoring strategy strengthens an organization’s overall observability and security posture.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -884,7 +935,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Enable Network Watcher for all Azure regions within your subscription to monitor and diagnose network activity.", "AdditionalInformation": "Network Watcher provides network diagnostic and visualization tools that help organizations analyze traffic, troubleshoot connectivity issues, and detect anomalies. Enabling it enhances network visibility, security monitoring, and operational insights across Azure environments.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -901,7 +953,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Ensure endpoint protection is installed on all Azure Virtual Machines (VMs) to provide real-time security monitoring and threat detection.", "AdditionalInformation": "Endpoint protection helps detect and remove malware, viruses, and other security threats, protecting VMs from compromise and unauthorized access. It also provides configurable alerts for suspicious activity, enhancing security monitoring and incident response across Azure environments.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -918,7 +971,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Ensure that all Windows and Linux Virtual Machines (VMs) have the latest OS patches and security updates applied to mitigate vulnerabilities and improve system stability.", "AdditionalInformation": "Keeping VMs updated helps address security vulnerabilities, bug fixes, and stability improvements. Microsoft Defender for Cloud continuously checks for missing critical and security updates using Windows Update, WSUS, or Linux package managers. Applying recommended updates reduces the risk of exploits, malware, and performance issues, ensuring a secure and resilient cloud environment.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -935,7 +989,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Ensure that all Microsoft Cloud Security Benchmark (MCSB) policies are enabled to effectively evaluate resource configurations against best practice security recommendations.", "AdditionalInformation": "The MCSB Policy Initiative enforces security best practices and helps ensure compliance with organizational and regulatory requirements. If a policy’s Effect is set to Disabled, it will not be evaluated, potentially preventing administrators from detecting security misconfigurations. Keeping all MCSB policies enabled allows Microsoft Defender for Cloud to assess relevant resources and provide actionable security insights to strengthen cloud security.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -952,7 +1007,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Ensure that security alert emails are enabled for subscription owners to receive notifications about potential security threats and vulnerabilities.", "AdditionalInformation": "Enabling security alert emails ensures that subscription owners are promptly informed of security incidents, threats, or misconfigurations detected by Microsoft Defender for Cloud. Timely notifications allow administrators to take immediate action, mitigate risks, and maintain a strong security posture within the Azure environment.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -969,7 +1025,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Configure Microsoft Defender for Cloud to send high-severity security alerts to a designated security contact email in addition to the subscription owner.", "AdditionalInformation": "By default, Microsoft Defender for Cloud notifies only subscription owners of critical security alerts. Adding a security contact email ensures that the security team is promptly informed of potential threats, allowing for faster response and risk mitigation to maintain a secure Azure environment.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -986,7 +1043,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Ensure that Diagnostic Settings are configured to log relevant control and management plane activities for enhanced visibility and security monitoring. (Note: A Diagnostic Setting must exist for this configuration to be available.)", "AdditionalInformation": "Diagnostic settings determine how logs are exported and stored. Capturing logs from the control and management plane enables proper alerting, monitoring, and analysis of administrative actions, helping to detect unauthorized changes and security threats.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1003,7 +1061,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Enable security alert emails to notify the subscription owner or designated security contacts about potential security threats.", "AdditionalInformation": "Ensuring security alerts are sent to the appropriate personnel allows for timely detection and response to security incidents. This helps mitigate risks by ensuring that critical threats and vulnerabilities are addressed promptly, improving the overall security posture of the Azure environment.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1020,7 +1079,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Enable Microsoft Defender for DNS to monitor and scan outgoing DNS traffic within a subscription for potential security threats. (Note: As of August 1, 2023, new subscribers receive DNS threat alerts as part of Defender for Servers P2.)", "AdditionalInformation": "Defender for DNS analyzes DNS queries against a dynamic threat intelligence list to detect potential malicious activity, compromised services, or security breaches. Monitoring DNS lookups helps identify and prevent threats such as data exfiltration, command and control (C2) communications, and phishing attacks, improving overall network security.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1037,7 +1097,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Create an activity log alert for the Create Policy Assignment event to track changes in Azure Policy assignments.", "AdditionalInformation": "Monitoring policy assignment events helps detect unauthorized or unintended changes in Azure policies, providing greater visibility and reducing the time required to identify and respond to policy modifications.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1054,7 +1115,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Create an activity log alert for the Delete Policy Assignment event to monitor and track policy removal activities in Azure Policy assignments.", "AdditionalInformation": "Monitoring policy deletions helps detect unauthorized or unintended changes, ensuring policy integrity and reducing the time required to identify and respond to security or compliance violations.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1071,7 +1133,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Create an Activity Log Alert for the Create or Update Network Security Group (NSG) event to track changes in network security configurations.", "AdditionalInformation": "Monitoring NSG creation and updates provides visibility into network access changes, helping to detect unauthorized modifications and identify potential security risks in a timely manner.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1088,7 +1151,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Create an Activity Log Alert for the Delete Network Security Group (NSG) event to monitor and track network security group removals.", "AdditionalInformation": "Monitoring NSG deletions provides visibility into network access changes, helping to detect unauthorized modifications and identify potential security threats before they impact the environment.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1105,7 +1169,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Create an Activity Log Alert for the Create or Update Security Solution event to track modifications to security solutions in your environment.", "AdditionalInformation": "Monitoring security solution changes provides visibility into updates, additions, or modifications to active security configurations. This helps detect unauthorized changes or suspicious activity and ensures that security controls remain properly configured.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1122,7 +1187,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Create an Activity Log Alert for the Delete Security Solution event to monitor and track the removal of security solutions in your environment.", "AdditionalInformation": "Monitoring security solution deletions helps detect unauthorized removals or suspicious activity, ensuring that critical security controls are not accidentally or maliciously disabled. This improves security visibility and helps maintain a strong security posture.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1139,7 +1205,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Create an Activity Log Alert for the Create or Update SQL Server Firewall Rule event to track changes in SQL Server firewall configurations.", "AdditionalInformation": "Monitoring firewall rule modifications provides visibility into network access changes, helping detect unauthorized updates that could expose the database to security risks. Timely alerts can reduce the time needed to identify and respond to suspicious activity, ensuring a secure SQL environment.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1156,7 +1223,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "Enable require_secure_transport on PostgreSQL flexible servers to enforce SSL connections between the database server and client applications, ensuring encrypted communication.", "AdditionalInformation": "Requiring SSL encryption helps protect data in transit from man-in-the-middle attacks by securing the connection between the database server and client applications. Enforcing secure transport prevents unauthorized access and strengthens data integrity and confidentiality.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1173,7 +1241,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "Enable require_secure_transport on MySQL flexible servers to enforce SSL encryption between the database server and client applications, ensuring secure communication.", "AdditionalInformation": "Requiring SSL connectivity protects data in transit from man-in-the-middle attacks by encrypting communication between client applications and the database server. Enforcing secure transport helps prevent unauthorized access and data interception, strengthening the overall security posture of the database.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1190,7 +1259,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "Ensure that the minimum TLS version for Azure Storage is set to TLS 1.2 or higher to mitigate security vulnerabilities associated with older TLS versions.", "AdditionalInformation": "TLS 1.0 is outdated and has known security vulnerabilities that can expose data in transit to attacks and interception. Upgrading to TLS 1.2 or later enhances encryption security, ensuring secure communication between clients and Azure Storage while reducing the risk of data compromise.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -1207,7 +1277,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "Configure Azure App Service to enforce HTTPS-only traffic, ensuring that all HTTP requests are automatically redirected to HTTPS for secure communication.", "AdditionalInformation": "Enforcing HTTPS protects data in transit by using TLS/SSL encryption, preventing man-in-the-middle attacks and ensuring secure authentication. Redirecting non-secure HTTP traffic to HTTPS enhances the confidentiality and integrity of application communications, reducing exposure to security vulnerabilities.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1224,7 +1295,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "Ensure that Azure App Service is configured to use TLS 1.2 or higher to secure data transmission and align with industry security standards.", "AdditionalInformation": "TLS 1.0 and 1.1 are outdated protocols with known vulnerabilities that expose web applications to security threats, including data interception and man-in-the-middle attacks. TLS 1.2 is the recommended encryption standard by industry frameworks such as PCI DSS, providing stronger encryption and improved security for web app connections.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1241,7 +1313,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "Ensure that Azure App Service is configured to use the latest supported HTTP version to benefit from security improvements, performance enhancements, and new functionalities.", "AdditionalInformation": "Newer HTTP versions provide security enhancements, performance optimizations, and better resource management. HTTP/2, for example, improves efficiency by addressing issues such as head-of-line blocking, header compression, and request prioritization. Keeping apps updated to the latest HTTP version ensures they leverage modern security protocols and enhanced data transmission capabilities while maintaining compatibility and performance standards.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -1258,7 +1331,8 @@ "SubSection": "4.2 At-Rest", "AttributeDescription": "Enable encryption at rest using Customer Managed Keys (CMK) instead of Microsoft Managed Keys to maintain greater control over sensitive data encryption in Azure Storage accounts.", "AdditionalInformation": "By default, Azure encrypts all storage resources (blobs, disks, files, queues, and tables) using Microsoft Managed Keys. However, using Customer Managed Keys (CMK) allows organizations to control, manage, and rotate their encryption keys through Azure Key Vault, ensuring compliance with security policies. CMKs also support automatic key version updates for enhanced security. Since this recommendation applies specifically to storage accounts holding critical data, its assessment remains manual rather than automated.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1275,7 +1349,8 @@ "SubSection": "4.2 At-Rest", "AttributeDescription": "Enable Transparent Data Encryption (TDE) with Customer-Managed Keys (CMK) to enhance control, security, and separation of duties over the TDE Protector. TDE encrypts data at rest using a database encryption key (DEK). Traditionally, DEKs were protected by Azure SQL Service-managed certificates, but with Customer-Managed Key support, the DEK can now be secured using an asymmetric key stored in Azure Key Vault. Azure Key Vault provides centralized key management, FIPS 140-2 Level 2 validated HSMs, and additional security through key and data separation. Based on business needs and data sensitivity, organizations should use Customer-Managed Keys for TDE encryption.", "AdditionalInformation": "Using Customer-Managed Keys for TDE gives organizations full control over encryption keys, including who can access them and when. Azure Key Vault serves as a secure external key management system, ensuring that TDE encryption keys remain protected from unauthorized access. When configured, the asymmetric key is set at the server level and inherited by all databases under that server, providing consistent encryption management across the environment.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1292,7 +1367,8 @@ "SubSection": "4.2 At-Rest", "AttributeDescription": "Enable Transparent Data Encryption (TDE) on all Azure SQL Servers to ensure real-time encryption and decryption of databases, backups, and transaction logs at rest. TDE operates seamlessly without requiring modifications to applications.", "AdditionalInformation": "TDE helps protect Azure SQL Databases from unauthorized access and malicious activity by encrypting data at rest. This ensures that database files, backups, and logs remain secure, reducing the risk of data breaches while maintaining operational transparency.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -1309,7 +1385,8 @@ "SubSection": "4.2 At-Rest", "AttributeDescription": "Configure storage accounts used for activity log exports to use Customer Managed Keys (CMK) for enhanced security and access control.", "AdditionalInformation": "Using CMKs for log storage provides additional confidentiality controls, ensuring that only users with read access to the storage account and explicit decrypt permissions can access the logs. This enhances data security by restricting unauthorized access and strengthening compliance with encryption and access management policies.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] } diff --git a/prowler/compliance/gcp/prowler_threatscore_gcp.json b/prowler/compliance/gcp/prowler_threatscore_gcp.json index f21949095f..ed60fed075 100644 --- a/prowler/compliance/gcp/prowler_threatscore_gcp.json +++ b/prowler/compliance/gcp/prowler_threatscore_gcp.json @@ -17,7 +17,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Service account keys consist of a key ID (private_key_id) and a private key, which are used to authenticate programmatic requests to Google Cloud services. It is recommended to regularly rotate service account keys to enhance security and reduce the risk of unauthorized access.", "AdditionalInformation": "Regularly rotating service account keys minimizes the risk of a compromised, lost, or stolen key being used to access cloud resources. Google-managed keys are automatically rotated daily for internal authentication, ensuring strong security. For user-managed (external) keys, users are responsible for key security, storage, and rotation. Since Google does not retain private keys once generated, proper key management practices must be followed. Google Cloud allows up to 10 external keys per service account, making it easier to rotate them without disruption. Implementing regular key rotation ensures that old keys are not left active, reducing the potential attack surface.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -34,7 +35,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "API keys should only be used when no other authentication method is available, as they pose significant security risks. Unused API keys with active permissions may still exist within a project, potentially exposing resources to unauthorized access. It is recommended to use standard authentication flows such as OAuth 2.0 or service account authentication instead.", "AdditionalInformation": "API keys are inherently insecure because they: Are simple encrypted strings that can be easily exposed in browsers, client-side applications, or devices. Do not authenticate users or applications making API requests. Can be accidentally leaked in logs, repositories, or web traffic.To enhance security, API keys should be avoided when possible, and unused keys should be deleted to minimize the risk of unauthorized access.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -51,7 +53,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "API keys should only be used when no other authentication method is available. If API keys are in use, it is recommended to rotate them every 90 days to minimize security risks.", "AdditionalInformation": "API keys are inherently insecure because: They are simple encrypted strings that can be easily exposed. They do not authenticate users or applications making API requests. They are often accessible to clients, increasing the risk of theft and misuse. Unlike credentials with expiration policies, stolen API keys remain valid indefinitely unless revoked or regenerated. Regularly rotating API keys reduces the risk of unauthorized access by ensuring that compromised keys cannot be used for extended periods. To enhance security, API keys should be rotated every 90 days or as part of a proactive security policy.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -68,7 +71,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Service accounts should not use user-managed keys, as they introduce security risks and require manual management. Instead, use Google Cloud-managed keys, which are automatically rotated and secured by Google.", "AdditionalInformation": "User-managed keys are downloadable and manually managed, making them vulnerable to leaks, mismanagement, and unauthorized access. In contrast, GCP-managed keys are non-downloadable, automatically rotated weekly, and securely handled by Google Cloud services like App Engine and Compute Engine. Managing user-generated keys requires key storage, distribution, rotation, revocation, and protectionall of which introduce potential security gaps. Common risks include keys being exposed in source code repositories, left in unsecured locations, or unintentionally shared. To minimize security risks, it is recommended to disable user-managed service account keys and rely on GCP-managed keys instead.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -85,7 +89,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "A service account is a special Google account assigned to an application or virtual machine (VM) rather than an individual user. It is used to authenticate API requests on behalf of the application. Service accounts should not be granted admin privileges to minimize security risks.", "AdditionalInformation": "Service accounts control resource access based on their assigned roles. Granting admin privileges to a service account allows full control over applications or VMs, enabling actions like deletion, updates, and configuration changes without user intervention. This increases the risk of misconfigurations, privilege escalation, or potential security breaches. To follow the principle of least privilege, it is recommended to restrict admin access for service accounts and assign only the necessary permissions.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -102,7 +107,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "The IAM policy on Cloud KMS cryptographic keys should not allow anonymous (allUsers) or public (allAuthenticatedUsers) access to prevent unauthorized key usage.", "AdditionalInformation": "Granting permissions to allUsers or allAuthenticatedUsers allows anyone to access the cryptographic keys, which can lead to data exposure, unauthorized encryption/decryption operations, or potential key compromise. This is particularly critical if sensitive data is protected using these keys. To maintain data security and compliance, ensure that Cloud KMS cryptographic keys are only accessible to authorized users, groups, or service accounts and do not have public or anonymous access permissions.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -119,7 +125,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Google Cloud Key Management Service (KMS) organizes cryptographic keys in a hierarchical structure to facilitate secure and efficient access control. Keys should be configured with a defined rotation schedule to ensure their cryptographic strength is maintained over time.", "AdditionalInformation": "Key rotation ensures that new key versions are automatically generated at regular intervals, reducing the risk of key compromise and unauthorized access. The key material (actual encryption bits) changes over time, even though the keys logical identity remains the same. Since cryptographic keys protect sensitive data, setting a specific rotation period ensures that encrypted data remains secure, minimizes the impact of a potential key leak, and aligns with best security practices.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -136,7 +143,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "The principle of Separation of Duties should be enforced when assigning Google Cloud Key Management Service (KMS) roles to users. This prevents excessive privileges and reduces security risks.", "AdditionalInformation": "The Cloud KMS Admin role grants the ability to create, delete, and manage keys, while the Cloud KMS CryptoKey Encrypter/Decrypter, Encrypter, and Decrypter roles control encryption and decryption of data. Granting both administrative and cryptographic privileges to the same user violates the Separation of Duties principle, potentially allowing unauthorized access to sensitive data. To mitigate risks and prevent privilege escalation, no user should hold the Cloud KMS Admin role along with any of the CryptoKey Encrypter/Decrypter roles. Enforcing Separation of Duties helps ensure secure key management and aligns with security best practices.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -153,7 +161,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "API keys should only be used when no other authentication method is available, as they pose a higher security risk due to their public visibility. To minimize exposure, API keys should be restricted to access only the specific APIs required by an application.", "AdditionalInformation": "API keys present several security risks, including: They are simple encrypted strings that can be easily exposed in client-side applications or browsers. They do not authenticate the user or application making API requests. They are often accessible to clients, making them susceptible to discovery and theft. Google recommends using standard authentication methods instead of API keys whenever possible. However, in limited scenarios where API keys are necessary (e.g., mobile applications using Google Cloud Translation API without a backend server), restricting API key access to only the required APIs helps enforce least privilege access and reduces attack surfaces.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -170,7 +179,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "It is recommended to assign the Service Account User (iam.serviceAccountUser) and Service Account Token Creator (iam.serviceAccountTokenCreator) roles to users at the service account level rather than granting them project-wide access.", "AdditionalInformation": "Service accounts are identities used by applications and virtual machines (VMs) to interact with Google Cloud APIs. They also function as resources with IAM policies defining who can use them. Granting service account permissions at the project level allows users to access all service accounts within the project, including any created in the future. This increases the risk of privilege escalation, as users with Compute Instance Admin or App Engine Deployer roles could execute code as a service account, gaining access to additional resources. To enforce the principle of least privilege, users should be assigned service account roles at the specific service account level rather than at the project level. This ensures that each user has access only to the necessary service accounts while preventing unintended privilege escalation.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -187,7 +197,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "It is recommended to enforce the principle of Separation of Duties when assigning service account-related IAM roles to users to prevent excessive privileges and security risks.", "AdditionalInformation": "The Service Account Admin role allows a user to create, delete, and manage service accounts, while the Service Account User role allows a user to assign service accounts to applications or compute instances. Granting both roles to the same user violates the Separation of Duties principle, as it would allow an individual to create and assign service accounts, potentially leading to unauthorized access or privilege escalation. To minimize security risks, no user should be assigned both Service Account Admin and Service Account User roles simultaneously. Enforcing Separation of Duties ensures better access control, reduces the risk of privilege abuse, and aligns with security best practices.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -204,7 +215,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "Cloud Audit Logging should be configured to track all administrative activities and read/write access to user data. This ensures comprehensive visibility into who accessed or modified resources within a project, folder, or organization.", "AdditionalInformation": "Cloud Audit Logging maintains two types of audit logs: 1. Admin Activity Logs Captures API calls and administrative actions that modify configurations or metadata. These logs are enabled by default and cannot be disabled. 2. Data Access Logs Tracks API calls that create, modify, or read user data. These logs are disabled by default and should be enabled for better monitoring. Data Access Logs provide three types of visibility: Admin Read Tracks metadata or configuration reads. Data Read Logs operations where user-provided data is accessed. Data Write Captures modifications to user-provided data.To ensure effective logging, it is recommended to: 1. Enable DATA_READ logs (for user activity tracking) and DATA_WRITE logs (to track modifications). 2. Apply audit logging to all supported services where Data Access logs are available. 3.Avoid exempting users from audit logs to maintain full tracking capabilities. Properly configuring Cloud Audit Logging helps strengthen security, detect unauthorized access, and ensure compliance with security policies.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -221,7 +233,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "In order to prevent unnecessary project ownership assignments to users or service accounts and mitigate potential misuse of projects and resources, all role assignments to roles/Owner should be monitored. Users or service accounts assigned the roles/Owner primitive role are considered project owners. The Owner role grants full control over the project, including: full viewer permissions on all GCP services, permissions to modify the state of all services, manage roles and permissions for the project and its resources, and set up billing for the project. Granting the Owner role allows the member to modify the IAM policy, which contains sensitive access control data. To minimize security risks, the Owner role should only be assigned when strictly necessary, and the number of users with this role should be kept to a minimum.", "AdditionalInformation": "Project ownership has the highest level of privileges within a project, making it a high-risk role if misused. To reduce potential security risks, all project ownership assignments and changes should be monitored and alerted to security teams or relevant recipients. Critical events to monitor include: sending project ownership invitations, acceptance or rejection of ownership invites, assigning the roles/Owner role to a user or service account, and removing a user or service account from the roles/Owner role. Monitoring these activities helps prevent unauthorized access, enforces least privilege principles, and improves security auditing and compliance.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -238,7 +251,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "Google Cloud Platform (GCP) services generate audit log entries in the Admin Activity and Data Access logs, providing visibility into who performed what action, where, and when within GCP projects. These logs capture key details such as the identity of the API caller, timestamp, source IP address, request parameters, and response data. Cloud audit logging records API calls made through the GCP Console, SDKs, command-line tools, and other GCP services, offering a comprehensive activity history for security monitoring and compliance.", "AdditionalInformation": "Admin activity and data access logs play a critical role in security analysis, resource change tracking, and compliance auditing. Configuring metric filters and alerts for audit configuration changes ensures that audit logging remains in its recommended state, allowing organizations to detect and respond to unauthorized modifications while ensuring all project activities remain fully auditable at any time.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -255,7 +269,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "It is recommended to set up a metric filter and alarm to track changes to Identity and Access Management (IAM) roles, including their creation, deletion, and updates. Google Cloud IAM provides predefined roles for granular access control but also allows organizations to create custom roles to meet specific needs.", "AdditionalInformation": "IAM role modifications can impact security by granting excessive privileges if not properly managed. Monitoring role creation, deletion, and updates helps detect potential misconfigurations or over-privileged roles early, ensuring that only intended access permissions are assigned within the organization.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -272,7 +287,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "A project should not have a default network to prevent the use of preconfigured and potentially insecure network settings.", "AdditionalInformation": "The default network automatically creates permissive firewall rules, including unrestricted internal traffic, SSH, RDP, and ICMP access, which increases the risk of unauthorized access. Additionally, it is an auto mode network, limiting flexibility in subnet configuration and restricting the use of Cloud VPN or VPC Network Peering. Organizations should create a custom network tailored to their security and networking needs and remove the default network to minimize exposure.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -289,7 +305,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "Projects should not have a legacy network configured to prevent the use of outdated and inflexible networking models. While new projects can no longer create legacy networks, older projects should be checked to ensure they are not still using them.", "AdditionalInformation": "Legacy networks use a single global IPv4 prefix and a single gateway IP for the entire network, lacking subnetting capabilities. This design limits flexibility, prevents migration to auto or custom subnet networks, and can create performance bottlenecks or single points of failure for high-traffic workloads. Removing legacy networks and transitioning to modern networking models improves scalability, security, and resilience.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -306,7 +323,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "GCP Firewall Rules control ingress and egress traffic within a VPC Network. These rules define traffic conditions such as ports, protocols, and source/destination IPs. Firewall rules operate at the VPC level and cannot be shared across networks. Only IPv4 addresses are supported, and it is crucial to restrict generic (0.0.0.0/0) incoming traffic, particularly for SSH on Port 22, to prevent unauthorized access.", "AdditionalInformation": "Firewall rules regulate traffic flow between instances and external networks. Allowing unrestricted inbound SSH access (0.0.0.0/0 on port 22) increases security risks by exposing instances to unauthorized access and brute-force attacks. To minimize threats, internet-facing access should be limited by specifying granular IP ranges and enforcing least privilege access.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -323,7 +341,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "GCP Firewall Rules control incoming (ingress) and outgoing (egress) traffic within a VPC Network. Each rule specifies traffic conditions, including ports, protocols, and source/destination IPs. These rules operate at the VPC level, cannot be shared across networks, and support only IPv4 addresses. To enhance security, unrestricted RDP access (0.0.0.0/0 on port 3389) should be avoided to prevent unauthorized remote connections.", "AdditionalInformation": "Firewall rules regulate traffic flow between instances and external networks. Allowing unrestricted RDP access from the Internet exposes virtual machines (VMs) to unauthorized access and brute-force attacks. To mitigate risks, internet-facing access should be restricted by enforcing least privilege access, defining specific IP ranges, and implementing secure remote access solutions such as Bastion hosts or VPNs.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -340,7 +359,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "IAM policies on Cloud Storage buckets should not allow anonymous or public access to prevent unauthorized data exposure.", "AdditionalInformation": "Granting public or anonymous access allows anyone to access the buckets contents, posing a security risk, especially if sensitive data is stored. Restricting access ensures that only authorized users can interact with the bucket, reducing the risk of data breaches.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -357,7 +377,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Verify the user connection limits for Cloud SQL SQL Server instances to ensure they are not unnecessarily restricting the number of simultaneous connections.", "AdditionalInformation": "The user connections setting controls the maximum number of concurrent user connections allowed on an SQL Server instance. By default, SQL Server dynamically adjusts the number of connections as needed, up to a maximum of 32,767. Setting an artificial limit may prevent new connections from being established, leading to potential data loss or service outages. It is recommended to review and adjust this setting as necessary to avoid disruptions.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -374,7 +395,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Disable the remote access database flag for Cloud SQL SQL Server instances to prevent execution of stored procedures from remote servers.", "AdditionalInformation": "The remote access option allows stored procedures to be executed from or on remote SQL Server instances. By default, this setting is enabled, which could be exploited for unauthorized query execution or Denial-of-Service (DoS) attacks by offloading processing to a target server. Disabling remote access enhances security by restricting stored procedure execution to the local server, reducing potential attack vectors. This recommendation applies to SQL Server database instances.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -391,7 +413,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Restrict database server access to only trusted networks and IP addresses, preventing connections from public IPs.", "AdditionalInformation": "Allowing unrestricted access to a database server increases the risk of unauthorized access and attacks. To minimize the attack surface, only trusted and necessary IP addresses should be whitelisted. Authorized networks should not be set to 0.0.0.0/0, which permits connections from anywhere. This control applies specifically to instances with public IP addresses.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -408,7 +431,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Configure Second Generation Cloud SQL instances to use private IPs instead of public IPs.", "AdditionalInformation": "Using private IPs for Cloud SQL databases enhances security by reducing exposure to external threats. It also improves network performance and lowers latency by keeping traffic within the internal network, minimizing the attack surface of the database.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -425,7 +449,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Ensure that IAM policies on BigQuery datasets do not allow anonymous or public access.", "AdditionalInformation": "Granting access to allUsers or allAuthenticatedUsers permits unrestricted access to the dataset, which can lead to unauthorized data exposure. To protect sensitive information, public or anonymous access should be strictly prohibited.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -442,7 +467,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "To enforce the principle of least privilege and prevent potential privilege escalation, instances should not be assigned the Compute Engine default service account with the scope Allow full access to all Cloud APIs.", "AdditionalInformation": "Google Compute Engine provides a default service account for instances to access necessary cloud services. This default service account has the Project Editor role, granting broad permissions over most cloud services except billing. When assigned to an instance, it can operate in three modes: 1.Allow default access Grants minimal required permissions (recommended). 2.Allow full access to all Cloud APIs Grants excessive access to all cloud services (not recommended). 3.Set access for each API Allows administrators to specify required APIs (preferred for least privilege). Assigning an instance the Compute Engine default service account with full access to all APIs can expose cloud operations to unauthorized users based on IAM roles. To reduce security risks, instances should use custom service accounts with minimal required permissions.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -459,7 +485,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Instances should use instance-specific SSH keys instead of project-wide SSH keys to enhance security and reduce the risk of unauthorized access.", "AdditionalInformation": "Project-wide SSH keys are stored in Compute Project metadata and can be used to access all instances within a project. While this simplifies SSH key management, it also increases security risksif a project-wide SSH key is compromised, all instances in the project could be affected. Using instance-specific SSH keys provides better security by limiting access to individual instances, reducing the attack surface in case of key compromise.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -476,7 +503,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "The interactive serial console allows direct access to a virtual machines serial ports, similar to using a terminal window. When enabled, it allows connections from any IP address, creating a potential security risk. It is recommended to disable interactive serial console support.", "AdditionalInformation": "A virtual machine instance has four virtual serial ports, often used by the operating system, BIOS, or other system-level entities for input and output. The first serial port (serial port 1) is commonly referred to as the serial console. Unlike SSH, the interactive serial console does not support IP-based access restrictions, meaning anyone with the correct SSH key, username, project ID, zone, and instance name could gain access. This exposes the instance to unauthorized access. To mitigate this risk, interactive serial console support should be disabled unless absolutely necessary.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -493,7 +521,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Google Compute Engine instances should not forward data packets unless explicitly required for routing purposes. By default, an instance cannot forward packets unless the source IP matches the instances IP address. Similarly, GCP wont deliver packets if the destination IP does not match the instance. To prevent unauthorized data forwarding, it is recommended to disable IP forwarding.", "AdditionalInformation": "When IP forwarding is enabled (canIpForward field), an instance can send and receive packets with non-matching source or destination IPs, effectively allowing it to act as a network router. This can lead to data loss, information disclosure, or unauthorized traffic routing. To maintain security and prevent misuse, IP forwarding should be disabled unless explicitly required for network routing configurations.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -510,7 +539,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Shielded VMs are hardened virtual machines on Google Cloud Platform (GCP) designed to protect against rootkits, bootkits, and other low-level attacks. They ensure verifiable integrity using Secure Boot, virtual Trusted Platform Module (vTPM)-enabled Measured Boot, and integrity monitoring.", "AdditionalInformation": "Shielded VMs use signed and verified firmware from Googles Certificate Authority to establish a root of trust. Secure Boot ensures only authentic software runs by verifying digital signatures, preventing unauthorized modifications. Integrity monitoring helps detect unexpected changes in the VMs boot process, while vTPM-enabled Measured Boot provides a baseline to compare against future boots. Enabling Shielded VMs enhances security by protecting against malware, unauthorized firmware changes, and persistent threats.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -527,7 +557,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "Compute instances should not be assigned external IP addresses to minimize exposure to the internet and reduce security risks.", "AdditionalInformation": "Public IP addresses increase the attack surface of Compute instances, making them more vulnerable to threats. Instead, instances should be placed behind load balancers or use private networking to control access and reduce the risk of unauthorized exposure.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -544,7 +575,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "It is recommended to create a log sink to export and store copies of all log entries. This enables log aggregation across multiple projects and allows integration with a Security Information and Event Management (SIEM) system for centralized monitoring.", "AdditionalInformation": "Cloud Logging retains logs for a limited period. To ensure long-term storage and better security analysis, logs should be exported to a destination such as Cloud Storage, BigQuery, or Cloud Pub/Sub. A log sink allows you to: Aggregate logs from multiple projects, folders, or billing accounts. Extend log retention beyond Cloud Loggings default retention period. Send logs to a SIEM system for real-time monitoring and threat detection. To ensure all logs are captured and exported: 1.Create a sink without filters to capture all log entries. 2.Choose an appropriate destination (e.g., Cloud Storage for long-term storage, BigQuery for analysis, or Pub/Sub for real-time processing). 3.Apply logging at the organization level to cover all associated projects. Implementing log sinks enhances security visibility, forensic capabilities, and compliance adherence across cloud environments.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -561,7 +593,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "It is recommended to configure a metric filter and alarm to monitor Virtual Private Cloud (VPC) Network Firewall rule changes. Tracking modifications to firewall rules helps ensure that unauthorized or unintended changes do not compromise network security.", "AdditionalInformation": "Firewall rules control ingress and egress traffic within a VPC. Monitoring create or update events provides visibility into network access changes and helps quickly detect potential security threats or misconfigurations, reducing the risk of unauthorized access.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -578,7 +611,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "It is recommended to configure a metric filter and alarm to monitor Virtual Private Cloud (VPC) network route changes. Keeping track of modifications ensures that unauthorized or unintended changes do not disrupt expected network traffic flow.", "AdditionalInformation": "GCP routes define how network traffic is directed between VM instances and external destinations. Monitoring route table changes helps ensure that traffic follows the intended path, preventing misconfigurations or malicious alterations that could lead to data exposure or connectivity issues.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -595,7 +629,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "It is recommended to configure a metric filter and alarm to monitor Virtual Private Cloud (VPC) network changes. This helps track modifications to VPC configurations and peer connections, ensuring that network traffic remains secure and follows the intended paths.", "AdditionalInformation": "It is recommended to configure a metric filter and alarm to monitor Virtual Private Cloud (VPC) network changes. This helps track modifications to VPC configurations and peer connections, ensuring that network traffic remains secure and follows the intended paths.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -612,7 +647,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "It is recommended to set up a metric filter and alarm to monitor Cloud Storage Bucket IAM changes. This ensures that any modifications to bucket permissions are tracked and reviewed in a timely manner.", "AdditionalInformation": "Monitoring changes to Cloud Storage IAM policies helps detect and correct unauthorized access or overly permissive configurations. This reduces the risk of data exposure or breaches by ensuring that sensitive storage buckets and their contents remain properly secured.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -629,7 +665,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "It is recommended to configure a metric filter and alarm to track SQL instance configuration changes. This helps in detecting and addressing misconfigurations that may impact security, availability, and compliance.", "AdditionalInformation": "Monitoring SQL instance configuration changes ensures that critical security settings remain properly configured. Misconfigurations, such as disabling auto backups, allowing untrusted networks, or modifying high availability settings, can lead to data loss, security vulnerabilities, or operational disruptions. Early detection of such changes helps maintain a secure and resilient SQL environment.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -646,7 +683,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Enabling logging on an HTTPS Load Balancer captures all network traffic and its destination, providing visibility into requests made to your web applications.", "AdditionalInformation": "Logging HTTPS network traffic helps monitor access patterns, troubleshoot issues, and enhance security by detecting suspicious activity or unauthorized access attempts.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -663,7 +701,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Flow Logs capture and record IP traffic to and from network interfaces within VPC subnets. These logs are stored in Stackdriver Logging, allowing users to analyze traffic patterns, detect anomalies, and optimize network performance. It is recommended to enable Flow Logs for all critical VPC subnets to enhance network visibility and security.", "AdditionalInformation": "VPC Flow Logs provide detailed insights into inbound and outbound traffic for virtual machines (VMs), whether they communicate with other VMs, on-premises data centers, Google services, or external networks. Enabling Flow Logs supports: Network monitoring Traffic analysis and cost optimization Incident investigation and forensics Real-time security threat detection For effective monitoring, Flow Logs should be configured to capture all traffic, use granular logging intervals, avoid log filtering, and include metadata for detailed investigations. Note that subnets reserved for internal HTTP(S) load balancing do not support Flow Logs.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -680,7 +719,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "The log_connections setting should be enabled to log all attempted connections to the PostgreSQL server, including successful client authentication.", "AdditionalInformation": "By default, PostgreSQL does not log connection attempts, making it harder to detect unauthorized access. Enabling log_connections provides visibility into all connection attempts, aiding in troubleshooting and identifying unusual or suspicious access patterns. This is particularly useful for security monitoring and incident response.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -697,7 +737,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "The log_disconnections setting should be enabled to log the end of each PostgreSQL session, including session duration.", "AdditionalInformation": "By default, PostgreSQL does not log session termination details, making it difficult to track session activity. Enabling log_disconnections helps monitor session durations and detect unusual activity. Combined with log_connections, it provides a complete audit trail of user access, aiding in troubleshooting and security monitoring.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -714,7 +755,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "The log_statement setting in PostgreSQL determines which SQL statements are logged. Acceptable values include none, ddl, mod, and all. A recommended setting is ddl, which logs all data definition statements unless otherwise specified by the organizations logging policy.", "AdditionalInformation": "Proper SQL statement logging is crucial for auditing and forensic analysis. If too many statements are logged, it can become difficult to extract relevant information; if too few are logged, critical details may be missing. Setting log_statement to an appropriate value, such as ddl, ensures a balance between comprehensive auditing and log manageability, aiding in database security and compliance.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -731,7 +773,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "The log_min_messages setting in PostgreSQL defines the minimum severity level for messages to be logged as errors. Accepted values range from DEBUG5 (least severe) to PANIC (most severe). Best practice is to set this value to ERROR, ensuring that only critical issues are logged unless an organizations policy requires a different threshold.", "AdditionalInformation": "Proper logging is essential for troubleshooting and forensic analysis. If log_min_messages is not configured correctly, important error messages may be missed or unnecessary logs may clutter records. Setting this parameter to ERROR helps maintain a balance between capturing relevant issues and avoiding excessive log noise, improving system monitoring and security.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -748,7 +791,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "The log_min_error_statement setting in PostgreSQL defines the minimum severity level for statements to be logged as errors. Valid values range from DEBUG5 (least severe) to PANIC (most severe). It is recommended to set this value to ERROR or stricter to ensure only relevant error statements are logged.", "AdditionalInformation": "Proper logging aids in troubleshooting and forensic analysis. If log_min_error_statement is set too leniently, excessive log entries may make it difficult to identify actual errors. Conversely, if it is set too strictly, important errors may be missed. Setting this parameter to ERROR or higher ensures that significant issues are recorded while avoiding unnecessary log clutter.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -765,7 +809,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "The log_min_duration_statement setting in PostgreSQL determines the minimum execution time (in milliseconds) required for a statement to be logged. It is recommended to disable this setting by setting its value to -1.", "AdditionalInformation": "Logging SQL statements may expose sensitive information, which could lead to security risks if recorded in logs. Disabling this setting ensures that confidential data is not inadvertently captured. This recommendation applies to PostgreSQL database instances.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -782,7 +827,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Ensure that the cloudsql.enable_pgaudit database flag is set to on for Cloud SQL PostgreSQL instances to enable centralized logging and auditing.", "AdditionalInformation": "Enabling the pgaudit extension provides detailed session and object-level logging, which helps organizations comply with security standards such as government, financial, and ISO regulations. This logging capability enhances threat detection by monitoring security events on the database instance. Additionally, enabling this flag allows logs to be sent to Google Logs Explorer for centralized access and monitoring. This recommendation applies specifically to PostgreSQL database instances.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -799,7 +845,8 @@ "SubSection": "3.2 Retention", "AttributeDescription": "Enabling retention policies on log storage buckets prevents logs from being overwritten or accidentally deleted. It is recommended to configure retention policies and enable Bucket Lock for all storage buckets used as log sinks.", "AdditionalInformation": "Cloud Logging allows logs to be exported to storage buckets through sinks. Without a retention policy, logs can be altered or deleted, making it difficult to perform security investigations or comply with audit requirements. To ensure logs remain intact for forensics and security analysis: 1.Set a retention policy on log storage buckets to prevent early deletion. 2.Enable Bucket Lock to make the policy immutable, ensuring logs cannot be altered even by privileged users. 3.Apply appropriate access controls to protect logs from unauthorized access. By implementing retention policies and Bucket Lock, organizations preserve critical security logs, prevent attackers from covering their tracks, and enhance compliance with security regulations.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -816,7 +863,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Google Cloud Asset Inventory provides a historical view of GCP resources and IAM policies using a time-series database. It captures metadata on cloud resources, policy configurations, and runtime data. Enabling Cloud Asset Inventory allows for efficient searching and exporting of asset data.", "AdditionalInformation": "Cloud Asset Inventory enhances security analysis, resource change tracking, and compliance auditing by maintaining a detailed history of GCP resources and their configurations. Enabling it across all GCP projects ensures visibility into changes, helping organizations detect misconfigurations, track policy changes, and strengthen security posture.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -833,7 +881,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "GCP Access Approval allows organizations to require explicit approval before Google support personnel can access their projects. Administrators can assign security roles in IAM to specific users who can review and approve these requests. Notifications of access requests, including the requesting Google employees details, are sent via email or Pub/Sub messages, providing transparency and control.", "AdditionalInformation": "Managing who accesses your organizations data is critical for information security. While Google support may require access for troubleshooting, Access Approval ensures that access is only granted when explicitly authorized. This feature adds an additional layer of security and logging, ensuring that only approved Google personnel can access sensitive information.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -850,7 +899,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "Cloud DNS provides a scalable and reliable domain name system (DNS) service. Domain Name System Security Extensions (DNSSEC) enhance DNS security by protecting domains against DNS hijacking, man-in-the-middle attacks, and other threats.", "AdditionalInformation": "DNSSEC cryptographically signs DNS records, ensuring the integrity and authenticity of DNS responses. Without DNSSEC, attackers can manipulate DNS lookups, redirecting users to malicious websites through DNS hijacking or spoofing attacks. Enabling DNSSEC helps prevent unauthorized modifications to DNS records, reducing the risk of phishing, malware distribution, and other cyber threats.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -867,7 +917,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "Require all incoming connections to SQL database instances to use SSL encryption.", "AdditionalInformation": "Unencrypted SQL database connections are vulnerable to man-in-the-middle (MITM) attacks, which can expose sensitive data such as credentials, queries, and results. Enforcing SSL ensures secure communication by encrypting data in transit, protecting against interception and unauthorized access. This recommendation applies to PostgreSQL, MySQL (Generation 1 and 2), and SQL Server 2017 instances.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -884,7 +935,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "DNSSEC (Domain Name System Security Extensions) relies on cryptographic algorithms to ensure the integrity and authenticity of DNS responses. It is important to use strong and recommended algorithms for key signing to maintain robust security. SHA-1 is deprecated and requires explicit approval from Google if used.", "AdditionalInformation": "DNSSEC signing algorithms play a critical role in securing DNS transactions. Using weak or outdated algorithms can expose DNS infrastructure to spoofing, hijacking, and other attacks. Organizations should select recommended and secure algorithms when enabling DNSSEC to protect DNS records from unauthorized modifications. If adjustments to DNSSEC settings are required, DNSSEC must be disabled and re-enabled with the updated configurations.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -901,7 +953,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "DNSSEC (Domain Name System Security Extensions) enhances DNS security by using cryptographic algorithms for zone signing and transaction security. It is essential to use strong and recommended algorithms for key signing. SHA-1 has been deprecated and requires Googles explicit approval and a support contract if used.", "AdditionalInformation": "Using weak or outdated cryptographic algorithms compromises DNS integrity and exposes systems to threats like spoofing and hijacking. Organizations should ensure that DNSSEC settings use strong, recommended algorithms. If DNSSEC is already enabled and changes are needed, it must be disabled and re-enabled with updated configurations to apply the changes effectively.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -918,7 +971,8 @@ "SubSection": "4.2 At-Rest", "AttributeDescription": "Customer-Supplied Encryption Keys (CSEK) is a feature available in Google Cloud Storage and Google Compute Engine, allowing users to supply their own encryption keys. When you provide your key, Google uses it to protect the Google-generated keys that are responsible for encrypting and decrypting your data. By default, Google Compute Engine encrypts all data at rest automatically, managing this encryption for you with no additional action required. However, if you wish to have full control over the encryption process, you can choose to supply your own encryption keys.", "AdditionalInformation": "By default, Compute Engine automatically encrypts all data at rest, with the service managing the encryption without any further input required from you or your application. However, if you require complete control over encryption, you have the option to provide your own encryption keys to manage the encryption of instance disks.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -935,7 +989,8 @@ "SubSection": "4.2 At-Rest", "AttributeDescription": "When using Dataproc, the data associated with clusters and jobs is stored on Persistent Disks (PDs) linked to the Compute Engine VMs in your cluster and in a Cloud Storage staging bucket. This data is encrypted using a Google-generated Data Encryption Key (DEK) and a Key Encryption Key (KEK). The Customer-Managed Encryption Keys (CMEK) feature allows you to create, use, and revoke the KEK, although Google still controls the DEK used to encrypt the data.", "AdditionalInformation": "Dataproc cluster data is encrypted using Google-managed keys: the Data Encryption Key (DEK) and the Key Encryption Key (KEK). If you wish to have control over the encryption of your cluster data, you can use your own Customer-Managed Keys (CMKs). Cloud KMS Customer-Managed Keys can add an extra layer of security and are commonly used in environments with strict compliance and security requirements.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -952,7 +1007,8 @@ "SubSection": "4.2 At-Rest", "AttributeDescription": "Ensure that BigQuery datasets are encrypted using Customer-Managed Keys (CMKs) to gain more granular control over the data encryption and decryption process.", "AdditionalInformation": "For enhanced control over encryption, Customer-Managed Encryption Keys (CMEK) can be implemented as a key management solution for BigQuery datasets.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -969,7 +1025,8 @@ "SubSection": "4.2 At-Rest", "AttributeDescription": "Ensure that BigQuery tables are encrypted using Customer-Managed Keys (CMKs) for more granular control over the data encryption and decryption process.", "AdditionalInformation": "For greater control over encryption, Customer-Managed Encryption Keys (CMEK) can be utilized as the key management solution for BigQuery tables.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] } diff --git a/prowler/compliance/m365/prowler_threatscore_m365.json b/prowler/compliance/m365/prowler_threatscore_m365.json index 24d100064d..ace3eee2a6 100644 --- a/prowler/compliance/m365/prowler_threatscore_m365.json +++ b/prowler/compliance/m365/prowler_threatscore_m365.json @@ -17,7 +17,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Microsoft cloud-only accounts are governed by a built-in password policy that cannot be customized. The only configurable options are the password expiration period and whether password expiration is enabled at all.", "AdditionalInformation": "Modern security guidance from organizations like NIST and Microsoft recommends against forcing regular password changes unless there is a known compromise or the user has forgotten the password. Arbitrary password expiration policies can lead to weaker password practices, such as predictable patterns or reused credentials. This is especially relevant even in single-factor (password-only) scenarios. When combined with strong security measures like Multi-Factor Authentication (MFA) and Entra ID password protection, the need for periodic password changes becomes less critical. As such, it’s more effective to focus on strengthening overall authentication practices rather than enforcing frequent password resets.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -34,7 +35,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Multifactor Authentication (MFA) enhances account security by requiring users to provide at least two forms of identity verification during sign-in—such as a password and a one-time code from a mobile device, biometric scan, or authentication app. It is critical to ensure that all users in administrator roles have MFA enabled to protect privileged access.", "AdditionalInformation": "MFA significantly reduces the risk of unauthorized access by requiring attackers to compromise multiple independent authentication factors. For administrative accounts—often targeted due to their elevated privileges—this additional layer of security is essential. Enforcing MFA for admins helps ensure that only authorized individuals can access sensitive systems and configurations, thereby strengthening the overall security posture of the organization.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -51,7 +53,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Enable Multifactor Authentication (MFA) for all users in the Microsoft 365 tenant to strengthen identity security. Once enabled, users will be prompted to verify their identity using a second factor during sign-in. Common second factors include a one-time code sent via SMS or generated through an authentication app such as Microsoft Authenticator.", "AdditionalInformation": "MFA adds a critical layer of protection by requiring users to provide two or more independent forms of authentication before access is granted. This significantly reduces the likelihood of unauthorized access, as an attacker would need to compromise both the primary credentials and the second authentication factor. Enabling MFA across all user accounts helps protect the organization from phishing, credential theft, and other identity-based threats.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -68,7 +71,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Microsoft Entra ID supports a variety of authentication and authorization protocols, including legacy authentication methods. Legacy authentication typically refers to Basic Authentication, which prompts users to submit a username and password without support for modern security features like Multifactor Authentication (MFA). Several messaging and connection protocols fall under legacy authentication, including: • Authenticated SMTP – Sends authenticated email messages. • Autodiscover – Helps Outlook and Exchange ActiveSync (EAS) clients locate mailboxes. • Exchange ActiveSync (EAS) – Connects mobile devices to Exchange Online. • Exchange Online PowerShell – Requires the Exchange Online PowerShell Module when Basic Auth is blocked. • Exchange Web Services (EWS) – Used by Outlook, Outlook for Mac, and third-party applications. • IMAP4 and POP3 – Used by legacy email clients. • MAPI over HTTP (MAPI/HTTP) – Primary protocol for Outlook 2010 SP2 and newer. • Offline Address Book (OAB) – Downloads address lists for Outlook. • Outlook Anywhere (RPC over HTTP) – Legacy access method for Outlook. • Reporting Web Services – Retrieves reporting data from Exchange Online.•Universal Outlook – Used by the Windows 10 Mail and Calendar app. • Other clients – Protocols identified as using legacy authentication patterns.", "AdditionalInformation": "Legacy authentication protocols do not support multifactor authentication, making them a common attack vector for credential theft and brute-force attacks. Blocking legacy authentication significantly reduces the organization’s attack surface and helps enforce modern, more secure sign-in methods that support MFA.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -85,7 +89,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Authentication Strength is a Conditional Access (CA) control in Microsoft Entra ID that allows administrators to define which authentication methods are permitted for accessing specific resources. This enables a tailored approach to security—stronger methods can be enforced for sensitive assets, while less secure methods may be acceptable for lower-risk scenarios. Microsoft provides three built-in authentication strength levels: • MFA Strength • Passwordless MFA Strength • Phishing-resistant MFA Strength It is recommended that all users in administrator roles are protected by a Conditional Access policy that enforces Phishing-resistant MFA Strength. Administrators can meet this requirement by registering and using one of the following phishing-resistant authentication methods: • FIDO2 Security Key • Windows Hello for Business • Certificate-based Authentication (CBA) Note: Configuration steps for these methods (e.g., setting up FIDO2 keys) are not covered here but are available in Microsoft’s documentation. The Conditional Access policy only enforces that at least one of these methods is used. Warning: Ensure that administrators are pre-registered for one of the supported strong authentication methods before enforcing the policy. As also recommended elsewhere in the CIS Benchmark, a break-glass account should be excluded from this policy to maintain emergency access.", "AdditionalInformation": "As MFA adoption increases, so does the sophistication of attacks designed to bypass it. Phishing-resistant authentication methods are more secure because they eliminate passwords from the authentication process. These methods rely on strong public/private key cryptography and ensure that authentication can only occur between trusted devices and providers—preventing login attempts from fake or phishing websites.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -102,7 +107,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Conditional Access (CA) policies can be configured to enforce access controls based on whether a device is compliant or Microsoft Entra hybrid joined. These conditions allow organizations to distinguish between managed and unmanaged devices, enabling more granular enforcement of authentication policies. • The Require device to be marked as compliant control ensures that devices meet the compliance standards defined in Intune compliance policies. Devices must first be enrolled in Intune Mobile Device Management (MDM) before these policies can be evaluated. • The Require Microsoft Entra hybrid joined device control applies to devices synchronized from an on-premises Active Directory environment, marking them as trusted within the hybrid identity model. When both conditions are included in the same Conditional Access policy, the evaluation functions as an OR logic—only one of the two conditions needs to be met for the user to authenticate successfully from a device. Recommended configuration: • Require device to be marked as compliant • Require Microsoft Entra hybrid joined device • Require one of the selected controls", "AdditionalInformation": "Managed devices are generally more secure due to enforced configurations such as Group Policy, mobile device compliance policies, endpoint detection and response (EDR), managed patching, and centralized alerting. Limiting access to only compliant or hybrid joined devices ensures that users are authenticating from secure environments. This policy helps mitigate the risk of compromised credentials by requiring attackers to first obtain access to a trusted device. When combined with additional CA controls—such as multi-factor authentication—it adds a further barrier to unauthorized access and strengthens the organization’s overall security posture.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -119,7 +125,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Conditional Access (CA) policies can be used to restrict the registration of multi-factor authentication (MFA) methods based on a device’s compliance status or whether it is Microsoft Entra hybrid joined. This allows organizations to enforce that only managed devices are used when users register security information.• Require device to be marked as compliant enforces that the device meets all conditions defined in Intune compliance policies. Devices must be enrolled in Intune Mobile Device Management (MDM) for this to apply. • Require Microsoft Entra hybrid joined device ensures the device has been synchronized from an on-premises Active Directory, marking it as trusted within the hybrid identity environment. When both controls are included in a Conditional Access policy for MFA registration, they operate with OR logic—only one of the conditions must be satisfied for the user to proceed. Recommended configuration: Restrict the “Register security information” operation to devices that are either compliant or Microsoft Entra hybrid joined.", "AdditionalInformation": "Restricting MFA registration to trusted, managed devices significantly reduces the risk of attackers using stolen credentials to set up fraudulent authentication methods. Accounts that exist but are not yet registered for MFA are particularly vulnerable to takeover. This policy ensures that security information is registered only from secured, policy-enforced endpoints—which often include additional layers of protection such as endpoint detection, encryption, and monitoring—thereby reducing the attack surface and strengthening the organization’s identity security posture.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -136,7 +143,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Modern authentication in Microsoft 365 enables advanced authentication capabilities such as multifactor authentication (MFA), smart card support, certificate-based authentication (CBA), and integration with third-party SAML identity providers. It replaces legacy authentication protocols with more secure, token-based authentication methods. It is recommended to enforce modern authentication for SharePoint applications to ensure secure access.", "AdditionalInformation": "If SharePoint applications are allowed to use basic authentication, they may bypass strong authentication controls such as MFA, exposing the environment to potential compromise. Enforcing modern authentication ensures that all sessions between users, applications, and SharePoint utilize robust, policy-enforced authentication methods—significantly reducing the risk of credential theft and unauthorized access.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -153,7 +161,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "SharePoint Online allows users to share files, folders, and entire site collections both internally and externally. With appropriate permissions, internal users can extend access to external collaborators, enabling seamless cross-organizational collaboration.", "AdditionalInformation": "While external sharing supports productivity and collaboration, it’s essential that owners of files, folders, or site collections retain control over what content is shared and with whom. This helps prevent unauthorized data disclosure and ensures that sensitive information is only accessible to intended recipients. Proper sharing governance empowers data owners to make informed decisions and reinforces accountability across the organization.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -170,7 +179,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "The “Multi-Factor Authentication (MFA) Status” setting determines whether users are required to authenticate using a second factor beyond their password. For privileged users—those with administrative roles or elevated permissions—this setting should be set to “Enabled” to ensure that MFA is enforced whenever they sign in.", "AdditionalInformation": "Privileged accounts have access to critical systems, sensitive data, and administrative functions that, if compromised, could lead to significant security breaches. Enforcing MFA for all privileged users greatly reduces the risk of unauthorized access by requiring attackers to compromise two or more independent authentication factors. MFA is one of the most effective defenses against phishing, credential theft, and brute-force attacks, making it a foundational control for protecting administrative accounts in any secure identity and access management strategy.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -187,7 +197,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "The “Multi-Factor Authentication (MFA) Status” setting determines whether users must verify their identity using a second factor in addition to their password. For non-privileged users—those without administrative or elevated permissions—it is recommended that MFA is enabled across the entire user base to provide comprehensive protection against identity-based attacks.", "AdditionalInformation": "While non-privileged users may not have administrative access, they still have access to email, internal systems, and potentially sensitive business data. These accounts are often targeted in phishing campaigns, credential stuffing attacks, and social engineering tactics to gain an initial foothold in the organization. Enforcing MFA for all users significantly reduces the likelihood of account compromise by requiring a second form of verification, such as a mobile app, hardware token, or one-time passcode. This broad protection is essential in a Zero Trust security model and ensures that every account—regardless of privilege—is secured against unauthorized access.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -204,7 +215,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "The External Callouts feature in Exchange Online introduces a native visual indicator for emails originating from outside the organization. When enabled, this feature displays a localized “External” tag within supported Outlook clients, along with additional user interface elements at the top of the message reading pane. These enhancements help users easily identify and verify the actual sender’s email address, providing critical context when evaluating incoming messages. The feature is enabled via PowerShell using the Set-ExternalInOutlook cmdlet, and typically becomes visible to end users within 24–48 hours, provided their Outlook client version supports the functionality. Note: While Exchange administrators have historically used mail flow rules to prepend “[External]” or similar text to subject lines, this method is less reliable and may not consistently apply across all message types or clients. The CIS Benchmark recommends enabling the native External tagging feature for a more consistent and secure user experience.", "AdditionalInformation": "Tagging emails from external senders increases user awareness and vigilance, enabling recipients to recognize messages that originate outside the organization’s trusted environment. This visual cue acts as a simple but effective layer of defense, encouraging users to treat unexpected or suspicious emails with caution—especially those that may be phishing attempts, impersonation attacks, or social engineering lures. By clearly marking external messages, organizations enhance their users’ ability to make informed security decisions, reducing the likelihood of credential compromise, malware infection, or inadvertent data disclosure.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -221,7 +233,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "Modern authentication in Microsoft 365 enables advanced authentication capabilities such as multi-factor authentication (MFA), smart card-based login, certificate-based authentication (CBA), and integration with third-party SAML identity providers. When enabled for Exchange Online, clients like Outlook 2016 and Outlook 2013 utilize modern authentication protocols (such as OAuth 2.0) to securely connect to Microsoft 365 mailboxes. If modern authentication is disabled, these clients fall back to basic authentication, a legacy protocol that transmits credentials in plaintext and lacks support for MFA. Newer clients—including Outlook for Mac 2016, Outlook Mobile, and all Microsoft 365 Apps for Enterprise versions of Outlook—are built to use modern authentication by default.", "AdditionalInformation": "Allowing basic authentication significantly weakens the security posture of an organization. It bypasses modern controls like multi-factor authentication, exposing user credentials to a higher risk of compromise through phishing, brute-force attacks, or session hijacking. By enabling modern authentication in Exchange Online, organizations enforce the use of strong, token-based authentication methods that are resistant to credential theft and session replay. This is critical for protecting sensitive email data and ensuring secure communication between user devices and Microsoft 365 services. Enabling modern authentication also supports compliance mandates and zero-trust principles, making it a foundational step in securing user identities and email infrastructure.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -238,7 +251,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "This policy setting in Microsoft Teams controls who is allowed to bypass the meeting lobby and directly join a meeting. When properly configured, only explicitly invited attendees—either those directly invited by the organizer or individuals to whom the invitation was intentionally forwarded—can skip the lobby and enter the meeting. All other participants, including anonymous users or those with access to the meeting link but not explicitly invited, must wait in the lobby for approval by the meeting organizer or a designated participant.", "AdditionalInformation": "For meetings involving sensitive, confidential, or regulated information, it is essential to tightly control participant access. Requiring all non-invited individuals to wait in the lobby allows the organizer to review and manually admit attendees, thereby preventing unauthorized access or accidental exposure of sensitive content. Additionally, this setting prevents misuse of the meeting link by anonymous or unintended users, such as initiating unauthorized meetings outside scheduled times. Even organizations that do not regularly operate in high-security (Level 2) environments but occasionally handle sensitive data should consider enabling this policy to reinforce data protection and meeting integrity. By limiting automatic entry to only verified, intended participants, this control supports secure collaboration, reduces risk of information leakage, and enhances confidence in Microsoft Teams as a platform for sensitive communications.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -255,7 +269,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "This policy setting in Microsoft Teams determines whether anonymous participants can start a meeting before a verified user from the organization or a trusted external organization has joined. When this setting is enabled, anonymous users and dial-in callers must wait in the meeting lobby until the meeting is initiated by an authenticated participant. Anonymous participants are defined as: • Users not signed in with a work or school account • Participants from non-trusted organizations, based on external access configuration • Individuals from organizations without mutual trust relationships Note: This setting only applies when the “Who can bypass the lobby” policy is set to Everyone. If the broader setting “Anonymous users can join a meeting” is disabled at the organizational level, this policy applies only to dial-in callers.", "AdditionalInformation": "Disallowing anonymous participants from starting meetings helps mitigate the risk of meeting abuse, such as spamming, hijacking, or unauthorized use of Teams meetings for unintended purposes. Anonymous users pose a higher risk because their identities cannot be verified, and they are not subject to organizational controls or compliance policies. Requiring an authenticated user to start the meeting ensures that someone with verified access and accountability is present before the session begins. This adds a layer of security and governance, especially in meetings that could involve sensitive discussions or are exposed to a wide range of external participants. Enforcing this policy supports a secure and controlled meeting environment and aligns with best practices for preventing unauthorized or disruptive activity in collaborative platforms.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -272,7 +287,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "This policy setting in Microsoft Teams defines who can bypass the meeting lobby and join a meeting directly, versus who must wait in the lobby until admitted by a meeting organizer, co-organizer, or designated presenter. Options include allowing access to everyone, people in your organization, trusted external organizations, or only invited users.", "AdditionalInformation": "Restricting direct access to meetings—particularly those that involve sensitive, confidential, or regulated information—ensures that only authorized and expected attendees can participate. Requiring participants to wait in the lobby gives meeting organizers the opportunity to vet and approve each attendee before admitting them. This policy also helps prevent unauthorized access through forwarded meeting links and reduces the risk of anonymous users joining meetings at unscheduled times, which can lead to disruptions or even security breaches. Enforcing lobby controls aligns with zero trust principles and is a best practice for maintaining the integrity and confidentiality of Teams meetings.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -289,7 +305,8 @@ "SubSection": "1.1 Authentication", "AttributeDescription": "This policy setting in Microsoft Teams determines whether dial-in participants—users who join meetings by phone—can bypass the lobby and join directly, or if they must wait in the lobby until admitted by a meeting organizer, co-organizer, or presenter.", "AdditionalInformation": "Dial-in participants typically cannot be authenticated in the same way as users joining via Teams apps or web clients, making it more difficult to verify their identity. For meetings that may involve sensitive, confidential, or regulated information, it is essential that the meeting organizer has the opportunity to manually vet and admit these participants.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -306,7 +323,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Microsoft 365 Groups serve as the foundation for collaboration across Microsoft 365, providing shared resources (e.g., Outlook inbox, SharePoint site, Teams workspace) to group members. While various group types exist, this recommendation specifically addresses Microsoft 365 Groups. By default, when a Microsoft 365 Group is created via the admin panel, its privacy setting is set to “Public”, meaning anyone in the organization can access its content unless the setting is manually changed.", "AdditionalInformation": "To protect sensitive organizational data, it’s important to ensure that only authorized and managed public groups exist. Public groups expose their content to all users in the organization through several access paths: • Users can add themselves to a public group using the Azure portal. • Users can request access via the Access Panel’s Groups app—this sends a request to the group owner but still grants immediate access. • Users may discover and directly access the associated SharePoint site via a guessable or easily discoverable URL.While admins are notified when Azure Portal access is used, other methods may not generate alerts. If group privacy settings are not properly managed, sensitive data could be inadvertently exposed. For this reason, privacy settings should be reviewed and adjusted to Private by default unless a public setting is explicitly required and approved.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -323,7 +341,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "A dynamic group in Microsoft Entra ID automatically manages group membership based on user attributes such as userType, department, or country/region. Administrators can define rules to ensure that users meeting specific criteria are added to—or removed from—a group without manual intervention. The recommended configuration is to create a dynamic group that specifically includes guest accounts.", "AdditionalInformation": "Dynamic groups streamline user management by automating group assignments. By including guest users in a dynamic group, organizations can consistently apply existing Conditional Access policies, access controls, and other security measures. This ensures that new guest accounts are governed by the same security standards as existing ones, reducing the risk of misconfiguration or oversight.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -340,7 +359,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "The admin consent workflow provides a secure and controlled process for granting access to applications that require administrator approval. When a user attempts to access an application but lacks permission to grant consent, they can submit a request for review. This request is sent via email to designated administrators, who act as reviewers. Once a decision is made, the user is notified of the outcome.", "AdditionalInformation": "The admin consent workflow (Preview) enhances security by ensuring that access to sensitive applications is reviewed and approved by authorized administrators. It prevents users from unintentionally granting permissions to potentially risky applications while maintaining a clear approval process with full visibility and accountability.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -357,7 +377,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Microsoft Entra ID Protection uses user risk policies to evaluate the likelihood that a user account has been compromised. These policies assign a risk level (low, medium, or high) based on detected anomalies, such as unfamiliar sign-ins, leaked credentials, or atypical behavior. Note: While Entra ID Protection includes built-in user risk policies, Microsoft strongly recommends implementing risk-based Conditional Access (CA) policies instead of relying on the older, legacy policy model. The modern CA approach offers several key advantages:• Access to enhanced diagnostic and troubleshooting data • Integration with report-only mode for safe testing • Support for automation via Microsoft Graph API • Greater flexibility through advanced Conditional Access attributes, such as sign-in frequency and session controls", "AdditionalInformation": "Enabling user risk policies through Conditional Access allows organizations to automatically respond to suspected account compromise by enforcing real-time controls—such as blocking access or requiring secure reauthentication. This proactive approach enhances the organization’s ability to detect and mitigate identity-based threats before they escalate.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -374,7 +395,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Microsoft Entra ID Protection evaluates sign-in risk by detecting potentially suspicious sign-in attempts—both in real time and through offline analysis. A risky sign-in indicates that the attempt may not have been performed by the legitimate account owner, based on signals such as unusual location, device anomalies, or unfamiliar sign-in behavior. Note: Although Microsoft Entra ID Protection includes built-in sign-in risk policies, it is strongly recommended to implement risk-based policies using Conditional Access instead of relying on legacy risk policies. The Conditional Access method provides several key advantages: • Access to enhanced diagnostic and investigation data • Ability to test with report-only mode • Integration with Microsoft Graph API for automation and management • Use of additional CA attributes such as sign-in frequency and session controls", "AdditionalInformation": "Enabling a sign-in risk Conditional Access policy allows organizations to automatically challenge suspicious sign-ins with multi-factor authentication (MFA). This reduces the likelihood of unauthorized access by requiring an additional verification step whenever unusual activity is detected, strengthening identity protection and reducing the risk of account compromise.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -391,7 +413,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "External sharing settings in Microsoft 365 govern how content is shared outside the organization. While each SharePoint site can have its own sharing configuration, it must be equal to or more restrictive than the organization-wide setting. The recommended configuration is “New and existing guests” or a more restrictive option. This setting requires external users to either sign in with a Microsoft 365 work or school account, a personal Microsoft account, or verify their identity using a one-time passcode. Users can share content with existing guests in the directory or invite new guests, who will be added to the directory upon sign-in.", "AdditionalInformation": "Requiring guest authentication ensures that external users are registered and identifiable within the organization’s directory. This allows administrators to apply governance controls—such as Conditional Access policies, group-based restrictions, and activity monitoring—to external identities. By enforcing authenticated sharing, organizations maintain visibility and control over externally shared resources, reducing the risk of unauthorized data access and supporting compliance with security and privacy policies.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -408,7 +431,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Organizations can control how documents are shared externally by configuring domain-based restrictions. This can be done by either blocking specific external domains or allowing sharing only with a defined list of trusted domains. These settings apply to services like SharePoint and OneDrive to help manage external collaboration securely.", "AdditionalInformation": "Restricting document sharing to approved domains reduces the risk of accidental or malicious data exposure. Attackers may attempt to exfiltrate sensitive information by sharing it with external entities. By limiting sharing to trusted domains, organizations minimize their external attack surface and maintain greater control over data flow.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -425,7 +449,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Anti-spam protection in Exchange Online leverages configurable policies to reduce the volume of unwanted emails—such as junk, bulk, and phishing messages—received by users. These policies include several configurable lists that influence how email from specific sources is treated:• Allowed Senders List • Allowed Domains List • Blocked Senders List • Blocked Domains List While these features offer flexibility, it is strongly recommended not to define any entries in the Allowed Domains List in a production environment.", "AdditionalInformation": "When a sender or domain is added to the Allowed Domains List, their messages bypass key security checks—including spam filtering and authentication mechanisms like SPF, DKIM, and DMARC—unless flagged as containing malware or high-confidence phishing. This introduces a significant security risk, as attackers may exploit these exceptions to deliver malicious emails directly to users’ inboxes. The risk is especially high when common or widely used domains are allow-listed, as these are frequent targets for spoofing attempts. Moreover, Microsoft’s official guidance clearly states that allowed domains should only be used for testing purposes, not for general use in production environments. To maintain a strong email security posture, organizations should avoid defining Allowed Domains and instead rely on more controlled methods such as block lists, quarantine policies, or targeted safe sender configurations for trusted entities.", - "LevelOfRisk": 1 + "LevelOfRisk": 1, + "Weight": 1 } ] }, @@ -443,7 +468,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Exchange Online provides multiple mechanisms to manage and control the flow of outbound email messages, helping organizations prevent unauthorized data exfiltration. These mechanisms include: • Remote Domain Settings • Transport Rules • Anti-Spam Outbound Policies These tools work in tandem to control and monitor various email forwarding methods that users or attackers may exploit, such as: • Inbox rules configured in Outlook • Automatic forwarding via Out of Office (OOF) rules • Forwarding settings in Outlook Web Access (OWA) using ForwardingSmtpAddress • Admin-defined forwarding in the Exchange Admin Center (EAC) using ForwardingAddress • Automated forwarding using Power Automate / Microsoft Flow To effectively reduce the risk of unauthorized data leaks, organizations should implement both a Transport Rule and an Outbound Anti-Spam Policy to block automatic mail forwarding. Note: If any exclusions are required (e.g., for trusted third-party systems or compliance tools), they should be strictly defined and approved in accordance with organizational policy.", "AdditionalInformation": "", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -460,7 +486,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Mail flow rules (also known as transport rules) in Exchange Online allow administrators to inspect, modify, or block email messages as they pass through the organization. These rules can be configured based on a wide range of conditions—such as sender, recipient, subject content, or attachment type—and can enforce actions including message redirection, header modification, or delivery rejection. While transport rules offer powerful control over email behavior, they must be implemented with caution—particularly when it comes to whitelisting domains or bypassing standard filtering mechanisms.", "AdditionalInformation": "Whitelisting external domains through transport rules can disable critical security checks such as anti-malware scanning, phishing detection, and sender authentication (e.g., SPF, DKIM, DMARC). If a trusted domain is later compromised—or was malicious from the start—this bypass can allow attackers to deliver malicious content directly to user inboxes without scrutiny. By avoiding broad or permanent domain whitelisting in transport rules, organizations preserve the integrity of their email filtering and reduce the risk of successful phishing campaigns, malware delivery, or data exfiltration originating from seemingly trusted sources. Transport rules should be reviewed regularly, and any exceptions must be justified, narrowly scoped, and documented according to organizational policy.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -477,7 +504,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "MailTips are real-time, context-aware notifications displayed to users as they compose email messages in Outlook. These tips are generated by Exchange while a message is being drafted and are based on an analysis of the email’s content and recipient list. If Exchange detects potential issues—such as the message being sent to a large distribution group, an external recipient, or someone who is out of office—it presents the user with a MailTip alert before the message is sent.This proactive feedback helps users avoid common issues like sending sensitive information to unintended recipients, triggering non-delivery reports (NDRs), or violating communication policies.", "AdditionalInformation": "Enabling MailTips provides valuable visual cues that promote user awareness and responsible communication. For example, users are warned when they are sending emails to external recipients or large distribution lists, which helps prevent data leakage, unintentional over-sharing, and excessive email traffic. MailTips serve as a lightweight but effective safeguard by nudging users to review recipients and message context before sending, reducing the risk of human error. In regulated or security-conscious environments, this feature reinforces compliance by helping users adhere to organizational communication policies in real time.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -494,7 +522,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "Microsoft Teams channel email addresses are an optional feature that enables users to send emails directly into a Teams channel. When enabled, each channel is assigned a unique email address that users can use to forward messages, share content, or initiate discussions from outside Teams. While this can enhance collaboration by bridging email and Teams-based communication, the generated email addresses are typically not part of the organization’s primary domain, and their usage is subject to broader Microsoft 365 infrastructure settings.", "AdditionalInformation": "Channel email addresses introduce potential security and governance concerns, as they are not managed under the organization’s domain and are exposed to external communication. If an attacker is able to discover or guess a channel’s email address, they could send messages directly into Teams, potentially introducing phishing links, malicious attachments, or inappropriate content into collaborative spaces. Furthermore, since organizations have limited control over the security configurations and exposure of these addresses, they may become a blind spot in security monitoring and email filtering. Disabling or restricting the use of Teams channel email addresses helps reduce the attack surface, prevent unauthorized message injection, and strengthen the overall security posture of Microsoft Teams.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -511,7 +540,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "This policy governs how external access is managed in Microsoft Teams, specifically determining whether users in your organization can communicate and collaborate with individuals from external domains. Administrators can configure this setting to: • Allow communication with all external domains • Block all external domains • Allow only specific (approved) external domains using an allowlist When external access is enabled, users can chat, invite external participants to meetings, and use audio/video conferencing with users in other Microsoft 365 or federated organizations. Recommended Configuration: To reduce exposure, it is recommended to either allow only specific external domains with whom collaboration is necessary or block all external domains entirely.", "AdditionalInformation": "While external collaboration can be valuable, unrestricted access to external domains introduces significant security risks. Without proper controls, users may inadvertently engage with untrusted or malicious entities, opening the door to phishing, social engineering, malware delivery, or data exfiltration. Notable threats that have leveraged Teams’ external access features include: • DarkGate malware distributed through malicious Teams messages • Phishing and impersonation campaigns by actors like Midnight Blizzard (APT29) • GIFShell, a technique for covert communication using GIFs within Teams • Username enumeration, allowing attackers to confirm the existence of user accounts By allowlisting only trusted domains, organizations retain the benefits of external collaboration while maintaining tight control over who can interact with internal users. This aligns with zero trust principles and helps ensure that external communication is both intentional and secure.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -528,7 +558,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "This policy setting in Microsoft Teams controls whether users in your organization can chat or join meetings with external Teams users who are not affiliated with a managed organization—for example, users of Microsoft Teams (free) or those without an associated Microsoft Entra ID (formerly Azure AD) tenant. These unmanaged accounts operate outside of enterprise governance and lack the administrative oversight, compliance enforcement, and security controls typically applied in organizational environments. Recommended Configuration: Set the policy to “Off” for “People in my organization can communicate with Teams users whose accounts aren’t managed by an organization” to block communication with unmanaged Teams users.", "AdditionalInformation": "Allowing communication with unmanaged external Teams users introduces a significant security risk. Since anyone can register for a free Teams account, attackers can easily create unmanaged identities and attempt to initiate contact with internal users. These interactions can be used to deliver malicious content, perform social engineering, or carry out reconnaissance. Documented attacks exploiting this communication channel include: • DarkGate malware delivery via malicious messages • Phishing and impersonation campaigns attributed to Midnight Blizzard (APT29) • GIFShell, a technique allowing covert exfiltration via GIFs in Teams chats • Username enumeration, enabling attackers to identify valid users in an organization Disabling communication with unmanaged Teams users helps enforce a zero trust posture, ensuring that all external interactions occur only with verified and trusted organizations under enforceable security policies. This reduces the organization’s exposure to external threats, protects sensitive communications, and upholds compliance standards.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -545,7 +576,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "This policy setting in Microsoft Teams controls whether external users with unmanaged Teams accounts—such as those using Microsoft Teams (free)—can initiate conversations with users in your organization. These unmanaged users do not belong to a verified Microsoft Entra (Azure AD) tenant and are not subject to organizational controls or governance. Recommended Configuration: Uncheck the option “External users with Teams accounts not managed by an organization can contact users in my organization” to prevent these users from initiating communication. This setting is designed as an additional safeguard to complement the broader policy that disables communication with unmanaged Teams users entirely. In scenarios where an organization allows limited interaction with such users, this control ensures that only internal users can initiate communication, further reducing exposure to unsolicited or malicious contact attempts.", "AdditionalInformation": "Enabling unmanaged Teams users to initiate contact with internal users poses a significant security risk, as anyone can easily register for a free Teams account with minimal identity verification. Threat actors can exploit this feature to deliver malicious content, impersonate legitimate contacts, or conduct reconnaissance by probing user availability and behavior. Notable real-world threats facilitated through external Teams access include:• DarkGate malware delivered via malicious chats • Social engineering and phishing campaigns by advanced threat actors such as Midnight Blizzard (APT29) • GIFShell, a covert data exfiltration method using GIFs in Teams • Username enumeration, enabling discovery of valid user accounts within an organization By preventing unmanaged external users from initiating conversations, organizations can better protect their internal users from unsolicited and potentially harmful contact attempts. This policy reinforces a defense-in-depth strategy, ensuring that even in exceptional cases where limited unmanaged communication is permitted, external contact remains tightly controlled and monitored.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -562,7 +594,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "This policy setting in Microsoft Teams controls who can read and write messages in the meeting chat. It allows administrators or meeting organizers to specify whether chat is available to everyone, only specific roles (such as presenters), or is disabled entirely for participants. This setting applies to chat interactions during the meeting and helps manage the flow and visibility of information shared in the chat pane.", "AdditionalInformation": "Limiting chat access to only authorized participants helps prevent the unintended disclosure of sensitive information and reduces the risk of inappropriate or disruptive content being shared during a meeting. In meetings involving confidential topics or external participants, restricting chat can safeguard against data leakage and maintain focus on the meeting agenda.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -579,7 +612,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "This policy setting in Microsoft Teams determines who is allowed to present content during a meeting. Presenters have elevated permissions that allow them to share their screen, display files, manage participants, and control other collaborative features. This setting can be configured at the organizational or meeting level to allow only organizers, co-organizers, or a designated group of participants to present.", "AdditionalInformation": "Restricting presentation privileges to authorized individuals helps ensure that only trusted participants can share content with the group. This minimizes the risk of inappropriate, disruptive, or unapproved material being displayed, whether intentionally or accidentally.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -596,7 +630,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "This policy setting in Microsoft Teams provides control over who can present content and who can request control of shared content during a meeting. It enables administrators and meeting organizers to limit these privileges to internal, trusted participants, while restricting or blocking external participants—including guests, external users, and anonymous users—from taking control of the presentation or initiating content sharing.", "AdditionalInformation": "Restricting presentation and control capabilities to authorized, internal participants significantly reduces the risk of accidental or malicious content sharing, interruptions, or abuse of meeting privileges. External participants—including guests, federated users, and anonymous joiners—may not be subject to the same identity verification or policy enforcement as users within the organization.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -613,7 +648,8 @@ "SubSection": "1.2 Authorization", "AttributeDescription": "This policy setting in Microsoft Teams determines whether a user is allowed to initiate the recording of a meeting in progress. When enabled, participants with the appropriate permissions can start recording audio, video, and screen-sharing content during the session.", "AdditionalInformation": "Restricting the ability to start a meeting recording ensures that only authorized individuals—such as organizers, co-organizers, team leads, or designated presenters—can capture meeting content. This is especially important for meetings that involve sensitive, confidential, or regulated information, where inappropriate or unauthorized recording could lead to data exposure, compliance violations, or reputational harm.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -630,7 +666,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "Each tenant should have more than one designated Global Administrator to ensure both accountability and redundancy in case one administrator leaves the organization. However, it’s equally important to limit the total number of Global Administrators to no more than four to reduce the overall security risk. Ideally, Global Administrator accounts should not have any user licenses assigned, limiting their exposure to commonly targeted services.", "AdditionalInformation": "Relying on a single Global Administrator creates a risk of unmonitored malicious activity. On the other hand, having too many Global Administrators increases the likelihood that one of their accounts could be compromised. A balanced approach supports both oversight and security.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -647,7 +684,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "Administrative accounts are privileged identities with elevated access to critical data, user management, and system settings. Assigning a license to these accounts may grant access to various applications, depending on the license type. The recommended practice is to avoid assigning licenses to privileged accounts altogether. If licensing is required—for example, to enable features such as Identity Protection, Privileged Identity Management (PIM), or Conditional Access—only Microsoft Entra ID P1 or P2 licenses should be used, as they do not include access to potentially vulnerable services like email or Teams.", "AdditionalInformation": "Minimizing application access for administrative accounts significantly reduces the attack surface associated with high-privilege identities. Access to tools like mailboxes or collaboration apps increases the risk of exposure to phishing or social engineering attacks. Administrative tasks should be performed using dedicated, unlicensed accounts, while day-to-day activities should be conducted through separate, unprivileged “daily driver” accounts.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -664,7 +702,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "App registration allows users to register custom or third-party applications for use within the organization’s Microsoft Entra ID directory. These applications can request access to organizational data and integrate with various Microsoft 365 services.", "AdditionalInformation": "While there are valid business cases for registering applications, this capability should be restricted to prevent unauthorized or insecure integrations. Attackers can exploit this feature by using compromised accounts to grant persistent access to third-party applications, enabling data exfiltration without needing to maintain direct control of the breached account. App registration should be disabled for standard users unless there is a clear business need and strong security controls—such as app consent policies and review workflows—are in place.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -681,7 +720,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "By default, non-privileged users can create new Microsoft Entra tenants through the “Manage tenant” option in the Entra admin portal. When a user creates a tenant, the action is logged in the Audit Log under the category DirectoryManagement with the activity Create Company. The user who creates the tenant is automatically assigned the Global Administrator role for that tenant. Note that newly created tenants do not inherit any of the organization’s existing security or configuration settings.", "AdditionalInformation": "Allowing unrestricted tenant creation introduces the risk of unauthorized or unmanaged environments, often referred to as shadow IT. These tenants may be mistakenly perceived as part of the organization’s secure infrastructure, leading users to adopt them for business use. This can fragment IT governance, complicate security oversight, and increase the likelihood of data exposure or policy violations. Restricting tenant creation ensures centralized control over the organization’s cloud environment and helps maintain consistent security and compliance standards.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -698,7 +738,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "Organizations can control whether end users and group owners are allowed to grant consent to applications, or whether such requests require administrator review and approval. While allowing user consent can enhance productivity by enabling access to useful apps, it also introduces potential security risks if not properly managed.", "AdditionalInformation": "Attackers often exploit application consent mechanisms by tricking users into authorizing malicious apps, thereby gaining access to sensitive company data. Disabling user consent for future app authorizations helps mitigate this risk by reducing the overall attack surface. When user consent is disabled, any existing consent remains valid, but all future consent requests must be explicitly approved by an administrator—ensuring better oversight and stronger security controls.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -715,7 +756,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "Microsoft Entra ID, as part of the Microsoft Entra suite, allows organizations to control what external guest users can view and access within the directory. By default, guest users are assigned a more limited permission level than internal members, who receive the full set of user permissions. These directory-level permissions apply across Microsoft Entra services, including Microsoft Graph, PowerShell v2, the Azure portal, and the My Apps portal. They also affect Microsoft 365 services that rely on Microsoft 365 Groups for collaboration—such as Outlook, Microsoft Teams, and SharePoint—though they do not override guest-specific settings within Teams or SharePoint. The recommended configuration is to ensure that guest users have limited access to directory properties and group memberships, or an even more restrictive setting.", "AdditionalInformation": "Restricting guest access helps prevent unauthorized enumeration of users and groups within the directory—a common reconnaissance tactic used by attackers during the early stages of a targeted attack (as defined in the Cyber Kill Chain framework). Limiting this visibility reduces the organization’s exposure to potential threats and supports a stronger security posture.", - "LevelOfRisk": 3 + "LevelOfRisk": 3, + "Weight": 10 } ] }, @@ -732,7 +774,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "By default, all users in the organization—including B2B collaboration guest users—can invite external users to collaborate via Microsoft Entra ID. This invitation capability can be broadly enabled or disabled, or it can be restricted to users in specific administrative roles. The recommended configuration is to limit guest invitations to only those users assigned to specific admin roles.", "AdditionalInformation": "Restricting who can invite external guests reduces the risk of unauthorized or unmanaged external access. By limiting this ability to trusted administrative roles, organizations can maintain tighter control over their environment and reduce potential exposure to security threats originating from unvetted accounts.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -749,7 +792,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "In complex environments, organizations may need to place limits on authentication session durations to reduce risk. Microsoft Entra Conditional Access (CA) policies allow organizations to enforce session controls based on user roles, device state, location, and application sensitivity. Common scenarios include: • Access from unmanaged or shared devices • External access to sensitive resources • High-privileged user accounts • Business-critical applications The following configurations are recommended: • Sign-in frequency: Require reauthentication at least every 4 hours for Microsoft 365 E3 tenants, or every 24 hours for E5 tenants using Privileged Identity Management (PIM). • Persistent browser session: Set to Never persistent. Note: These settings can be integrated into the Conditional Access policy that enforces multifactor authentication for users in administrative roles.", "AdditionalInformation": "Limiting the duration of authentication sessions helps prevent long-lived sessions that could be hijacked by attackers. Requiring periodic reauthentication ensures that a session cannot remain active indefinitely. Disabling persistent browser sessions further reduces the risk of drive-by browser attacks and ensures that session cookies are not stored, leaving nothing behind for an attacker to reuse in case of a compromised device.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -766,7 +810,8 @@ "SubSection": "1.3 Privilege Escalation Prevention", "AttributeDescription": "Microsoft OneDrive allows users to sign in with their organizational (cloud tenant) account and sync their OneDrive files—including selected folders or the entire contents of their storage—to a local computer. By default, synchronization is permitted on any device where OneDrive is installed, regardless of whether the device is Microsoft Entra ID Joined, Hybrid Entra ID Joined, or Active Directory Domain Joined. To improve control over where organizational data can be synchronized, it is recommended to restrict OneDrive syncing to only those devices joined to specific, trusted domains by enabling the policy: “Allow syncing only on computers joined to specific domains”, and specifying the appropriate Active Directory (AD) domain GUID(s)", "AdditionalInformation": "Allowing users to sync OneDrive data to unmanaged or personal devices introduces significant risk, as those endpoints may not comply with corporate security policies, lack endpoint protection, or be subject to malicious activity. When organizational data is synchronized to such devices, the organization loses visibility and control over how that data is accessed, shared, or protected. This opens the door to accidental data leaks, intentional misuse, or loss of sensitive information through theft or compromise. Restricting synchronization to verified, domain-joined devices ensures that only endpoints under the organization’s management and monitoring can access and store OneDrive data locally. This approach aligns with zero-trust principles, enforces data governance policies, and significantly reduces the risk of unauthorized access or exfiltration.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -783,7 +828,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "In Microsoft 365 environments—whether using Exchange Online mailboxes or standalone Exchange Online Protection (EOP)—connection filtering policies play a critical role in determining the trustworthiness of incoming email based on the source IP address. The default connection filter policy includes three main components: the IP Allow List, the IP Block List, and a Safe List. These lists influence how email messages are processed before any content filtering occurs. It is recommended that the IP Allow List remains empty or undefined to avoid bypassing essential security checks.", "AdditionalInformation": "Email originating from IP addresses on the Allow List bypasses several key layers of protection, including spam filtering and sender authentication protocols such as SPF, DKIM, and DMARC. Without additional safeguards like mail flow rules, this configuration introduces a significant risk: malicious actors can exploit the Allow List to deliver spoofed or harmful emails directly to users’ inboxes. Maintaining an empty IP Allow List ensures that all messages undergo full evaluation and filtering, reducing the likelihood of malware, phishing attempts, and impersonation attacks reaching end users.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -800,7 +846,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "In Microsoft 365 environments—whether using Exchange Online mailboxes or standalone Exchange Online Protection (EOP)—connection filtering policies are used to evaluate and classify incoming email based on the IP address of the sending server. The default connection filter policy includes three main components: the IP Allow List, the IP Block List, and the Safe List. The Safe List is a Microsoft-managed, dynamically updated set of sender IP addresses that are automatically treated as trusted sources. The recommended configuration is to have the Safe List disabled (set to Off or False) to ensure all incoming mail is properly evaluated by the organization’s email security policies.", "AdditionalInformation": "When the Safe List is enabled, messages from IP addresses on this list bypass key security mechanisms, including spam filtering and sender authentication checks such as SPF, DKIM, and DMARC. Although Microsoft manages this list dynamically, administrators have no visibility or control over which senders are included. As a result, allowing Safe List traffic to skip verification introduces significant risk—malicious actors could exploit this blind spot to deliver spam, phishing, or malware directly to user inboxes. Disabling the Safe List ensures that all messages undergo full inspection, allowing organizations to maintain strict control over the email filtering pipeline and reduce the likelihood of successful compromise.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -817,7 +864,8 @@ "SubSection": "2.1 Network", "AttributeDescription": "The SMTP AUTH (Simple Mail Transfer Protocol Authentication) setting in Exchange Online controls whether authenticated client SMTP submission is enabled at the organization level. This legacy protocol is used primarily by older applications and devices to send email via SMTP using basic authentication. By default, Microsoft recommends disabling SMTP AUTH at the tenant level to enhance security posture. Modern email clients and applications that connect to Microsoft 365 mailboxes no longer require SMTP AUTH and can use more secure, modern authentication methods (such as OAuth 2.0).", "AdditionalInformation": "SMTP AUTH is an outdated and insecure protocol that relies on basic authentication, which transmits credentials in plaintext and lacks support for multifactor authentication. Leaving this protocol enabled increases the risk of credential theft, account compromise, and unauthorized access, especially in environments not protected by additional controls such as Conditional Access or legacy protocol blocking. Disabling SMTP AUTH supports the principle of least functionality by reducing protocol exposure and hardening the email infrastructure against exploitation attempts. This action also aligns with Microsoft’s broader security guidance and helps organizations phase out legacy authentication methods in favor of modern, secure protocols.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -834,7 +882,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "The AdditionalStorageProvidersAvailable setting in Microsoft 365 controls whether users can connect to and open files from third-party storage services while using Outlook on the Web (OWA). When enabled, users may link external services such as Dropbox, Box, Google Drive, Facebook, or OneDrive Personal to access and interact with files directly within the Outlook web interface. Although this can enhance user productivity, it also introduces third-party services that Microsoft does not govern, meaning their terms of use, privacy policies, and security practices are outside the organization’s control. To mitigate potential risks, it is recommended to restrict or disable access to additional storage providers, limiting file access to only trusted organizational sources.", "AdditionalInformation": "Allowing connections to external storage providers from within Outlook on the Web significantly increases the risk of data leakage and malware exposure. Users may inadvertently upload or download sensitive organizational data to or from non-sanctioned storage platforms, where proper security controls and compliance measures may not be in place. Additionally, files retrieved from these services could serve as vectors for malware or phishing payloads, especially if users are unaware of their origin or if access controls on those platforms are weak. Restricting access to third-party storage providers helps enforce data governance policies, reduces the organization’s attack surface, and ensures that sensitive communications and files remain within controlled and monitored environments. This is especially important in industries with regulatory or compliance obligations.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -851,7 +900,8 @@ "SubSection": "2.2 Storage", "AttributeDescription": "Microsoft Teams facilitates collaboration by enabling users to share and access files within chat, meetings, and channels. By default, file sharing in Teams is integrated with SharePoint Online for team channels and OneDrive for Business for private chats. However, the platform also supports third-party cloud storage providers such as Dropbox, Box, and Google Drive, which can be made available within the Teams interface. Administrators have the ability to configure and restrict which external storage providers are accessible to end users. This helps align file-sharing capabilities with organizational data governance and compliance requirements. Note: While Skype for Business was officially deprecated on July 31, 2021, some configuration settings inherited from its infrastructure may still apply for a limited time. Refer to Microsoft’s official documentation for ongoing support timelines.", "AdditionalInformation": "Allowing unrestricted access to third-party cloud storage providers within Microsoft Teams can undermine an organization’s data protection and compliance efforts. Users may unintentionally store or share sensitive information using non-sanctioned platforms that fall outside of the organization’s control, monitoring, or security policies. By restricting file-sharing capabilities to only approved storage providers, organizations can ensure that collaboration remains within trusted ecosystems. This reduces the risk of data leakage, non-compliant data transfers, and unauthorized access, while also reinforcing secure and consistent file management practices across the collaboration environment.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -868,7 +918,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "The Common Attachment Types Filter allows users to block both well-known and custom-defined malicious file types from being attached to email messages.", "AdditionalInformation": "By blocking commonly exploited file types, this filter helps prevent the delivery of malware-laden attachments, reducing the risk of endpoint compromise and broader system infection.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -885,7 +936,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "The Common Attachment Types Filter allows users to block both known and custom-defined malicious file types from being attached to email messages. While Microsoft provides a default policy that blocks 53 high-risk file extensions, organizations can extend this protection by defining their own custom list. This recommendation includes a broader set of 186 potentially dangerous file extensions, offering a more robust safeguard. Although comprehensive, the list is not exhaustive and should be tailored to fit organizational needs.", "AdditionalInformation": "Blocking file types commonly associated with malware helps prevent the delivery of malicious payloads that can compromise hosts, exfiltrate data, or facilitate phishing attacks. By enforcing a strict attachment policy, organizations reduce their exposure to threats delivered through legacy formats, binary executables, and compressed archives. Allow-listing only those file types necessary for business operations and blocking all others is an effective strategy for mitigating risks such as Business Email Compromise (BEC) and enhancing overall email security posture.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -902,7 +954,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "In Exchange Online, administrators have the ability to control who can install and manage Outlook add-ins within the Microsoft 365 environment. By default, end users are allowed to install third-party or custom add-ins directly in their Outlook desktop client, which can access data within the application such as emails, calendar events, and contacts. To enhance security and reduce potential risks, it is recommended to restrict add-in management privileges to a limited set of trusted administrators and users. This can be configured via the Microsoft 365 admin center or PowerShell, providing centralized control over which add-ins are allowed and who can deploy them.", "AdditionalInformation": "Allowing end users to install Outlook add-ins introduces a potential attack surface, especially if the add-ins are vulnerable, poorly maintained, or intentionally malicious. Threat actors can exploit this capability to gain unauthorized access to sensitive mailbox data or to execute malicious code within the Outlook client. By disabling or restricting user-installed add-ins, organizations can significantly reduce the risk of data exfiltration, phishing, and privilege abuse. Managing add-ins centrally ensures that only vetted and trusted integrations are used, aligning with best practices for securing email clients and minimizing exposure to third-party threats.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -919,7 +972,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "This meeting policy setting in Microsoft Teams governs whether users in your organization can read or write messages in meeting chats hosted by external, untrusted organizations. If the external meeting is hosted by an organization that has been explicitly designated as trusted, this restriction does not apply.", "AdditionalInformation": "Allowing unrestricted chat participation in meetings hosted by untrusted external organizations increases the risk of exposure to malicious content, including links, files, or payloads designed to exploit user behavior or application vulnerabilities.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -937,7 +991,8 @@ "SubSection": "2.3 Application", "AttributeDescription": "User reporting settings in Microsoft Teams and Microsoft 365 allow end users to report messages they believe to be malicious or suspicious, enabling quicker response and investigation by security teams. To ensure the reporting feature functions as intended, this recommendation encompasses three distinct but interdependent settings that must all be correctly configured: 1. Teams Admin Center – User Reporting: This setting controls whether users can report messages directly from the Teams interface. It is enabled by default for new tenants. If disabled, users cannot report messages in Teams, and downstream settings in Microsoft Defender will not have any effect. 2. Microsoft 365 Defender Portal – User Reporting Integration: Also enabled by default in new tenants, this setting must be explicitly enabled for existing tenants. It ensures that messages reported from Teams are properly surfaced on the “User reported” tab of the Submissions page in Microsoft 365 Defender. 3. Defender – Report Message Destinations: This broader configuration applies to multiple Microsoft 365 services, including Teams. It allows organizations to control where reported messages are routed, such as keeping them within the organization or forwarding them to Microsoft for deeper analysis. Given its influence on how user submissions are processed, it is included as a required configuration in this assessment.", "AdditionalInformation": "Enabling user reporting equips employees with a straightforward mechanism to flag suspicious or potentially malicious content in Teams, acting as a critical early warning system for security teams. This improves organizational responsiveness to phishing, social engineering, or targeted attacks that may initially evade automated detection.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] }, @@ -954,7 +1009,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "The setting “Mailbox auditing on by default” determines whether mailbox auditing is automatically enabled across all mailboxes in the organization, regardless of their individual auditing configuration. When this setting is configured as False, it enables auditing at the organization level, overriding the AuditEnabled property for individual mailboxes—even if it is explicitly set to False. With this setting enabled, default audit actions are automatically recorded for all mailboxes without requiring manual configuration. Conversely, disabling this setting (True) effectively turns off mailbox auditing across the organization and overrides any mailbox-level auditing settings. The consequences of disabling this setting include: • Mailbox auditing is completely disabled organization-wide. • No mailbox actions are logged, even if AuditEnabled is set to True for individual mailboxes. • New mailboxes do not inherit auditing, and setting AuditEnabled=True has no effect. • Bypass audit rules set via Set-MailboxAuditBypassAssociation are ignored. • Existing audit records remain in place until they expire based on the audit log retention policy. The recommended configuration is to set this value to False at the organization level to ensure auditing is enforced consistently.", "AdditionalInformation": "Enforcing mailbox auditing by default ensures that audit logging cannot be unintentionally or maliciously disabled on individual mailboxes. This setting provides vital visibility for forensic investigations and incident response (IR) teams, allowing them to trace suspicious or malicious activity—such as unauthorized inbox access, message deletion, or rule manipulation—that may signal account compromise. Consistent auditing across all mailboxes is critical for detecting threat actor behaviors (TTPs) and correlating events across users. While organizations without Microsoft 365 E5 licenses are limited to 90 days of audit log retention, enabling this setting still significantly improves detection and accountability within that window.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -971,7 +1027,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "As of January 2019, Microsoft enables mailbox audit logging by default across all organizations. This feature ensures that specific actions performed by mailbox owners, delegates, and administrators are automatically captured and recorded. These audit records can then be searched by administrators through the mailbox audit log in Microsoft 365. Each mailbox type—whether user, shared, resource, or public folder—can have tailored audit settings to track activities that are most relevant to the organization. While audit logging is enabled by default at the organizational level, it is important to explicitly configure the AuditEnabled property to True on all user mailboxes, and to expand the list of audited actions beyond the Microsoft defaults to meet specific visibility or compliance needs. Note: This recommendation is particularly relevant to users with Microsoft 365 E3 licenses, where audit actions differ slightly from the default configurations in E5.", "AdditionalInformation": "Mailbox auditing plays a critical role in supporting both regulatory compliance and security monitoring. Whether investigating unauthorized configuration changes, potential account compromise, or insider threats, detailed mailbox audit logs provide essential evidence for security operations, forensic analysis, and general administrative oversight. While mailbox auditing is enabled by default for most user mailboxes, certain mailbox types—such as Resource Mailboxes, Public Folder Mailboxes, and the DiscoverySearch Mailbox—do not inherit the organizational auditing default. For these mailboxes, AuditEnabled must be manually set to True to ensure relevant activities are captured. Note: Organizations without Microsoft 365 E5 licenses are subject to a 90-day audit log retention limit, but enabling comprehensive mailbox auditing remains a best practice for operational readiness and incident response.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -988,7 +1045,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "Since January 2019, mailbox audit logging has been enabled by default in all Microsoft 365 organizations. This feature ensures that specific actions performed by mailbox owners, delegates, and administrators are automatically captured and stored as audit records. These logs are accessible to administrators through the Microsoft 365 mailbox audit log, enabling visibility into key mailbox-level activity. Although logging is enabled by default, each mailbox—particularly user and shared mailboxes—can have custom audit actions assigned to capture the specific types of events deemed valuable by the organization. For environments with Microsoft 365 E5 licenses or the advanced auditing add-on, it is recommended to explicitly set AuditEnabled to True on all user mailboxes and to configure additional audit actions beyond Microsoft’s default settings for enhanced visibility. Note: This recommendation specifically applies to E5 or equivalent auditing-enabled license holders, as the available audit depth and event coverage differ from E3.", "AdditionalInformation": "Mailbox audit logging is essential for supporting security investigations, regulatory compliance, and operational forensics in Microsoft 365. Whether you’re tracking unauthorized changes, detecting suspicious access, or conducting post-incident analysis, having a complete and accurate mailbox audit trail is critical. While audit logging is broadly applied by default, certain mailbox types bypass the organizational setting and require manual configuration to enable auditing. These include: • Resource Mailboxes • Public Folder Mailboxes • DiscoverySearch Mailboxes For these mailbox types, the AuditEnabled property must be explicitly set to True to ensure that audit events are captured. Important: Without advanced auditing (included in E5 or via add-on), mailbox audit logs are retained for only 90 days, limiting the historical window for investigations. Nonetheless, enabling detailed auditing remains a key best practice for maintaining strong visibility and compliance readiness.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1005,7 +1063,8 @@ "SubSection": "3.1 Logging", "AttributeDescription": "The AuditBypassEnabled setting in Microsoft 365 allows specific user or computer accounts to bypass mailbox audit logging, meaning that any actions they perform on mailboxes will not be recorded in the audit logs. This includes actions such as reading, deleting, moving, or modifying messages.", "AdditionalInformation": "Allowing an account to bypass mailbox audit logging creates a blind spot in security monitoring. If the account is compromised, misused, or maliciously configured, it can access and interact with mailboxes without leaving any trace in the logs. This significantly undermines the organization’s ability to conduct forensic investigations, detect insider threats, or comply with audit requirements.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1022,7 +1081,8 @@ "SubSection": "3.2 Retention", "AttributeDescription": "Audit log search in the Microsoft Purview compliance portal allows organizations to track and retain user and administrator activities across Microsoft 365 services. When enabled, audit events—such as sign-ins, file access, configuration changes, and other operational actions—are captured and stored for up to 90 days by default. While some organizations may choose to integrate auditing data with third-party Security Information and Event Management (SIEM) systems, audit log search in Microsoft Purview remains a critical native capability for centralized visibility and incident response. Although global administrators have the ability to disable audit log search, it is generally recommended to keep it enabled to maintain full visibility into user and system activity.", "AdditionalInformation": "Activating audit log search provides essential forensic and compliance value. It enables organizations to detect anomalous behavior, investigate potential security incidents, and demonstrate adherence to regulatory and legal requirements. In addition, it supports operational monitoring, internal audits, and proactive threat detection. By retaining and centralizing audit data within the Microsoft 365 ecosystem, security and compliance teams gain faster access to actionable insights, reducing response times and strengthening the organization’s overall security posture.", - "LevelOfRisk": 5 + "LevelOfRisk": 5, + "Weight": 1000 } ] }, @@ -1039,7 +1099,8 @@ "SubSection": "3.3 Monitoring", "AttributeDescription": "Exchange Online Protection (EOP) is Microsoft’s cloud-based email filtering service designed to safeguard organizations against spam, malware, and other email-borne threats. It is included by default in all Microsoft 365 tenants with Exchange Online mailboxes. EOP provides customizable anti-malware policies that allow administrators to define protection settings and configure alerts for detected malicious activity.", "AdditionalInformation": "Enabling notifications for malware detections ensures that administrators are alerted when an internal user sends a message containing malware. Such incidents may signal a compromised user account or infected device, requiring immediate investigation to mitigate potential security breaches.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1056,7 +1117,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "DomainKeys Identified Mail (DKIM) is one of the three key email authentication technologies—alongside SPF and DMARC—used to protect domains from being spoofed by malicious actors. DKIM allows organizations to apply a cryptographic digital signature to the header of outbound email messages. When properly configured, DKIM enables a domain to associate its identity with the message, allowing recipient email systems to verify the authenticity of the sender. Combined with SPF and DMARC, DKIM helps prevent unauthorized use of your domain in phishing or spoofing attacks.", "AdditionalInformation": "Enabling DKIM in Office 365 ensures that all outbound emails sent via Exchange Online are cryptographically signed. This enables recipient mail servers to verify that messages originate from an authorized source, significantly reducing the risk of email spoofing and reinforcing trust in the organization’s email communications.", - "LevelOfRisk": 4 + "LevelOfRisk": 4, + "Weight": 100 } ] }, @@ -1073,7 +1135,8 @@ "SubSection": "4.1 In-Transit", "AttributeDescription": "Password Hash Synchronization (PHS) is a hybrid identity sign-in method that enables secure synchronization of user credentials from an on-premises Active Directory to Microsoft Entra ID. Microsoft Entra Connect performs this by syncing a hash of the password hash, ensuring credentials are not exposed in transit or storage. Note: This recommendation applies only to Microsoft 365 tenants configured with Entra Connect synchronization in a hybrid environment. It does not apply to tenants using federated domain configurations.", "AdditionalInformation": "PHS simplifies the user experience by allowing a single password to be used across both on-premises and cloud resources. It also enables leaked credential detection via Microsoft Entra ID Protection, helping identify compromised accounts when passwords appear in known data breaches or public forums. Compared to other synchronization methods like federation, PHS offers greater resilience—users can still sign in to Microsoft 365 even if connectivity to the on-premises environment is temporarily unavailable.", - "LevelOfRisk": 2 + "LevelOfRisk": 2, + "Weight": 8 } ] } diff --git a/prowler/lib/check/compliance_models.py b/prowler/lib/check/compliance_models.py index 92bfaec168..5e01e590f3 100644 --- a/prowler/lib/check/compliance_models.py +++ b/prowler/lib/check/compliance_models.py @@ -193,6 +193,7 @@ class Prowler_ThreatScore_Requirement_Attribute(BaseModel): AttributeDescription: str AdditionalInformation: str LevelOfRisk: int + Weight: int # Base Compliance Model diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/models.py b/prowler/lib/outputs/compliance/prowler_threatscore/models.py index f1908b8de3..c8ac0dd783 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/models.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/models.py @@ -21,6 +21,7 @@ class ProwlerThreatScoreAWSModel(BaseModel): Requirements_Attributes_AttributeDescription: str Requirements_Attributes_AdditionalInformation: str Requirements_Attributes_LevelOfRisk: int + Requirements_Attributes_Weight: int Status: str StatusExtended: str ResourceId: str @@ -47,6 +48,7 @@ class ProwlerThreatScoreAzureModel(BaseModel): Requirements_Attributes_AttributeDescription: str Requirements_Attributes_AdditionalInformation: str Requirements_Attributes_LevelOfRisk: int + Requirements_Attributes_Weight: int Status: str StatusExtended: str ResourceId: str @@ -73,6 +75,7 @@ class ProwlerThreatScoreGCPModel(BaseModel): Requirements_Attributes_AttributeDescription: str Requirements_Attributes_AdditionalInformation: str Requirements_Attributes_LevelOfRisk: int + Requirements_Attributes_Weight: int Status: str StatusExtended: str ResourceId: str @@ -99,6 +102,7 @@ class ProwlerThreatScoreM365Model(BaseModel): Requirements_Attributes_AttributeDescription: str Requirements_Attributes_AdditionalInformation: str Requirements_Attributes_LevelOfRisk: int + Requirements_Attributes_Weight: int Status: str StatusExtended: str ResourceId: str diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py index e041e3e7f3..eb5194074e 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore.py @@ -24,7 +24,8 @@ def get_prowler_threatscore_table( muted_count = [] pillars = {} score_per_pillar = {} - number_findings_per_pillar = {} + max_score_per_pillar = {} + counted_findings = [] for index, finding in enumerate(findings): check = bulk_checks_metadata[finding.check_metadata.CheckID] check_compliances = check.Compliance @@ -34,12 +35,24 @@ def get_prowler_threatscore_table( for attribute in requirement.Attributes: pillar = attribute.Section - if pillar not in score_per_pillar.keys(): + if not any( + [ + pillar in score_per_pillar.keys(), + pillar in max_score_per_pillar.keys(), + ] + ): score_per_pillar[pillar] = 0 - number_findings_per_pillar[pillar] = 0 - if finding.status == "FAIL" and not finding.muted: - score_per_pillar[pillar] += attribute.LevelOfRisk - number_findings_per_pillar[pillar] += 1 + max_score_per_pillar[pillar] = 0 + + if index not in counted_findings: + if finding.status == "PASS": + score_per_pillar[pillar] += ( + attribute.LevelOfRisk * attribute.Weight + ) + max_score_per_pillar[pillar] += ( + attribute.LevelOfRisk * attribute.Weight + ) + counted_findings.append(index) if pillar not in pillars: pillars[pillar] = {"FAIL": 0, "PASS": 0, "Muted": 0} @@ -60,14 +73,9 @@ def get_prowler_threatscore_table( for pillar in pillars: pillar_table["Provider"].append(compliance.Provider) pillar_table["Pillar"].append(pillar) - if number_findings_per_pillar[pillar] == 0: - pillar_table["Score"].append( - f"{Style.BRIGHT}{Fore.GREEN}0{Style.RESET_ALL}" - ) - else: - pillar_table["Score"].append( - f"{Style.BRIGHT}{Fore.RED}{score_per_pillar[pillar] / number_findings_per_pillar[pillar]:.2f}/5{Style.RESET_ALL}" - ) + pillar_table["Score"].append( + f"{Style.BRIGHT}{Fore.RED}{(score_per_pillar[pillar] / max_score_per_pillar[pillar]) * 100:.2f}%{Style.RESET_ALL}" + ) if pillars[pillar]["FAIL"] > 0: pillar_table["Status"].append( f"{Fore.RED}FAIL({pillars[pillar]['FAIL']}){Style.RESET_ALL}" @@ -110,10 +118,10 @@ def get_prowler_threatscore_table( ) print( - f"{Style.BRIGHT}\n=== Risk Score Guide ===\nScore ranges from 1 (lowest risk) to 5 (highest risk), indicating the severity of the potential impact.{Style.RESET_ALL}" + f"{Style.BRIGHT}\n=== Threat Score Guide ===\nThe lower the score, the higher the risk.{Style.RESET_ALL}" ) print( - f"{Style.BRIGHT}(Only sections containing results appear, the score is calculated as the sum of the level of risk of the failed findings divided by the number of failed findings){Style.RESET_ALL}" + f"{Style.BRIGHT}(Only sections containing results appear, the score is calculated as the sum of the level of risk * weight of the passed findings divided by the sum of the risk * weight of all the findings){Style.RESET_ALL}" ) print(f"\nDetailed results of {compliance_framework.upper()} are in:") print( diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py index 2d876b402f..b4021646ca 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws.py @@ -55,6 +55,7 @@ class ProwlerThreatScoreAWS(ComplianceOutput): Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Requirements_Attributes_Weight=attribute.Weight, Status=finding.status, StatusExtended=finding.status_extended, ResourceId=finding.resource_uid, @@ -81,6 +82,7 @@ class ProwlerThreatScoreAWS(ComplianceOutput): Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Requirements_Attributes_Weight=attribute.Weight, Status="MANUAL", StatusExtended="Manual check", ResourceId="manual_check", diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py index 01786e2d08..4671666ba0 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure.py @@ -55,6 +55,7 @@ class ProwlerThreatScoreAzure(ComplianceOutput): Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Requirements_Attributes_Weight=attribute.Weight, Status=finding.status, StatusExtended=finding.status_extended, ResourceId=finding.resource_uid, @@ -81,6 +82,7 @@ class ProwlerThreatScoreAzure(ComplianceOutput): Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Requirements_Attributes_Weight=attribute.Weight, Status="MANUAL", StatusExtended="Manual check", ResourceId="manual_check", diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py index 9bf577255f..0d57ce0ba7 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp.py @@ -55,6 +55,7 @@ class ProwlerThreatScoreGCP(ComplianceOutput): Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Requirements_Attributes_Weight=attribute.Weight, Status=finding.status, StatusExtended=finding.status_extended, ResourceId=finding.resource_uid, @@ -81,6 +82,7 @@ class ProwlerThreatScoreGCP(ComplianceOutput): Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Requirements_Attributes_Weight=attribute.Weight, Status="MANUAL", StatusExtended="Manual check", ResourceId="manual_check", diff --git a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365.py b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365.py index 26c5d01af0..f4ff630572 100644 --- a/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365.py +++ b/prowler/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365.py @@ -55,6 +55,7 @@ class ProwlerThreatScoreM365(ComplianceOutput): Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Requirements_Attributes_Weight=attribute.Weight, Status=finding.status, StatusExtended=finding.status_extended, ResourceId=finding.resource_uid, @@ -81,6 +82,7 @@ class ProwlerThreatScoreM365(ComplianceOutput): Requirements_Attributes_AttributeDescription=attribute.AttributeDescription, Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Requirements_Attributes_Weight=attribute.Weight, Status="MANUAL", StatusExtended="Manual check", ResourceId="manual_check", diff --git a/prowler_threatscore_aws b/prowler_threatscore_aws new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler_threatscore_azure b/prowler_threatscore_azure new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler_threatscore_gcp b/prowler_threatscore_gcp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler_threatscore_m365 b/prowler_threatscore_m365 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/lib/outputs/compliance/fixtures.py b/tests/lib/outputs/compliance/fixtures.py index 94c9604cf3..ddf25ad661 100644 --- a/tests/lib/outputs/compliance/fixtures.py +++ b/tests/lib/outputs/compliance/fixtures.py @@ -875,6 +875,7 @@ PROWLER_THREATSCORE_AWS = Compliance( AttributeDescription="The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", AdditionalInformation="Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.", LevelOfRisk=5, + Weight=1000, ) ], Checks=[ @@ -892,6 +893,7 @@ PROWLER_THREATSCORE_AWS = Compliance( AttributeDescription="The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", AdditionalInformation="A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.", LevelOfRisk=3, + Weight=10, ) ], Checks=[], @@ -917,6 +919,7 @@ PROWLER_THREATSCORE_AZURE = Compliance( AttributeDescription="The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", AdditionalInformation="Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.", LevelOfRisk=5, + Weight=1000, ) ], Checks=[ @@ -934,6 +937,7 @@ PROWLER_THREATSCORE_AZURE = Compliance( AttributeDescription="The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", AdditionalInformation="A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.", LevelOfRisk=3, + Weight=10, ) ], Checks=[], @@ -959,6 +963,7 @@ PROWLER_THREATSCORE_GCP = Compliance( AttributeDescription="The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", AdditionalInformation="Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.", LevelOfRisk=5, + Weight=1000, ) ], Checks=[ @@ -976,6 +981,51 @@ PROWLER_THREATSCORE_GCP = Compliance( AttributeDescription="The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", AdditionalInformation="A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.", LevelOfRisk=3, + Weight=10, + ) + ], + Checks=[], + ), + ], +) + +PROWLER_THREATSCORE_M365_NAME = "prowler_threatscore_m365" +PROWLER_THREATSCORE_M365 = Compliance( + Framework="ProwlerThreatScore", + Version="1.0", + Provider="M365", + Description="Prowler ThreatScore Compliance Framework for M365 ensures that the M365 account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption", + Requirements=[ + Compliance_Requirement( + Id="1.1.1", + Description="Ensure MFA is enabled for the 'root' user account", + Attributes=[ + Prowler_ThreatScore_Requirement_Attribute( + Title="MFA enabled for 'root'", + Section="1. IAM", + SubSection="1.1 Authentication", + AttributeDescription="The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", + AdditionalInformation="Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.", + LevelOfRisk=5, + Weight=1000, + ) + ], + Checks=[ + "iam_root_mfa_enabled", + ], + ), + Compliance_Requirement( + Id="1.1.2", + Description="Ensure hardware MFA is enabled for the 'root' user account", + Attributes=[ + Prowler_ThreatScore_Requirement_Attribute( + Title="CloudTrail logging enabled", + Section="1. IAM", + SubSection="1.1 Authentication", + AttributeDescription="The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.", + AdditionalInformation="A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.", + LevelOfRisk=3, + Weight=10, ) ], Checks=[], diff --git a/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws_test.py b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws_test.py index 28cf82ab44..b4771b6139 100644 --- a/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws_test.py +++ b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_aws_test.py @@ -66,6 +66,10 @@ class TestProwlerThreatScoreAWS: output_data.Requirements_Attributes_LevelOfRisk == PROWLER_THREATSCORE_AWS.Requirements[0].Attributes[0].LevelOfRisk ) + assert ( + output_data.Requirements_Attributes_Weight + == PROWLER_THREATSCORE_AWS.Requirements[0].Attributes[0].Weight + ) assert output_data.Status == "PASS" assert output_data.StatusExtended == "" assert output_data.ResourceId == "" @@ -113,6 +117,10 @@ class TestProwlerThreatScoreAWS: output_data_manual.Requirements_Attributes_LevelOfRisk == PROWLER_THREATSCORE_AWS.Requirements[1].Attributes[0].LevelOfRisk ) + assert ( + output_data_manual.Requirements_Attributes_Weight + == PROWLER_THREATSCORE_AWS.Requirements[1].Attributes[0].Weight + ) assert output_data_manual.Status == "MANUAL" assert output_data_manual.StatusExtended == "Manual check" assert output_data_manual.ResourceId == "manual_check" @@ -136,5 +144,5 @@ class TestProwlerThreatScoreAWS: mock_file.seek(0) content = mock_file.read() - expected_csv = f"PROVIDER;DESCRIPTION;ACCOUNTID;REGION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_TITLE;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_ATTRIBUTEDESCRIPTION;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_LEVELOFRISK;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\naws;Prowler ThreatScore Compliance Framework for AWS ensures that the AWS account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;123456789012;eu-west-1;{datetime.now()};1.1.1;Ensure MFA is enabled for the 'root' user account;MFA enabled for 'root';1. IAM;1.1 Authentication;The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.;5;PASS;;;;test-check-id;False\r\naws;Prowler ThreatScore Compliance Framework for AWS ensures that the AWS account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;;;{datetime.now()};1.1.2;Ensure hardware MFA is enabled for the 'root' user account;CloudTrail logging enabled;1. IAM;1.1 Authentication;The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.;3;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n" + expected_csv = f"PROVIDER;DESCRIPTION;ACCOUNTID;REGION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_TITLE;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_ATTRIBUTEDESCRIPTION;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_LEVELOFRISK;REQUIREMENTS_ATTRIBUTES_WEIGHT;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\naws;Prowler ThreatScore Compliance Framework for AWS ensures that the AWS account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;123456789012;eu-west-1;{datetime.now()};1.1.1;Ensure MFA is enabled for the 'root' user account;MFA enabled for 'root';1. IAM;1.1 Authentication;The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.;5;1000;PASS;;;;test-check-id;False\r\naws;Prowler ThreatScore Compliance Framework for AWS ensures that the AWS account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;;;{datetime.now()};1.1.2;Ensure hardware MFA is enabled for the 'root' user account;CloudTrail logging enabled;1. IAM;1.1 Authentication;The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.;3;10;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n" assert content == expected_csv diff --git a/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure_test.py b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure_test.py index 0dec129f06..64b79acb9e 100644 --- a/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure_test.py +++ b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_azure_test.py @@ -77,6 +77,10 @@ class TestProwlerThreatScoreAzure: output_data.Requirements_Attributes_LevelOfRisk == PROWLER_THREATSCORE_AZURE.Requirements[0].Attributes[0].LevelOfRisk ) + assert ( + output_data.Requirements_Attributes_Weight + == PROWLER_THREATSCORE_AZURE.Requirements[0].Attributes[0].Weight + ) assert output_data.Status == "PASS" assert output_data.StatusExtended == "" assert output_data.ResourceId == "" @@ -124,6 +128,10 @@ class TestProwlerThreatScoreAzure: output_data_manual.Requirements_Attributes_LevelOfRisk == PROWLER_THREATSCORE_AZURE.Requirements[1].Attributes[0].LevelOfRisk ) + assert ( + output_data_manual.Requirements_Attributes_Weight + == PROWLER_THREATSCORE_AZURE.Requirements[1].Attributes[0].Weight + ) assert output_data_manual.Status == "MANUAL" assert output_data_manual.StatusExtended == "Manual check" assert output_data_manual.ResourceId == "manual_check" @@ -146,5 +154,5 @@ class TestProwlerThreatScoreAzure: mock_file.seek(0) content = mock_file.read() - expected_csv = f"PROVIDER;DESCRIPTION;SUBSCRIPTIONID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_TITLE;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_ATTRIBUTEDESCRIPTION;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_LEVELOFRISK;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\naws;Prowler ThreatScore Compliance Framework for Azure ensures that the Azure account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;123456789012;eu-west-1;{datetime.now()};1.1.1;Ensure MFA is enabled for the 'root' user account;MFA enabled for 'root';1. IAM;1.1 Authentication;The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.;5;PASS;;;;test-check-id;False\r\nazure;Prowler ThreatScore Compliance Framework for Azure ensures that the Azure account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;;;{datetime.now()};1.1.2;Ensure hardware MFA is enabled for the 'root' user account;CloudTrail logging enabled;1. IAM;1.1 Authentication;The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.;3;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n" + expected_csv = f"PROVIDER;DESCRIPTION;SUBSCRIPTIONID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_TITLE;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_ATTRIBUTEDESCRIPTION;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_LEVELOFRISK;REQUIREMENTS_ATTRIBUTES_WEIGHT;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\naws;Prowler ThreatScore Compliance Framework for Azure ensures that the Azure account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;123456789012;eu-west-1;{datetime.now()};1.1.1;Ensure MFA is enabled for the 'root' user account;MFA enabled for 'root';1. IAM;1.1 Authentication;The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.;5;1000;PASS;;;;test-check-id;False\r\nazure;Prowler ThreatScore Compliance Framework for Azure ensures that the Azure account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;;;{datetime.now()};1.1.2;Ensure hardware MFA is enabled for the 'root' user account;CloudTrail logging enabled;1. IAM;1.1 Authentication;The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.;3;10;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n" assert content == expected_csv diff --git a/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp_test.py b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp_test.py index 40a5e60494..4727cc748c 100644 --- a/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp_test.py +++ b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_gcp_test.py @@ -72,6 +72,10 @@ class TestProwlerThreatScoreGCP: output_data.Requirements_Attributes_LevelOfRisk == PROWLER_THREATSCORE_GCP.Requirements[0].Attributes[0].LevelOfRisk ) + assert ( + output_data.Requirements_Attributes_Weight + == PROWLER_THREATSCORE_GCP.Requirements[0].Attributes[0].Weight + ) assert output_data.Status == "PASS" assert output_data.StatusExtended == "" assert output_data.ResourceId == "" @@ -119,6 +123,10 @@ class TestProwlerThreatScoreGCP: output_data_manual.Requirements_Attributes_LevelOfRisk == PROWLER_THREATSCORE_GCP.Requirements[1].Attributes[0].LevelOfRisk ) + assert ( + output_data_manual.Requirements_Attributes_Weight + == PROWLER_THREATSCORE_GCP.Requirements[1].Attributes[0].Weight + ) assert output_data_manual.Status == "MANUAL" assert output_data_manual.StatusExtended == "Manual check" assert output_data_manual.ResourceId == "manual_check" @@ -142,6 +150,6 @@ class TestProwlerThreatScoreGCP: mock_file.seek(0) content = mock_file.read() - expected_csv = f"PROVIDER;DESCRIPTION;PROJECTID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_TITLE;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_ATTRIBUTEDESCRIPTION;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_LEVELOFRISK;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\naws;Prowler ThreatScore Compliance Framework for GCP ensures that the GCP account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;123456789012;eu-west-1;{datetime.now()};1.1.1;Ensure MFA is enabled for the 'root' user account;MFA enabled for 'root';1. IAM;1.1 Authentication;The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.;5;PASS;;;;test-check-id;False\r\ngcp;Prowler ThreatScore Compliance Framework for GCP ensures that the GCP account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;;;{datetime.now()};1.1.2;Ensure hardware MFA is enabled for the 'root' user account;CloudTrail logging enabled;1. IAM;1.1 Authentication;The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.;3;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n" + expected_csv = f"PROVIDER;DESCRIPTION;PROJECTID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_TITLE;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_ATTRIBUTEDESCRIPTION;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_LEVELOFRISK;REQUIREMENTS_ATTRIBUTES_WEIGHT;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\naws;Prowler ThreatScore Compliance Framework for GCP ensures that the GCP account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;123456789012;eu-west-1;{datetime.now()};1.1.1;Ensure MFA is enabled for the 'root' user account;MFA enabled for 'root';1. IAM;1.1 Authentication;The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.;5;1000;PASS;;;;test-check-id;False\r\ngcp;Prowler ThreatScore Compliance Framework for GCP ensures that the GCP account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;;;{datetime.now()};1.1.2;Ensure hardware MFA is enabled for the 'root' user account;CloudTrail logging enabled;1. IAM;1.1 Authentication;The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.;3;10;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n" assert content == expected_csv diff --git a/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365_test.py b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365_test.py new file mode 100644 index 0000000000..ea0b3e529f --- /dev/null +++ b/tests/lib/outputs/compliance/prowler_threatscore/prowler_threatscore_m365_test.py @@ -0,0 +1,157 @@ +from datetime import datetime +from io import StringIO + +from freezegun import freeze_time +from mock import patch + +from prowler.lib.outputs.compliance.prowler_threatscore.models import ( + ProwlerThreatScoreM365Model, +) +from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_m365 import ( + ProwlerThreatScoreM365, +) +from tests.lib.outputs.compliance.fixtures import ( + PROWLER_THREATSCORE_M365, + PROWLER_THREATSCORE_M365_NAME, +) +from tests.lib.outputs.fixtures.fixtures import generate_finding_output +from tests.providers.m365.m365_fixtures import TENANT_ID + + +class TestProwlerThreatScoreM365: + def test_output_transform(self): + findings = [ + generate_finding_output( + compliance={"ProwlerThreatScore-1.0": "1.1.1"}, + provider="m365", + account_name=TENANT_ID, + account_uid=TENANT_ID, + region="", + ) + ] + + output = ProwlerThreatScoreM365( + findings, PROWLER_THREATSCORE_M365, PROWLER_THREATSCORE_M365_NAME + ) + output_data = output.data[0] + assert isinstance(output_data, ProwlerThreatScoreM365Model) + assert output_data.Provider == "m365" + assert output_data.Description == PROWLER_THREATSCORE_M365.Description + assert output_data.TenantId == TENANT_ID + assert output_data.Location == "" + assert ( + output_data.Requirements_Id == PROWLER_THREATSCORE_M365.Requirements[0].Id + ) + assert ( + output_data.Requirements_Description + == PROWLER_THREATSCORE_M365.Requirements[0].Description + ) + assert ( + output_data.Requirements_Attributes_Title + == PROWLER_THREATSCORE_M365.Requirements[0].Attributes[0].Title + ) + assert ( + output_data.Requirements_Attributes_Section + == PROWLER_THREATSCORE_M365.Requirements[0].Attributes[0].Section + ) + assert ( + output_data.Requirements_Attributes_SubSection + == PROWLER_THREATSCORE_M365.Requirements[0].Attributes[0].SubSection + ) + assert ( + output_data.Requirements_Attributes_AttributeDescription + == PROWLER_THREATSCORE_M365.Requirements[0] + .Attributes[0] + .AttributeDescription + ) + assert ( + output_data.Requirements_Attributes_AdditionalInformation + == PROWLER_THREATSCORE_M365.Requirements[0] + .Attributes[0] + .AdditionalInformation + ) + assert ( + output_data.Requirements_Attributes_LevelOfRisk + == PROWLER_THREATSCORE_M365.Requirements[0].Attributes[0].LevelOfRisk + ) + assert ( + output_data.Requirements_Attributes_Weight + == PROWLER_THREATSCORE_M365.Requirements[0].Attributes[0].Weight + ) + assert output_data.Status == "PASS" + assert output_data.StatusExtended == "" + assert output_data.ResourceId == "" + assert output_data.ResourceName == "" + assert output_data.CheckId == "test-check-id" + assert not output_data.Muted + # Test manual check + output_data_manual = output.data[1] + assert output_data_manual.Provider == "m365" + assert output_data_manual.TenantId == "" + assert output_data_manual.Location == "" + assert ( + output_data_manual.Requirements_Id + == PROWLER_THREATSCORE_M365.Requirements[1].Id + ) + assert ( + output_data_manual.Requirements_Description + == PROWLER_THREATSCORE_M365.Requirements[1].Description + ) + assert ( + output_data_manual.Requirements_Attributes_Title + == PROWLER_THREATSCORE_M365.Requirements[1].Attributes[0].Title + ) + assert ( + output_data_manual.Requirements_Attributes_Section + == PROWLER_THREATSCORE_M365.Requirements[1].Attributes[0].Section + ) + assert ( + output_data_manual.Requirements_Attributes_SubSection + == PROWLER_THREATSCORE_M365.Requirements[1].Attributes[0].SubSection + ) + assert ( + output_data_manual.Requirements_Attributes_AttributeDescription + == PROWLER_THREATSCORE_M365.Requirements[1] + .Attributes[0] + .AttributeDescription + ) + assert ( + output_data_manual.Requirements_Attributes_AdditionalInformation + == PROWLER_THREATSCORE_M365.Requirements[1] + .Attributes[0] + .AdditionalInformation + ) + assert ( + output_data_manual.Requirements_Attributes_LevelOfRisk + == PROWLER_THREATSCORE_M365.Requirements[1].Attributes[0].LevelOfRisk + ) + assert ( + output_data_manual.Requirements_Attributes_Weight + == PROWLER_THREATSCORE_M365.Requirements[1].Attributes[0].Weight + ) + assert output_data_manual.Status == "MANUAL" + assert output_data_manual.StatusExtended == "Manual check" + assert output_data_manual.ResourceId == "manual_check" + assert output_data_manual.ResourceName == "Manual check" + assert output_data_manual.CheckId == "manual" + assert not output_data_manual.Muted + + @freeze_time(datetime.now()) + def test_batch_write_data_to_file(self): + mock_file = StringIO() + findings = [ + generate_finding_output(compliance={"ProwlerThreatScore-1.0": "1.1.1"}) + ] + output = ProwlerThreatScoreM365( + findings, PROWLER_THREATSCORE_M365, PROWLER_THREATSCORE_M365_NAME + ) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + expected_csv = f"PROVIDER;DESCRIPTION;TENANTID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_TITLE;REQUIREMENTS_ATTRIBUTES_SECTION;REQUIREMENTS_ATTRIBUTES_SUBSECTION;REQUIREMENTS_ATTRIBUTES_ATTRIBUTEDESCRIPTION;REQUIREMENTS_ATTRIBUTES_ADDITIONALINFORMATION;REQUIREMENTS_ATTRIBUTES_LEVELOFRISK;REQUIREMENTS_ATTRIBUTES_WEIGHT;STATUS;STATUSEXTENDED;RESOURCEID;RESOURCENAME;CHECKID;MUTED\r\naws;Prowler ThreatScore Compliance Framework for M365 ensures that the M365 account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;123456789012;eu-west-1;{datetime.now()};1.1.1;Ensure MFA is enabled for the 'root' user account;MFA enabled for 'root';1. IAM;1.1 Authentication;The root user account holds the highest level of privileges within an AWS account. Enabling Multi-Factor Authentication (MFA) enhances security by adding an additional layer of protection beyond just a username and password. With MFA activated, users must provide their credentials (username and password) along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;Enabling MFA enhances console security by requiring the authenticating user to both possess a time-sensitive key-generating device and have knowledge of their credentials.;5;1000;PASS;;;;test-check-id;False\r\nm365;Prowler ThreatScore Compliance Framework for M365 ensures that the M365 account is compliant taking into account four main pillars: Identity and Access Management, Attack Surface, Forensic Readiness and Encryption;;;{datetime.now()};1.1.2;Ensure hardware MFA is enabled for the 'root' user account;CloudTrail logging enabled;1. IAM;1.1 Authentication;The root user account in AWS has the highest level of privileges. Multi-Factor Authentication (MFA) enhances security by adding an extra layer of protection beyond a username and password. When MFA is enabled, users must enter their credentials along with a unique authentication code generated by their AWS MFA device when signing into an AWS website.;A hardware MFA has a smaller attack surface compared to a virtual MFA. Unlike a virtual MFA, which relies on a mobile device that may be vulnerable to malware or compromise, a hardware MFA operates independently, reducing exposure to potential security threats.;3;10;MANUAL;Manual check;manual_check;Manual check;manual;False\r\n" + + assert content == expected_csv From 6d005540823c6e06f3045090a6c9fbd350174e62 Mon Sep 17 00:00:00 2001 From: Sergio Garcia Date: Wed, 21 May 2025 14:01:54 +0200 Subject: [PATCH 21/25] chore(readme): add Prowler Hub link (#7814) Co-authored-by: Pepe Fagoaga --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 24f6201f46..7c7c12d3fc 100644 --- a/README.md +++ b/README.md @@ -94,13 +94,19 @@ prowler dashboard | M365 | 44 | 2 | 2 | 0 | | NHN (Unofficial) | 6 | 2 | 1 | 0 | +> [!Note] +> The numbers in the table are updated periodically. + +> [!Tip] +> For the most accurate and up-to-date information about checks, services, frameworks, and categories, visit [**Prowler Hub**](https://hub.prowler.com). + +> [!Note] > Use the following commands to list Prowler's available checks, services, compliance frameworks, and categories: `prowler --list-checks`, `prowler --list-services`, `prowler --list-compliance` and `prowler --list-categories`. # 💻 Installation ## Prowler App -Installing Prowler App Prowler App offers flexible installation methods tailored to various environments: > For detailed instructions on using Prowler App, refer to the [Prowler App Usage Guide](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/prowler-app/). From dddec4c688d5f5046b119d3ba6d9670b5185c652 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Wed, 21 May 2025 15:13:03 +0200 Subject: [PATCH 22/25] fix(m365): add `powershell.close()` to `msgraph` services (#7816) --- prowler/CHANGELOG.md | 1 + .../providers/m365/services/admincenter/admincenter_service.py | 3 +++ prowler/providers/m365/services/entra/entra_service.py | 2 ++ .../providers/m365/services/sharepoint/sharepoint_service.py | 3 +++ 4 files changed, 9 insertions(+) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 7836182c3a..f3a117ba4d 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -17,6 +17,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### Fixed - Fix `m365_powershell test_credentials` to use sanitized credentials. [(#7761)](https://github.com/prowler-cloud/prowler/pull/7761) - Fix `admincenter_users_admins_reduced_license_footprint` check logic to pass when admin user has no license. [(#7779)](https://github.com/prowler-cloud/prowler/pull/7779) +- Fix `m365_powershell` to close the PowerShell sessions in msgraph services. [(#7816)](https://github.com/prowler-cloud/prowler/pull/7816) --- diff --git a/prowler/providers/m365/services/admincenter/admincenter_service.py b/prowler/providers/m365/services/admincenter/admincenter_service.py index bb1cad48ba..feac9c8375 100644 --- a/prowler/providers/m365/services/admincenter/admincenter_service.py +++ b/prowler/providers/m365/services/admincenter/admincenter_service.py @@ -11,6 +11,9 @@ from prowler.providers.m365.m365_provider import M365Provider class AdminCenter(M365Service): def __init__(self, provider: M365Provider): super().__init__(provider) + if self.powershell: + self.powershell.close() + self.organization_config = None self.sharing_policy = None if self.powershell: diff --git a/prowler/providers/m365/services/entra/entra_service.py b/prowler/providers/m365/services/entra/entra_service.py index 3e548afdba..b14f82b03c 100644 --- a/prowler/providers/m365/services/entra/entra_service.py +++ b/prowler/providers/m365/services/entra/entra_service.py @@ -14,6 +14,8 @@ from prowler.providers.m365.m365_provider import M365Provider class Entra(M365Service): def __init__(self, provider: M365Provider): super().__init__(provider) + if self.powershell: + self.powershell.close() loop = get_event_loop() self.tenant_domain = provider.identity.tenant_domain diff --git a/prowler/providers/m365/services/sharepoint/sharepoint_service.py b/prowler/providers/m365/services/sharepoint/sharepoint_service.py index 02a701821f..244f27521f 100644 --- a/prowler/providers/m365/services/sharepoint/sharepoint_service.py +++ b/prowler/providers/m365/services/sharepoint/sharepoint_service.py @@ -13,6 +13,9 @@ from prowler.providers.m365.m365_provider import M365Provider class SharePoint(M365Service): def __init__(self, provider: M365Provider): super().__init__(provider) + if self.powershell: + self.powershell.close() + loop = get_event_loop() self.tenant_domain = provider.identity.tenant_domain attributes = loop.run_until_complete( From 62dcbc29613fde0b053c39d9b2e0186ef299266a Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Wed, 21 May 2025 15:28:30 +0200 Subject: [PATCH 23/25] feat(repository): add new check `repository_has_codeowners_file` (#7752) --- prowler/CHANGELOG.md | 1 + .../__init__.py | 0 ...pository_has_codeowners_file.metadata.json | 30 +++++ .../repository_has_codeowners_file.py | 42 +++++++ .../services/repository/repository_service.py | 43 +++++-- .../repository_has_codeowners_file_test.py | 112 ++++++++++++++++++ .../repository/repository_service_test.py | 26 +++- 7 files changed, 242 insertions(+), 12 deletions(-) create mode 100644 prowler/providers/github/services/repository/repository_has_codeowners_file/__init__.py create mode 100644 prowler/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file.metadata.json create mode 100644 prowler/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file.py create mode 100644 tests/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index f3a117ba4d..fc0028d6a2 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add new check `admincenter_external_calendar_sharing_disabled` for M365 provider. [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733) - Add a level for Prowler ThreatScore in the accordion in Dashboard. [(#7739)](https://github.com/prowler-cloud/prowler/pull/7739) - Add CIS 4.0 compliance framework for GCP. [(7785)](https://github.com/prowler-cloud/prowler/pull/7785) +- Add `repository_has_codeowners_file` check for GitHub provider. [(#7752)](https://github.com/prowler-cloud/prowler/pull/7752) ### Fixed - Fix `m365_powershell test_credentials` to use sanitized credentials. [(#7761)](https://github.com/prowler-cloud/prowler/pull/7761) diff --git a/prowler/providers/github/services/repository/repository_has_codeowners_file/__init__.py b/prowler/providers/github/services/repository/repository_has_codeowners_file/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file.metadata.json b/prowler/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file.metadata.json new file mode 100644 index 0000000000..58cb6ce8d2 --- /dev/null +++ b/prowler/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "github", + "CheckID": "repository_has_codeowners_file", + "CheckTitle": "Check if repositories have a CODEOWNERS file", + "CheckType": [], + "ServiceName": "repository", + "SubServiceName": "", + "ResourceIdTemplate": "github:user-id:repository/repository-name", + "Severity": "high", + "ResourceType": "GitHubRepository", + "Description": "Ensure that repositories have a CODEOWNERS file.", + "Risk": "Not having a CODEOWNERS file in a repository may lead to unclear code ownership and review responsibilities, increasing the risk of unreviewed or unauthorized changes.", + "RelatedUrl": "https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Add a CODEOWNERS file to the root, .github/, or docs/ directory of the repository. The file should specify code owners for files and directories as appropriate for your organization.", + "Url": "https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file.py b/prowler/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file.py new file mode 100644 index 0000000000..a158f63880 --- /dev/null +++ b/prowler/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file.py @@ -0,0 +1,42 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportGithub +from prowler.providers.github.services.repository.repository_client import ( + repository_client, +) + + +class repository_has_codeowners_file(Check): + """Check if a repository has a CODEOWNERS file + + This class verifies whether each repository has a CODEOWNERS file. + """ + + def execute(self) -> List[CheckReportGithub]: + """Execute the Github Repository CODEOWNERS file existence check + + Iterates over all repositories and checks if they have a CODEOWNERS file. + + Returns: + List[CheckReportGithub]: A list of reports for each repository + """ + findings = [] + for repo in repository_client.repositories.values(): + if repo.codeowners_exists is not None: + report = CheckReportGithub( + metadata=self.metadata(), resource=repo, repository=repo.name + ) + if repo.codeowners_exists: + report.status = "PASS" + report.status_extended = ( + f"Repository {repo.name} does have a CODEOWNERS file." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"Repository {repo.name} does not have a CODEOWNERS file." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/github/services/repository/repository_service.py b/prowler/providers/github/services/repository/repository_service.py index 571dda3429..719a4ac71c 100644 --- a/prowler/providers/github/services/repository/repository_service.py +++ b/prowler/providers/github/services/repository/repository_service.py @@ -11,6 +11,19 @@ class Repository(GithubService): super().__init__(__class__.__name__, provider) self.repositories = self._list_repositories() + def _file_exists(self, repo, filename): + """Check if a file exists in the repository. Returns True if exists, False if not, None if error.""" + try: + return repo.get_contents(filename) is not None + except Exception as error: + if "404" in str(error): + return False + else: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return None + def _list_repositories(self): logger.info("Repository - Listing Repositories...") repos = {} @@ -18,22 +31,28 @@ class Repository(GithubService): for client in self.clients: for repo in client.get_user().get_repos(): default_branch = repo.default_branch + securitymd_exists = self._file_exists(repo, "SECURITY.md") + # CODEOWNERS file can be in .github/, root, or docs/ + # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-file-location + codeowners_paths = [ + ".github/CODEOWNERS", + "CODEOWNERS", + "docs/CODEOWNERS", + ] + codeowners_files = [ + self._file_exists(repo, path) for path in codeowners_paths + ] + if True in codeowners_files: + codeowners_exists = True + elif all(file is None for file in codeowners_files): + codeowners_exists = None + else: + codeowners_exists = False delete_branch_on_merge = ( repo.delete_branch_on_merge if repo.delete_branch_on_merge is not None else False ) - securitymd_exists = False - try: - securitymd_exists = repo.get_contents("SECURITY.md") is not None - except Exception as error: - if "404" in str(error): - securitymd_exists = False - else: - logger.error( - f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - securitymd_exists = None require_pr = False approval_cnt = 0 @@ -107,6 +126,7 @@ class Repository(GithubService): enforce_admins=enforce_admins, conversation_resolution=conversation_resolution, default_branch_protection=branch_protection, + codeowners_exists=codeowners_exists, delete_branch_on_merge=delete_branch_on_merge, ) @@ -134,5 +154,6 @@ class Repo(BaseModel): status_checks: Optional[bool] enforce_admins: Optional[bool] approval_count: Optional[int] + codeowners_exists: Optional[bool] delete_branch_on_merge: Optional[bool] conversation_resolution: Optional[bool] diff --git a/tests/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file_test.py b/tests/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file_test.py new file mode 100644 index 0000000000..57cb5a53e7 --- /dev/null +++ b/tests/providers/github/services/repository/repository_has_codeowners_file/repository_has_codeowners_file_test.py @@ -0,0 +1,112 @@ +from unittest import mock + +from prowler.providers.github.services.repository.repository_service import Repo +from tests.providers.github.github_fixtures import set_mocked_github_provider + + +class Test_repository_has_codeowners_file: + def test_no_repositories(self): + repository_client = mock.MagicMock + repository_client.repositories = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_has_codeowners_file.repository_has_codeowners_file.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_has_codeowners_file.repository_has_codeowners_file import ( + repository_has_codeowners_file, + ) + + check = repository_has_codeowners_file() + result = check.execute() + assert len(result) == 0 + + def test_one_repository_no_codeowners(self): + repository_client = mock.MagicMock + repo_name = "repo1" + repository_client.repositories = { + 1: Repo( + id=1, + name=repo_name, + full_name="account-name/repo1", + default_branch="main", + private=False, + securitymd=True, + require_pull_request=False, + approval_count=0, + codeowners_exists=False, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_has_codeowners_file.repository_has_codeowners_file.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_has_codeowners_file.repository_has_codeowners_file import ( + repository_has_codeowners_file, + ) + + check = repository_has_codeowners_file() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_id == 1 + assert result[0].resource_name == repo_name + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Repository {repo_name} does not have a CODEOWNERS file." + ) + + def test_one_repository_with_codeowners(self): + repository_client = mock.MagicMock + repo_name = "repo2" + repository_client.repositories = { + 2: Repo( + id=2, + name=repo_name, + full_name="account-name/repo2", + default_branch="main", + private=False, + securitymd=True, + require_pull_request=False, + approval_count=0, + codeowners_exists=True, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_has_codeowners_file.repository_has_codeowners_file.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_has_codeowners_file.repository_has_codeowners_file import ( + repository_has_codeowners_file, + ) + + check = repository_has_codeowners_file() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_id == 2 + assert result[0].resource_name == repo_name + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Repository {repo_name} does have a CODEOWNERS file." + ) diff --git a/tests/providers/github/services/repository/repository_service_test.py b/tests/providers/github/services/repository/repository_service_test.py index bcc3b19791..4b9d45b479 100644 --- a/tests/providers/github/services/repository/repository_service_test.py +++ b/tests/providers/github/services/repository/repository_service_test.py @@ -1,4 +1,4 @@ -from unittest.mock import patch +from unittest.mock import MagicMock, patch from prowler.providers.github.services.repository.repository_service import ( Repo, @@ -23,6 +23,7 @@ def mock_list_repositories(_): default_branch_deletion=True, status_checks=True, approval_count=2, + codeowners_exists=True, enforce_admins=True, delete_branch_on_merge=True, conversation_resolution=True, @@ -60,3 +61,26 @@ class Test_Repository_Service: assert repository_service.repositories[1].delete_branch_on_merge assert repository_service.repositories[1].conversation_resolution assert repository_service.repositories[1].approval_count == 2 + assert repository_service.repositories[1].codeowners_exists is True + + +class Test_Repository_FileExists: + def setup_method(self): + self.repository = Repository(set_mocked_github_provider()) + self.mock_repo = MagicMock() + + def test_file_exists_returns_true(self): + self.mock_repo.get_contents.return_value = object() + assert self.repository._file_exists(self.mock_repo, "somefile.txt") is True + + def test_file_not_found_returns_false(self): + self.mock_repo.get_contents.side_effect = Exception("404 Not Found") + assert self.repository._file_exists(self.mock_repo, "nofile.txt") is False + + def test_other_error_returns_none_and_logs(self): + self.mock_repo.get_contents.side_effect = Exception("Some other error") + with patch( + "prowler.providers.github.services.repository.repository_service.logger" + ) as mock_logger: + assert self.repository._file_exists(self.mock_repo, "errorfile.txt") is None + assert mock_logger.error.called From f72eb7e212ca6d8a0926c793a61d21f1bb9f26f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 21 May 2025 16:15:04 +0200 Subject: [PATCH 24/25] fix(files): remove empty files (#7819) --- prowler_threatscore_aws | 0 prowler_threatscore_azure | 0 prowler_threatscore_gcp | 0 prowler_threatscore_m365 | 0 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 prowler_threatscore_aws delete mode 100644 prowler_threatscore_azure delete mode 100644 prowler_threatscore_gcp delete mode 100644 prowler_threatscore_m365 diff --git a/prowler_threatscore_aws b/prowler_threatscore_aws deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/prowler_threatscore_azure b/prowler_threatscore_azure deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/prowler_threatscore_gcp b/prowler_threatscore_gcp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/prowler_threatscore_m365 b/prowler_threatscore_m365 deleted file mode 100644 index e69de29bb2..0000000000 From d036e0054b39980a3e6c4cc2ad76cd0a35c6414c Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Wed, 21 May 2025 16:18:55 +0200 Subject: [PATCH 25/25] feat(repository): add new check `repository_default_branch_requires_codeowners_review` (#7753) Co-authored-by: Sergio Garcia --- prowler/CHANGELOG.md | 1 + .../__init__.py | 0 ...h_requires_codeowners_review.metadata.json | 30 +++++ ...fault_branch_requires_codeowners_review.py | 38 ++++++ .../services/repository/repository_service.py | 9 ++ ..._branch_requires_codeowners_review_test.py | 112 ++++++++++++++++++ .../repository/repository_service_test.py | 2 + 7 files changed, 192 insertions(+) create mode 100644 prowler/providers/github/services/repository/repository_default_branch_requires_codeowners_review/__init__.py create mode 100644 prowler/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review.metadata.json create mode 100644 prowler/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review.py create mode 100644 tests/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index fc0028d6a2..3b3f77db43 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add a level for Prowler ThreatScore in the accordion in Dashboard. [(#7739)](https://github.com/prowler-cloud/prowler/pull/7739) - Add CIS 4.0 compliance framework for GCP. [(7785)](https://github.com/prowler-cloud/prowler/pull/7785) - Add `repository_has_codeowners_file` check for GitHub provider. [(#7752)](https://github.com/prowler-cloud/prowler/pull/7752) +- Add `repository_default_branch_requires_codeowners_review` check for GitHub provider. [(#7753)](https://github.com/prowler-cloud/prowler/pull/7753) ### Fixed - Fix `m365_powershell test_credentials` to use sanitized credentials. [(#7761)](https://github.com/prowler-cloud/prowler/pull/7761) diff --git a/prowler/providers/github/services/repository/repository_default_branch_requires_codeowners_review/__init__.py b/prowler/providers/github/services/repository/repository_default_branch_requires_codeowners_review/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review.metadata.json b/prowler/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review.metadata.json new file mode 100644 index 0000000000..8c19b24818 --- /dev/null +++ b/prowler/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "github", + "CheckID": "repository_default_branch_requires_codeowners_review", + "CheckTitle": "Check if code owner approval is required for changes to owned code", + "CheckType": [], + "ServiceName": "repository", + "SubServiceName": "", + "ResourceIdTemplate": "github:user-id:repository/repository-name", + "Severity": "high", + "ResourceType": "GitHubRepository", + "Description": "Ensure that code owners are required to review and approve any proposed changes that affect their respective areas of ownership in the code base.", + "Risk": "If code owner approval is not required, unauthorized or unqualified individuals may merge changes to sensitive or critical areas of the codebase, increasing the risk of security vulnerabilities, bugs, or malicious modifications.", + "RelatedUrl": "https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#requiring-code-owner-review", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "To require code owner review, navigate to the repository settings, click on 'Branches', add or edit a branch protection rule, and enable 'Require review from Code Owners'.", + "Url": "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-review-from-code-owners" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review.py b/prowler/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review.py new file mode 100644 index 0000000000..11cc3fd05b --- /dev/null +++ b/prowler/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review.py @@ -0,0 +1,38 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportGithub +from prowler.providers.github.services.repository.repository_client import ( + repository_client, +) + + +class repository_default_branch_requires_codeowners_review(Check): + """Check if code owner approval is required for changes to owned code + + This class verifies whether each repository requires code owner review for changes to code they own. + """ + + def execute(self) -> List[CheckReportGithub]: + """Execute the Github Repository Code Owner Approval Requirement check + + Iterates over all repositories and checks if they require code owner review for changes. + + Returns: + List[CheckReportGithub]: A list of reports for each repository + """ + findings = [] + for repo in repository_client.repositories.values(): + if repo.require_code_owner_reviews is not None: + report = CheckReportGithub( + metadata=self.metadata(), resource=repo, repository=repo.name + ) + if repo.require_code_owner_reviews: + report.status = "PASS" + report.status_extended = f"Repository {repo.name} requires code owner approval for changes to owned code." + else: + report.status = "FAIL" + report.status_extended = f"Repository {repo.name} does not require code owner approval for changes to owned code." + + findings.append(report) + + return findings diff --git a/prowler/providers/github/services/repository/repository_service.py b/prowler/providers/github/services/repository/repository_service.py index 719a4ac71c..a3afffccc1 100644 --- a/prowler/providers/github/services/repository/repository_service.py +++ b/prowler/providers/github/services/repository/repository_service.py @@ -60,6 +60,7 @@ class Repository(GithubService): required_linear_history = False allow_force_pushes = True branch_deletion = True + require_code_owner_reviews = False status_checks = False enforce_admins = False conversation_resolution = False @@ -89,6 +90,11 @@ class Repository(GithubService): protection.required_conversation_resolution ) branch_protection = True + require_code_owner_reviews = ( + protection.required_pull_request_reviews.require_code_owner_reviews + if require_pr + else False + ) except Exception as error: # If the branch is not found, it is not protected if "404" in str(error): @@ -103,6 +109,7 @@ class Repository(GithubService): required_linear_history = None allow_force_pushes = None branch_deletion = None + require_code_owner_reviews = None status_checks = None enforce_admins = None conversation_resolution = None @@ -127,6 +134,7 @@ class Repository(GithubService): conversation_resolution=conversation_resolution, default_branch_protection=branch_protection, codeowners_exists=codeowners_exists, + require_code_owner_reviews=require_code_owner_reviews, delete_branch_on_merge=delete_branch_on_merge, ) @@ -155,5 +163,6 @@ class Repo(BaseModel): enforce_admins: Optional[bool] approval_count: Optional[int] codeowners_exists: Optional[bool] + require_code_owner_reviews: Optional[bool] delete_branch_on_merge: Optional[bool] conversation_resolution: Optional[bool] diff --git a/tests/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review_test.py b/tests/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review_test.py new file mode 100644 index 0000000000..fe64448402 --- /dev/null +++ b/tests/providers/github/services/repository/repository_default_branch_requires_codeowners_review/repository_default_branch_requires_codeowners_review_test.py @@ -0,0 +1,112 @@ +from unittest import mock + +from prowler.providers.github.services.repository.repository_service import Repo +from tests.providers.github.github_fixtures import set_mocked_github_provider + + +class Test_repository_default_branch_requires_codeowners_review: + def test_no_repositories(self): + repository_client = mock.MagicMock + repository_client.repositories = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_default_branch_requires_codeowners_review.repository_default_branch_requires_codeowners_review.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_default_branch_requires_codeowners_review.repository_default_branch_requires_codeowners_review import ( + repository_default_branch_requires_codeowners_review, + ) + + check = repository_default_branch_requires_codeowners_review() + result = check.execute() + assert len(result) == 0 + + def test_one_repository_no_codeowner_approval(self): + repository_client = mock.MagicMock + repo_name = "repo1" + repository_client.repositories = { + 1: Repo( + id=1, + name=repo_name, + full_name="account-name/repo1", + default_branch="main", + private=False, + securitymd=True, + require_pull_request=False, + approval_count=0, + require_code_owner_reviews=False, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_default_branch_requires_codeowners_review.repository_default_branch_requires_codeowners_review.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_default_branch_requires_codeowners_review.repository_default_branch_requires_codeowners_review import ( + repository_default_branch_requires_codeowners_review, + ) + + check = repository_default_branch_requires_codeowners_review() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_id == 1 + assert result[0].resource_name == repo_name + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Repository {repo_name} does not require code owner approval for changes to owned code." + ) + + def test_one_repository_with_codeowner_approval(self): + repository_client = mock.MagicMock + repo_name = "repo2" + repository_client.repositories = { + 2: Repo( + id=2, + name=repo_name, + full_name="account-name/repo2", + default_branch="main", + private=False, + securitymd=True, + require_pull_request=False, + approval_count=0, + require_code_owner_reviews=True, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.repository.repository_default_branch_requires_codeowners_review.repository_default_branch_requires_codeowners_review.repository_client", + new=repository_client, + ), + ): + from prowler.providers.github.services.repository.repository_default_branch_requires_codeowners_review.repository_default_branch_requires_codeowners_review import ( + repository_default_branch_requires_codeowners_review, + ) + + check = repository_default_branch_requires_codeowners_review() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_id == 2 + assert result[0].resource_name == repo_name + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Repository {repo_name} requires code owner approval for changes to owned code." + ) diff --git a/tests/providers/github/services/repository/repository_service_test.py b/tests/providers/github/services/repository/repository_service_test.py index 4b9d45b479..82535fad14 100644 --- a/tests/providers/github/services/repository/repository_service_test.py +++ b/tests/providers/github/services/repository/repository_service_test.py @@ -24,6 +24,7 @@ def mock_list_repositories(_): status_checks=True, approval_count=2, codeowners_exists=True, + require_code_owner_reviews=True, enforce_admins=True, delete_branch_on_merge=True, conversation_resolution=True, @@ -62,6 +63,7 @@ class Test_Repository_Service: assert repository_service.repositories[1].conversation_resolution assert repository_service.repositories[1].approval_count == 2 assert repository_service.repositories[1].codeowners_exists is True + assert repository_service.repositories[1].require_code_owner_reviews is True class Test_Repository_FileExists: