From 22f79edec50bbbd33f4e0c66bb183f3eb805b19e Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Fri, 13 Mar 2026 16:31:39 +0100 Subject: [PATCH 001/366] refactor(ui): replace HeroUI Snippet with CodeSnippet component (#10319) --- .../integrations/saml/saml-config-form.tsx | 16 +++--- .../invitations/invitation-details.tsx | 17 +------ ui/components/scans/table/scan-detail.tsx | 14 +++-- .../ui/code-snippet/code-snippet.tsx | 31 +++++++---- ui/components/ui/entities/index.ts | 1 - ui/components/ui/entities/snippet-chip.tsx | 51 ------------------- .../ui/table/data-table-pagination.tsx | 2 +- .../users/profile/api-key-success-modal.tsx | 21 +++----- .../users/profile/membership-item.tsx | 4 +- .../users/profile/revoke-api-key-modal.tsx | 18 +++---- ui/components/users/profile/role-item.tsx | 1 + .../users/profile/user-basic-info-card.tsx | 5 +- 12 files changed, 61 insertions(+), 120 deletions(-) delete mode 100644 ui/components/ui/entities/snippet-chip.tsx diff --git a/ui/components/integrations/saml/saml-config-form.tsx b/ui/components/integrations/saml/saml-config-form.tsx index d52e4e4ba7..daeb301832 100644 --- a/ui/components/integrations/saml/saml-config-form.tsx +++ b/ui/components/integrations/saml/saml-config-form.tsx @@ -14,9 +14,9 @@ import { createSamlConfig, updateSamlConfig } from "@/actions/integrations"; import { AddIcon } from "@/components/icons"; import { Button, Card, CardContent, CardHeader } from "@/components/shadcn"; import { useToast } from "@/components/ui"; +import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; import { CustomServerInput } from "@/components/ui/custom"; import { CustomLink } from "@/components/ui/custom/custom-link"; -import { SnippetChip } from "@/components/ui/entities"; import { FormButtons } from "@/components/ui/form"; import { apiBaseUrl } from "@/lib"; @@ -258,7 +258,11 @@ export const SamlConfigForm = ({ : `${apiBaseUrl}/accounts/saml/your-domain.com/acs/`; return ( -
+
Need help configuring SAML SSO?{" "} ACS URL: -
@@ -315,9 +319,9 @@ export const SamlConfigForm = ({ Audience: - diff --git a/ui/components/invitations/invitation-details.tsx b/ui/components/invitations/invitation-details.tsx index 38d831bbfa..0df447907f 100644 --- a/ui/components/invitations/invitation-details.tsx +++ b/ui/components/invitations/invitation-details.tsx @@ -1,11 +1,11 @@ "use client"; -import { Snippet } from "@heroui/snippet"; import Link from "next/link"; import { AddIcon } from "../icons"; import { Button, Card, CardContent, CardHeader } from "../shadcn"; import { Separator } from "../shadcn/separator/separator"; +import { CodeSnippet } from "../ui/code-snippet/code-snippet"; import { DateWithTime } from "../ui/entities"; interface InvitationDetailsProps { @@ -89,20 +89,7 @@ export const InvitationDetails = ({ attributes }: InvitationDetailsProps) => { Share this link with the user: -
- -

{invitationLink}

-
-
+
diff --git a/ui/components/scans/table/scan-detail.tsx b/ui/components/scans/table/scan-detail.tsx index 0cedd7e9c0..6c650271a3 100644 --- a/ui/components/scans/table/scan-detail.tsx +++ b/ui/components/scans/table/scan-detail.tsx @@ -1,8 +1,7 @@ "use client"; -import { Snippet } from "@heroui/snippet"; - import { Card, CardContent, CardHeader, CardTitle } from "@/components/shadcn"; +import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; import { DateWithTime, EntityInfo, InfoField } from "@/components/ui/entities"; import { StatusBadge } from "@/components/ui/table/status-badge"; import { ProviderProps, ProviderType, ScanProps, TaskDetails } from "@/types"; @@ -81,18 +80,17 @@ export const ScanDetail = ({
- {scanDetails.id} + {scan.state === "failed" && taskDetails?.attributes.result && ( <> {taskDetails.attributes.result.exc_message && ( - - - {taskDetails.attributes.result.exc_message.join("\n")} - - + )}
diff --git a/ui/components/ui/code-snippet/code-snippet.tsx b/ui/components/ui/code-snippet/code-snippet.tsx index 7fefa8b552..9440b500e6 100644 --- a/ui/components/ui/code-snippet/code-snippet.tsx +++ b/ui/components/ui/code-snippet/code-snippet.tsx @@ -21,6 +21,10 @@ interface CodeSnippetProps { icon?: ReactNode; /** Function to format the displayed text (value is still copied as-is) */ formatter?: (value: string) => string; + /** Enable multiline display (disables truncation, enables word wrap) */ + multiline?: boolean; + /** Custom aria-label for the copy button */ + ariaLabel?: string; } export const CodeSnippet = ({ @@ -30,6 +34,8 @@ export const CodeSnippet = ({ hideCopyButton = false, icon, formatter, + multiline = false, + ariaLabel = "Copy to clipboard", }: CodeSnippetProps) => { const [copied, setCopied] = useState(false); @@ -46,7 +52,7 @@ export const CodeSnippet = ({ type="button" onClick={handleCopy} className="text-text-neutral-secondary hover:text-text-neutral-primary shrink-0 cursor-pointer transition-colors" - aria-label="Copy to clipboard" + aria-label={ariaLabel} > {copied ? ( @@ -66,7 +72,7 @@ export const CodeSnippet = ({ "hover:bg-bg-neutral-tertiary text-text-neutral-secondary hover:text-text-neutral-primary shrink-0 cursor-pointer rounded-md p-1 transition-colors", className, )} - aria-label="Copy to clipboard" + aria-label={ariaLabel} > {copied ? ( @@ -80,19 +86,26 @@ export const CodeSnippet = ({ return (
{icon && ( {icon} )} - - - {displayValue} - - {value} - + {multiline ? ( + + {displayValue} + + ) : ( + + + {displayValue} + + {value} + + )} {!hideCopyButton && }
); diff --git a/ui/components/ui/entities/index.ts b/ui/components/ui/entities/index.ts index 88cf9504f3..d2b2b9aa22 100644 --- a/ui/components/ui/entities/index.ts +++ b/ui/components/ui/entities/index.ts @@ -3,4 +3,3 @@ export * from "./entity-info"; export * from "./get-provider-logo"; export * from "./info-field"; export * from "./scan-status"; -export * from "./snippet-chip"; diff --git a/ui/components/ui/entities/snippet-chip.tsx b/ui/components/ui/entities/snippet-chip.tsx deleted file mode 100644 index 04dabfde67..0000000000 --- a/ui/components/ui/entities/snippet-chip.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Snippet } from "@heroui/snippet"; -import { cn } from "@heroui/theme"; -import { Tooltip } from "@heroui/tooltip"; -import React from "react"; - -import { CopyIcon, DoneIcon } from "@/components/icons"; - -interface SnippetChipProps { - value: string; - ariaLabel?: string; - icon?: React.ReactNode; - hideCopyButton?: boolean; - formatter?: (value: string) => string; - className?: string; -} -export const SnippetChip = ({ - value, - hideCopyButton = false, - ariaLabel = `Copy ${value} to clipboard`, - icon, - formatter, - className, - ...props -}: SnippetChipProps) => { - return ( - } - checkIcon={} - hideCopyButton={hideCopyButton} - codeString={value} - {...props} - > -
- {icon} - - - {formatter ? formatter(value) : value} - - -
-
- ); -}; diff --git a/ui/components/ui/table/data-table-pagination.tsx b/ui/components/ui/table/data-table-pagination.tsx index 6ba2d9a158..7f3f5eb83d 100644 --- a/ui/components/ui/table/data-table-pagination.tsx +++ b/ui/components/ui/table/data-table-pagination.tsx @@ -191,7 +191,7 @@ export function DataTablePagination({ {/* Page info and navigation */}
- + Page {currentPage} of {totalPages}
diff --git a/ui/components/users/profile/api-key-success-modal.tsx b/ui/components/users/profile/api-key-success-modal.tsx index c3e039cab8..ee170b3738 100644 --- a/ui/components/users/profile/api-key-success-modal.tsx +++ b/ui/components/users/profile/api-key-success-modal.tsx @@ -1,10 +1,9 @@ "use client"; -import { Snippet } from "@heroui/snippet"; - import { Button } from "@/components/shadcn"; import { Modal } from "@/components/shadcn/modal"; import { Alert, AlertDescription } from "@/components/ui/alert/Alert"; +import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; interface ApiKeySuccessModalProps { isOpen: boolean; @@ -36,18 +35,12 @@ export const ApiKeySuccessModal = ({

Your API Key

- - {apiKey} - +
diff --git a/ui/components/users/profile/membership-item.tsx b/ui/components/users/profile/membership-item.tsx index b13574ad8e..23c616600c 100644 --- a/ui/components/users/profile/membership-item.tsx +++ b/ui/components/users/profile/membership-item.tsx @@ -32,8 +32,8 @@ export const MembershipItem = ({ setIsOpen={setIsEditOpen} /> - -
+ +
{membership.attributes.role} diff --git a/ui/components/users/profile/revoke-api-key-modal.tsx b/ui/components/users/profile/revoke-api-key-modal.tsx index 781440bc92..6097b747c5 100644 --- a/ui/components/users/profile/revoke-api-key-modal.tsx +++ b/ui/components/users/profile/revoke-api-key-modal.tsx @@ -1,6 +1,5 @@ "use client"; -import { Snippet } from "@heroui/snippet"; import { Trash2Icon } from "lucide-react"; import { revokeApiKey } from "@/actions/api-keys/api-keys"; @@ -10,6 +9,7 @@ import { AlertDescription, AlertTitle, } from "@/components/ui/alert/Alert"; +import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; import { ModalButtons } from "@/components/ui/custom/custom-modal-buttons"; import { FALLBACK_VALUES } from "./api-keys/constants"; @@ -67,16 +67,12 @@ export const RevokeApiKeyModal = ({

Are you sure you want to revoke this API key?

- -

{apiKey?.attributes.name || FALLBACK_VALUES.UNNAMED_KEY}

-

Prefix: {apiKey?.attributes.prefix}

-
+
{error && ( diff --git a/ui/components/users/profile/role-item.tsx b/ui/components/users/profile/role-item.tsx index 16264c58a7..5e1f6feef4 100644 --- a/ui/components/users/profile/role-item.tsx +++ b/ui/components/users/profile/role-item.tsx @@ -68,6 +68,7 @@ export const RoleItem = ({ variant="ghost" size="sm" onClick={() => setIsExpanded(!isExpanded)} + className="px-0" > {isExpanded ? "Hide details" : "Show details"} diff --git a/ui/components/users/profile/user-basic-info-card.tsx b/ui/components/users/profile/user-basic-info-card.tsx index fa3376ad84..4c85185e2d 100644 --- a/ui/components/users/profile/user-basic-info-card.tsx +++ b/ui/components/users/profile/user-basic-info-card.tsx @@ -4,13 +4,14 @@ import { Divider } from "@heroui/divider"; import { ProwlerShort } from "@/components/icons"; import { Card, CardContent } from "@/components/shadcn"; -import { DateWithTime, InfoField, SnippetChip } from "@/components/ui/entities"; +import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; +import { DateWithTime, InfoField } from "@/components/ui/entities"; import { UserDataWithRoles } from "@/types/users"; const TenantIdCopy = ({ id }: { id: string }) => { return (
- +
); }; From 4cd3b0981842f2ca533646aaca5a7d67aa009bdd Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Mon, 16 Mar 2026 10:32:12 +0100 Subject: [PATCH 002/366] feat(ui): add organization-specific actions to providers table dropdown (#10317) --- ui/actions/organizations/organizations.ts | 119 +++++++ .../providers/providers-page.utils.test.ts | 62 +--- .../providers/providers-page.utils.ts | 150 +-------- .../forms/delete-organization-form.tsx | 91 ++++++ ui/components/providers/forms/edit-form.tsx | 92 ------ .../providers/forms/edit-name-form.tsx | 123 ++++++++ ui/components/providers/forms/index.ts | 2 +- .../organizations/org-setup-form.tsx | 72 ++++- .../table/data-table-row-actions.test.tsx | 196 +++++++++++- .../table/data-table-row-actions.tsx | 294 +++++++++++++++--- .../hooks/use-provider-wizard-controller.ts | 26 +- .../wizard/provider-wizard-modal.tsx | 18 +- ui/components/providers/wizard/types.ts | 19 ++ ui/types/providers-table.ts | 2 - 14 files changed, 919 insertions(+), 347 deletions(-) create mode 100644 ui/components/providers/forms/delete-organization-form.tsx delete mode 100644 ui/components/providers/forms/edit-form.tsx create mode 100644 ui/components/providers/forms/edit-name-form.tsx diff --git a/ui/actions/organizations/organizations.ts b/ui/actions/organizations/organizations.ts index 6d0d0e6afd..9c3caf1080 100644 --- a/ui/actions/organizations/organizations.ts +++ b/ui/actions/organizations/organizations.ts @@ -92,6 +92,59 @@ export const createOrganization = async (formData: FormData) => { } }; +/** + * Updates an AWS Organization's name. + * PATCH /api/v1/organizations/{id} + */ +export const updateOrganizationName = async ( + organizationId: string, + name: string, +) => { + const trimmed = name.trim(); + if (!trimmed) { + return { error: "Organization name cannot be empty." }; + } + + const headers = await getAuthHeaders({ contentType: true }); + + const idValidation = validatePathIdentifier( + organizationId, + "Organization ID is required", + "Invalid organization ID", + ); + if ("error" in idValidation) { + return idValidation; + } + + const url = new URL( + `${apiBaseUrl}/organizations/${encodeURIComponent(idValidation.value)}`, + ); + + try { + const response = await fetch(url.toString(), { + method: "PATCH", + headers, + body: JSON.stringify({ + data: { + type: "organizations", + id: idValidation.value, + attributes: { + name: trimmed, + }, + }, + }), + }); + + const result = await handleApiResponse(response); + if (!hasActionError(result)) { + revalidatePath("/providers"); + } + return result; + } catch (error) { + return handleApiError(error); + } +}; + /** * Lists AWS Organizations filtered by external ID. * GET /api/v1/organizations?filter[external_id]={externalId}&filter[org_type]=aws @@ -273,6 +326,72 @@ export const listOrganizationSecretsByOrganizationId = async ( } }; +/** + * Deletes an AWS Organization resource. + * DELETE /api/v1/organizations/{id} + */ +export const deleteOrganization = async (organizationId: string) => { + const headers = await getAuthHeaders({ contentType: false }); + + const organizationIdValidation = validatePathIdentifier( + organizationId, + "Organization ID is required", + "Invalid organization ID", + ); + if ("error" in organizationIdValidation) { + return organizationIdValidation; + } + + const url = new URL( + `${apiBaseUrl}/organizations/${encodeURIComponent(organizationIdValidation.value)}`, + ); + + try { + const response = await fetch(url.toString(), { + method: "DELETE", + headers, + }); + + return handleApiResponse(response, "/providers"); + } catch (error) { + return handleApiError(error); + } +}; + +/** + * Deletes an organizational unit. + * DELETE /api/v1/organizational-units/{id} + */ +export const deleteOrganizationalUnit = async ( + organizationalUnitId: string, +) => { + const headers = await getAuthHeaders({ contentType: false }); + + const idValidation = validatePathIdentifier( + organizationalUnitId, + "Organizational unit ID is required", + "Invalid organizational unit ID", + ); + if ("error" in idValidation) { + return idValidation; + } + + const url = new URL( + `${apiBaseUrl}/organizational-units/${encodeURIComponent(idValidation.value)}`, + ); + + try { + const response = await fetch(url.toString(), { + method: "DELETE", + headers, + }); + + return handleApiResponse(response, "/providers"); + } catch (error) { + return handleApiError(error); + } +}; + /** * Triggers an async discovery of the AWS Organization. * POST /api/v1/organizations/{id}/discover diff --git a/ui/app/(prowler)/providers/providers-page.utils.test.ts b/ui/app/(prowler)/providers/providers-page.utils.test.ts index a937bcd89a..2bf4b4edd1 100644 --- a/ui/app/(prowler)/providers/providers-page.utils.test.ts +++ b/ui/app/(prowler)/providers/providers-page.utils.test.ts @@ -4,10 +4,6 @@ const providersActionsMock = vi.hoisted(() => ({ getProviders: vi.fn(), })); -const manageGroupsActionsMock = vi.hoisted(() => ({ - getProviderGroups: vi.fn(), -})); - const organizationsActionsMock = vi.hoisted(() => ({ listOrganizationsSafe: vi.fn(), listOrganizationUnitsSafe: vi.fn(), @@ -18,7 +14,6 @@ const scansActionsMock = vi.hoisted(() => ({ })); vi.mock("@/actions/providers", () => providersActionsMock); -vi.mock("@/actions/manage-groups", () => manageGroupsActionsMock); vi.mock( "@/actions/organizations/organizations", () => organizationsActionsMock, @@ -26,7 +21,6 @@ vi.mock( vi.mock("@/actions/scans", () => scansActionsMock); import { SearchParamsProps } from "@/types"; -import { ProviderGroupsResponse } from "@/types/components"; import { ProvidersApiResponse } from "@/types/providers"; import { ProvidersProviderRow } from "@/types/providers-table"; @@ -146,47 +140,6 @@ const providersResponse: ProvidersApiResponse = { }, }; -const providerGroupsResponse: ProviderGroupsResponse = { - links: { - first: "", - last: "", - next: null, - prev: null, - }, - data: [ - { - id: "group-1", - type: "provider-groups", - attributes: { - name: "AWS Team", - inserted_at: "2025-02-13T11:17:00Z", - updated_at: "2025-02-13T11:17:00Z", - }, - relationships: { - providers: { - meta: { count: 1 }, - data: [{ type: "providers", id: "provider-1" }], - }, - roles: { - meta: { count: 0 }, - data: [], - }, - }, - links: { - self: "", - }, - }, - ], - meta: { - pagination: { - page: 1, - pages: 1, - count: 1, - }, - version: "1", - }, -}; - const toProviderRow = ( provider: (typeof providersResponse.data)[number], overrides?: Partial, @@ -666,9 +619,6 @@ describe("loadProvidersAccountsViewData", () => { it("does not call organizations endpoints in OSS", async () => { // Given providersActionsMock.getProviders.mockResolvedValue(providersResponse); - manageGroupsActionsMock.getProviderGroups.mockResolvedValue( - providerGroupsResponse, - ); scansActionsMock.getScans.mockResolvedValue({ data: [] }); // When @@ -685,7 +635,7 @@ describe("loadProvidersAccountsViewData", () => { organizationsActionsMock.listOrganizationUnitsSafe, ).not.toHaveBeenCalled(); expect(viewData.filters.map((filter) => filter.labelCheckboxGroup)).toEqual( - ["Account Groups", "Status"], + ["Status"], ); }); @@ -712,9 +662,6 @@ describe("loadProvidersAccountsViewData", () => { }, })), }); - manageGroupsActionsMock.getProviderGroups.mockResolvedValue( - providerGroupsResponse, - ); organizationsActionsMock.listOrganizationsSafe.mockResolvedValue({ data: [ { @@ -769,7 +716,7 @@ describe("loadProvidersAccountsViewData", () => { organizationsActionsMock.listOrganizationUnitsSafe, ).toHaveBeenCalledTimes(1); expect(viewData.filters.map((filter) => filter.labelCheckboxGroup)).toEqual( - ["Organizations", "Account Groups", "Status"], + ["Status"], ); expect(viewData.rows[0].rowType).toBe(PROVIDERS_ROW_TYPE.ORGANIZATION); }); @@ -777,9 +724,6 @@ describe("loadProvidersAccountsViewData", () => { it("falls back to empty cloud grouping data when organizations endpoints fail", async () => { // Given providersActionsMock.getProviders.mockResolvedValue(providersResponse); - manageGroupsActionsMock.getProviderGroups.mockResolvedValue( - providerGroupsResponse, - ); organizationsActionsMock.listOrganizationsSafe.mockResolvedValue({ data: [], }); @@ -796,7 +740,7 @@ describe("loadProvidersAccountsViewData", () => { // Then expect(viewData.filters.map((filter) => filter.labelCheckboxGroup)).toEqual( - ["Account Groups", "Status"], + ["Status"], ); expect(viewData.rows).toHaveLength(2); expect( diff --git a/ui/app/(prowler)/providers/providers-page.utils.ts b/ui/app/(prowler)/providers/providers-page.utils.ts index 5781020354..781fb1fc6f 100644 --- a/ui/app/(prowler)/providers/providers-page.utils.ts +++ b/ui/app/(prowler)/providers/providers-page.utils.ts @@ -1,4 +1,3 @@ -import { getProviderGroups } from "@/actions/manage-groups"; import { listOrganizationsSafe, listOrganizationUnitsSafe, @@ -13,10 +12,8 @@ import { FilterEntity, FilterOption, OrganizationListResponse, - OrganizationResource, OrganizationUnitListResponse, OrganizationUnitResource, - ProviderGroupsResponse, ProvidersApiResponse, SearchParamsProps, } from "@/types"; @@ -52,11 +49,6 @@ interface ProvidersAccountsViewInput { searchParams: SearchParamsProps; } -interface ProvidersTableLocalFilters { - organizationIds: string[]; - providerGroupIds: string[]; -} - function hasActionError(result: unknown): result is { error: unknown; } { @@ -86,56 +78,16 @@ async function resolveActionResult( } } -const createProvidersFilters = ({ - isCloud, - organizations, - providerGroups, -}: { - isCloud: boolean; - organizations: OrganizationResource[]; - providerGroups: ProviderGroupsResponse["data"]; -}): FilterOption[] => { - // Provider type and account selection are handled by ProviderTypeSelector - // and AccountsSelector. These filters go in the expandable "More Filters" section. - const filters: FilterOption[] = []; - - if (isCloud && organizations.length > 0) { - filters.push({ - key: PROVIDERS_PAGE_FILTER.ORGANIZATION, - labelCheckboxGroup: "Organizations", - values: organizations.map((organization) => organization.id), +const createProvidersFilters = (): FilterOption[] => { + return [ + { + key: PROVIDERS_PAGE_FILTER.STATUS, + labelCheckboxGroup: "Status", + values: ["true", "false"], + valueLabelMapping: PROVIDERS_STATUS_MAPPING, index: 0, - valueLabelMapping: organizations.map((organization) => ({ - [organization.id]: { - name: organization.attributes.name, - uid: organization.attributes.external_id, - }, - })), - }); - } - - filters.push({ - key: PROVIDERS_PAGE_FILTER.ACCOUNT_GROUP, - labelCheckboxGroup: "Account Groups", - values: providerGroups.map((providerGroup) => providerGroup.id), - index: 1, - valueLabelMapping: providerGroups.map((providerGroup) => ({ - [providerGroup.id]: { - name: providerGroup.attributes.name, - uid: providerGroup.id, - }, - })), - }); - - filters.push({ - key: PROVIDERS_PAGE_FILTER.STATUS, - labelCheckboxGroup: "Status", - values: ["true", "false"], - valueLabelMapping: PROVIDERS_STATUS_MAPPING, - index: 2, - }); - - return filters; + }, + ]; }; const createProviderGroupLookup = ( @@ -193,55 +145,6 @@ const enrichProviders = ( })); }; -const getFilterValues = ( - searchParams: SearchParamsProps, - key: string, -): string[] => { - const rawValue = searchParams[`filter[${key}]`]; - - if (!rawValue) { - return []; - } - - const filterValue = Array.isArray(rawValue) ? rawValue.join(",") : rawValue; - return filterValue - .split(",") - .map((value) => value.trim()) - .filter(Boolean); -}; - -/** - * Filters providers client-side by organization and provider group. - * - * The API's ProviderFilter does not support `organization_id__in` or - * `provider_group_id__in`, so these filters must be applied after fetching. - * TODO: Move to server-side when API adds support for these filter params. - */ -const applyLocalFilters = ({ - organizationIds, - providerGroupIds, - providers, -}: ProvidersTableLocalFilters & { providers: ProvidersProviderRow[] }) => { - return providers.filter((provider) => { - const organizationId = - provider.relationships.organization?.data?.id ?? null; - const matchesOrganization = - organizationIds.length === 0 || - organizationIds.includes(organizationId ?? ""); - const providerGroupIdsForProvider = - provider.relationships.provider_groups.data.map( - (providerGroup) => providerGroup.id, - ); - const matchesGroup = - providerGroupIds.length === 0 || - providerGroupIds.some((providerGroupId) => - providerGroupIdsForProvider.includes(providerGroupId), - ); - - return matchesOrganization && matchesGroup; - }); -}; - const createOrganizationRow = ({ groupKind, id, @@ -527,17 +430,6 @@ export async function loadProvidersAccountsViewData({ const { encodedSort } = extractSortAndKey(searchParams); const { filters, query } = extractFiltersAndQuery(searchParams); - const localFilters = { - organizationIds: getFilterValues( - searchParams, - PROVIDERS_PAGE_FILTER.ORGANIZATION, - ), - providerGroupIds: getFilterValues( - searchParams, - PROVIDERS_PAGE_FILTER.ACCOUNT_GROUP, - ), - }; - const providerFilters = { ...filters }; // Map provider_type__in (used by ProviderTypeSelector) to provider__in (API param) @@ -548,12 +440,7 @@ export async function loadProvidersAccountsViewData({ providerTypeFilter; } - // Remove client-side-only filters before sending to the API. - // TODO: Move organization and account group filtering to server-side - // when API adds support for `organization_id__in` and `provider_group_id__in`. delete providerFilters[`filter[${PROVIDERS_PAGE_FILTER.PROVIDER_TYPE}]`]; - delete providerFilters[`filter[${PROVIDERS_PAGE_FILTER.ORGANIZATION}]`]; - delete providerFilters[`filter[${PROVIDERS_PAGE_FILTER.ACCOUNT_GROUP}]`]; const emptyOrganizationsResponse: OrganizationListResponse = { data: [], @@ -565,7 +452,6 @@ export async function loadProvidersAccountsViewData({ const [ providersResponse, allProvidersResponse, - providerGroupsResponse, scansResponse, organizationsResponse, organizationUnitsResponse, @@ -582,7 +468,6 @@ export async function loadProvidersAccountsViewData({ // Unfiltered fetch for ProviderTypeSelector — only needs distinct types; // TODO: Replace with a dedicated lightweight endpoint when available. resolveActionResult(getProviders({ pageSize: 500 })), - resolveActionResult(getProviderGroups({ page: 1, pageSize: 500 })), // Fetch active scheduled scans to determine daily schedule per provider resolveActionResult( getScans({ @@ -605,24 +490,19 @@ export async function loadProvidersAccountsViewData({ scansResponse?.data ?? [], ); - const providers = applyLocalFilters({ - ...localFilters, - providers: enrichProviders(providersResponse, scheduledProviderIds), - }); + const orgs = organizationsResponse?.data ?? []; + const ous = organizationUnitsResponse?.data ?? []; + const providers = enrichProviders(providersResponse, scheduledProviderIds); const rows = buildProvidersTableRows({ isCloud, - organizations: organizationsResponse?.data ?? [], - organizationUnits: organizationUnitsResponse?.data ?? [], + organizations: orgs, + organizationUnits: ous, providers, }); return { - filters: createProvidersFilters({ - isCloud, - organizations: organizationsResponse?.data ?? [], - providerGroups: providerGroupsResponse?.data ?? [], - }), + filters: createProvidersFilters(), metadata: providersResponse?.meta, providers: allProvidersResponse?.data ?? [], rows, diff --git a/ui/components/providers/forms/delete-organization-form.tsx b/ui/components/providers/forms/delete-organization-form.tsx new file mode 100644 index 0000000000..fd9434280f --- /dev/null +++ b/ui/components/providers/forms/delete-organization-form.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { Dispatch, SetStateAction, useState } from "react"; + +import { + deleteOrganization, + deleteOrganizationalUnit, +} from "@/actions/organizations/organizations"; +import { DeleteIcon } from "@/components/icons"; +import { Button } from "@/components/shadcn"; +import { useToast } from "@/components/ui"; +import { + PROVIDERS_GROUP_KIND, + ProvidersGroupKind, +} from "@/types/providers-table"; + +interface DeleteOrganizationFormProps { + id: string; + name: string; + variant: ProvidersGroupKind; + setIsOpen: Dispatch>; +} + +export function DeleteOrganizationForm({ + id, + name, + variant, + setIsOpen, +}: DeleteOrganizationFormProps) { + const [isLoading, setIsLoading] = useState(false); + const { toast } = useToast(); + + const isOrg = variant === PROVIDERS_GROUP_KIND.ORGANIZATION; + const entityLabel = isOrg ? "organization" : "organizational unit"; + + const handleDelete = async () => { + setIsLoading(true); + + const result = isOrg + ? await deleteOrganization(id) + : await deleteOrganizationalUnit(id); + + setIsLoading(false); + + if (result?.errors && result.errors.length > 0) { + const error = result.errors[0]; + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: `${error.detail}`, + }); + } else if (result?.error) { + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: result.error, + }); + } else { + toast({ + title: "Success!", + description: `The ${entityLabel} "${name}" was removed successfully.`, + }); + setIsOpen(false); + } + }; + + return ( +
+ + + +
+ ); +} diff --git a/ui/components/providers/forms/edit-form.tsx b/ui/components/providers/forms/edit-form.tsx deleted file mode 100644 index 6ecab34368..0000000000 --- a/ui/components/providers/forms/edit-form.tsx +++ /dev/null @@ -1,92 +0,0 @@ -"use client"; - -import { zodResolver } from "@hookform/resolvers/zod"; -import { Dispatch, SetStateAction } from "react"; -import { useForm } from "react-hook-form"; -import * as z from "zod"; - -import { updateProvider } from "@/actions/providers"; -import { useToast } from "@/components/ui"; -import { CustomInput } from "@/components/ui/custom"; -import { Form, FormButtons } from "@/components/ui/form"; -import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields"; -import { editProviderFormSchema } from "@/types"; - -export const EditForm = ({ - providerId, - providerAlias, - setIsOpen, -}: { - providerId: string; - providerAlias?: string; - setIsOpen: Dispatch>; -}) => { - const formSchema = editProviderFormSchema(providerAlias ?? ""); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - [ProviderCredentialFields.PROVIDER_ID]: providerId, - [ProviderCredentialFields.PROVIDER_ALIAS]: providerAlias, - }, - }); - - const { toast } = useToast(); - - const isLoading = form.formState.isSubmitting; - - const onSubmitClient = async (values: z.infer) => { - const formData = new FormData(); - - Object.entries(values).forEach( - ([key, value]) => value !== undefined && formData.append(key, value), - ); - - const data = await updateProvider(formData); - - if (data?.errors && data.errors.length > 0) { - const error = data.errors[0]; - const errorMessage = `${error.detail}`; - // show error - toast({ - variant: "destructive", - title: "Oops! Something went wrong", - description: errorMessage, - }); - } else { - toast({ - title: "Success!", - description: "The provider was updated successfully.", - }); - setIsOpen(false); // Close the modal on success - } - }; - - return ( - - -
- Current alias: {providerAlias} -
-
- -
- - - - - - ); -}; diff --git a/ui/components/providers/forms/edit-name-form.tsx b/ui/components/providers/forms/edit-name-form.tsx new file mode 100644 index 0000000000..5a2f1d2943 --- /dev/null +++ b/ui/components/providers/forms/edit-name-form.tsx @@ -0,0 +1,123 @@ +"use client"; + +import type { Dispatch, FormEvent, SetStateAction } from "react"; +import { useState } from "react"; + +import { SaveIcon } from "@/components/icons"; +import { Button } from "@/components/shadcn"; +import { Input } from "@/components/shadcn/input/input"; +import { useToast } from "@/components/ui"; + +interface EditNameFormProps { + currentValue: string; + label: string; + successMessage: string; + placeholder?: string; + helperText?: string; + validate?: (value: string) => string | null; + setIsOpen: Dispatch>; + onSave: (value: string) => Promise; +} + +export function EditNameForm({ + currentValue, + label, + successMessage, + placeholder, + helperText, + validate, + setIsOpen, + onSave, +}: EditNameFormProps) { + const [value, setValue] = useState(currentValue); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const { toast } = useToast(); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + + const trimmed = value.trim(); + if (validate) { + const validationError = validate(trimmed); + if (validationError) { + setError(validationError); + return; + } + } + + setError(null); + setIsLoading(true); + + const result = (await onSave(trimmed)) as Record; + + setIsLoading(false); + + const errors = result?.errors as Array<{ detail: string }> | undefined; + + if (errors && errors.length > 0) { + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: `${errors[0].detail}`, + }); + } else if (result?.error) { + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: String(result.error), + }); + } else { + toast({ title: "Success!", description: successMessage }); + setIsOpen(false); + } + }; + + return ( +
+
+ Current {label.toLowerCase()}:{" "} + {currentValue || "—"} +
+
+ + { + setValue(e.target.value); + if (error) setError(null); + }} + placeholder={placeholder ?? currentValue} + disabled={isLoading} + aria-invalid={!!error} + /> + {error &&

{error}

} + {helperText && !error && ( +

{helperText}

+ )} +
+ +
+ + +
+
+ ); +} diff --git a/ui/components/providers/forms/index.ts b/ui/components/providers/forms/index.ts index a081952ade..8a119f2aa9 100644 --- a/ui/components/providers/forms/index.ts +++ b/ui/components/providers/forms/index.ts @@ -1,2 +1,2 @@ export * from "./delete-form"; -export * from "./edit-form"; +export * from "./edit-name-form"; diff --git a/ui/components/providers/organizations/org-setup-form.tsx b/ui/components/providers/organizations/org-setup-form.tsx index 3990ffb435..3c480e3c48 100644 --- a/ui/components/providers/organizations/org-setup-form.tsx +++ b/ui/components/providers/organizations/org-setup-form.tsx @@ -8,22 +8,29 @@ import { FormEvent, useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; +import { updateOrganizationName } from "@/actions/organizations/organizations"; import { AWSProviderBadge } from "@/components/icons/providers-badge"; import { WIZARD_FOOTER_ACTION_TYPE, WizardFooterConfig, } from "@/components/providers/wizard/steps/footer-controls"; +import { + ORG_WIZARD_INTENT, + OrgWizardIntent, +} from "@/components/providers/wizard/types"; import { WizardInputField } from "@/components/providers/workflow/forms/fields"; import { Alert, AlertDescription } from "@/components/shadcn/alert"; import { Button } from "@/components/shadcn/button/button"; import { Checkbox } from "@/components/shadcn/checkbox/checkbox"; import { TreeSpinner } from "@/components/shadcn/tree-view/tree-spinner"; +import { useToast } from "@/components/ui"; import { Form } from "@/components/ui/form"; import { getAWSCredentialsTemplateLinks, PROWLER_CF_TEMPLATE_URL, STACKSET_CONSOLE_URL, } from "@/lib"; +import { useOrgSetupStore } from "@/store/organizations/store"; import { ORG_SETUP_PHASE, OrgSetupPhase } from "@/types/organizations"; import { useOrgSetupSubmission } from "./hooks/use-org-setup-submission"; @@ -53,23 +60,36 @@ const orgSetupSchema = z.object({ type OrgSetupFormData = z.infer; +interface OrgSetupFormInitialValues { + organizationName: string; + awsOrgId: string; +} + interface OrgSetupFormProps { onBack: () => void; + onClose?: () => void; onNext: () => void; onFooterChange: (config: WizardFooterConfig) => void; onPhaseChange: (phase: OrgSetupPhase) => void; initialPhase?: OrgSetupPhase; + initialValues?: OrgSetupFormInitialValues; + intent?: OrgWizardIntent; } export function OrgSetupForm({ onBack, + onClose, onNext, onFooterChange, onPhaseChange, initialPhase = ORG_SETUP_PHASE.DETAILS, + initialValues, + intent = ORG_WIZARD_INTENT.FULL, }: OrgSetupFormProps) { const { data: session } = useSession(); const stackSetExternalId = session?.tenantId ?? ""; + const { organizationId } = useOrgSetupStore(); + const { toast } = useToast(); const { copied: isExternalIdCopied, copy: copyExternalId } = useClipboard({ timeout: 1500, }); @@ -77,15 +97,18 @@ export function OrgSetupForm({ timeout: 1500, }); const [setupPhase, setSetupPhase] = useState(initialPhase); + const [isSaving, setIsSaving] = useState(false); const formId = "org-wizard-setup-form"; + const isReadOnlyOrgId = Boolean(initialValues?.awsOrgId); + const form = useForm({ resolver: zodResolver(orgSetupSchema), mode: "onChange", reValidateMode: "onChange", defaultValues: { - organizationName: "", - awsOrgId: "", + organizationName: initialValues?.organizationName ?? "", + awsOrgId: initialValues?.awsOrgId ?? "", roleArn: "", stackSetDeployed: false, }, @@ -120,21 +143,23 @@ export function OrgSetupForm({ useEffect(() => { if (setupPhase === ORG_SETUP_PHASE.DETAILS) { + const isEditName = intent === ORG_WIZARD_INTENT.EDIT_NAME; onFooterChange({ showBack: true, backLabel: "Back", onBack, showAction: true, - actionLabel: "Next", - actionDisabled: !isOrgIdValid, + actionLabel: isEditName ? "Save" : "Next", + actionDisabled: isEditName ? isSaving : !isOrgIdValid, actionType: WIZARD_FOOTER_ACTION_TYPE.SUBMIT, actionFormId: formId, }); return; } + const isEditCredentials = intent === ORG_WIZARD_INTENT.EDIT_CREDENTIALS; onFooterChange({ - showBack: true, + showBack: !isEditCredentials, backLabel: "Back", backDisabled: isSubmitting, onBack: () => setSetupPhase(ORG_SETUP_PHASE.DETAILS), @@ -146,7 +171,9 @@ export function OrgSetupForm({ }); }, [ formId, + intent, isOrgIdValid, + isSaving, isSubmitting, isValid, onBack, @@ -170,9 +197,42 @@ export function OrgSetupForm({ setSetupPhase(ORG_SETUP_PHASE.ACCESS); }; + const handleSaveNameOnly = async () => { + if (!organizationId) return; + setIsSaving(true); + const name = form.getValues("organizationName")?.trim() || ""; + + const result = await updateOrganizationName(organizationId, name); + + setIsSaving(false); + + if (result?.error || result?.errors) { + const errorMsg = + result.errors?.[0]?.detail ?? result.error ?? "Failed to update name"; + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: errorMsg, + }); + return; + } + + toast({ + title: "Success!", + description: "Organization name updated successfully.", + }); + onClose?.(); + }; + const handleFormSubmit = (event: FormEvent) => { if (setupPhase === ORG_SETUP_PHASE.DETAILS) { event.preventDefault(); + + if (intent === ORG_WIZARD_INTENT.EDIT_NAME) { + void handleSaveNameOnly(); + return; + } + handleContinueToAccess(); return; } @@ -251,6 +311,8 @@ export function OrgSetupForm({ autoCapitalize="none" autoCorrect="off" spellCheck={false} + isReadOnly={isReadOnlyOrgId} + isDisabled={isReadOnlyOrgId} /> vi.fn()); +vi.mock("@/actions/organizations/organizations", () => ({ + updateOrganizationName: vi.fn(), +})); + vi.mock("@/actions/providers/providers", () => ({ checkConnectionProvider: checkConnectionProviderMock, })); @@ -15,14 +22,18 @@ vi.mock("@/components/providers/wizard", () => ({ ProviderWizardModal: () => null, })); -vi.mock("../forms", () => ({ - EditForm: () => null, -})); - vi.mock("../forms/delete-form", () => ({ DeleteForm: () => null, })); +vi.mock("../forms/delete-organization-form", () => ({ + DeleteOrganizationForm: () => null, +})); + +vi.mock("../forms/edit-name-form", () => ({ + EditNameForm: () => null, +})); + vi.mock("@/components/ui", () => ({ useToast: () => ({ toast: vi.fn() }), })); @@ -76,6 +87,63 @@ const createRow = () => }, }) as Row; +const createOrgRow = () => + ({ + original: { + id: "org-1", + rowType: PROVIDERS_ROW_TYPE.ORGANIZATION, + groupKind: PROVIDERS_GROUP_KIND.ORGANIZATION, + name: "My AWS Organization", + externalId: "o-abc123def4", + parentExternalId: null, + organizationId: "org-1", + providerCount: 3, + subRows: [ + { + id: "provider-child-1", + rowType: PROVIDERS_ROW_TYPE.PROVIDER, + type: "providers", + attributes: { provider: "aws", uid: "111", alias: null }, + relationships: { + secret: { data: { id: "secret-1", type: "secrets" } }, + }, + }, + { + id: "provider-child-2", + rowType: PROVIDERS_ROW_TYPE.PROVIDER, + type: "providers", + attributes: { provider: "aws", uid: "222", alias: null }, + relationships: { secret: { data: null } }, + }, + ], + }, + }) as Row; + +const createOuRow = () => + ({ + original: { + id: "ou-1", + rowType: PROVIDERS_ROW_TYPE.ORGANIZATION, + groupKind: PROVIDERS_GROUP_KIND.ORGANIZATION_UNIT, + name: "Production OU", + externalId: "ou-abc123", + parentExternalId: "o-abc123def4", + organizationId: "org-1", + providerCount: 2, + subRows: [ + { + id: "provider-ou-child-1", + rowType: PROVIDERS_ROW_TYPE.PROVIDER, + type: "providers", + attributes: { provider: "aws", uid: "333", alias: null }, + relationships: { + secret: { data: { id: "secret-2", type: "secrets" } }, + }, + }, + ], + }, + }) as Row; + describe("DataTableRowActions", () => { it("renders the exact phase 1 menu actions for provider rows", async () => { // Given @@ -100,4 +168,122 @@ describe("DataTableRowActions", () => { expect(screen.getByText("Delete Provider")).toBeInTheDocument(); expect(screen.queryByText("Add Credentials")).not.toBeInTheDocument(); }); + + it("renders all 4 organization actions for org rows", async () => { + const user = userEvent.setup(); + render( + , + ); + + await user.click(screen.getByRole("button")); + + expect(screen.getByText("Edit Organization Name")).toBeInTheDocument(); + expect(screen.getByText("Update Credentials")).toBeInTheDocument(); + // 1 of 2 child providers has a secret + expect(screen.getByText("Test Connections (1)")).toBeInTheDocument(); + expect(screen.getByText("Delete Organization")).toBeInTheDocument(); + }); + + it("renders Delete Organization with destructive styling for org rows", async () => { + const user = userEvent.setup(); + render( + , + ); + + await user.click(screen.getByRole("button")); + + const deleteItem = screen.getByText("Delete Organization"); + // Destructive items are rendered with error text color + const menuItem = deleteItem.closest("[role='menuitem']"); + expect(menuItem).toBeInTheDocument(); + expect(menuItem).toHaveClass("text-text-error-primary"); + }); + + it("renders only Test Connections and Delete for OU rows", async () => { + const user = userEvent.setup(); + render( + , + ); + + await user.click(screen.getByRole("button")); + + expect(screen.getByText("Test Connections (1)")).toBeInTheDocument(); + expect(screen.getByText("Delete Organization Unit")).toBeInTheDocument(); + }); + + it("shows selected provider count in Test Connections when org row has active selection", async () => { + const user = userEvent.setup(); + render( + , + ); + + await user.click(screen.getByRole("button")); + + // Should show count of selected testable providers (2), not all org children (1) + expect(screen.getByText("Test Connections (2)")).toBeInTheDocument(); + expect(screen.queryByText("Test Connections (1)")).not.toBeInTheDocument(); + }); + + it("shows selected provider count in Test Connections when OU row has active selection", async () => { + const user = userEvent.setup(); + render( + , + ); + + await user.click(screen.getByRole("button")); + + // Should show count of selected testable providers (2), not all OU children (1) + expect(screen.getByText("Test Connections (2)")).toBeInTheDocument(); + expect(screen.queryByText("Test Connections (1)")).not.toBeInTheDocument(); + }); + + it("does NOT render Edit Organization Name or Update Credentials for OU rows", async () => { + const user = userEvent.setup(); + render( + , + ); + + await user.click(screen.getByRole("button")); + + expect( + screen.queryByText("Edit Organization Name"), + ).not.toBeInTheDocument(); + expect(screen.queryByText("Update Credentials")).not.toBeInTheDocument(); + }); }); diff --git a/ui/components/providers/table/data-table-row-actions.tsx b/ui/components/providers/table/data-table-row-actions.tsx index 18aea03937..bf9f1c5dd5 100644 --- a/ui/components/providers/table/data-table-row-actions.tsx +++ b/ui/components/providers/table/data-table-row-actions.tsx @@ -4,8 +4,14 @@ import { Row } from "@tanstack/react-table"; import { KeyRound, Pencil, Rocket, Trash2 } from "lucide-react"; import { useState } from "react"; +import { updateOrganizationName } from "@/actions/organizations/organizations"; +import { updateProvider } from "@/actions/providers"; import { VerticalDotsIcon } from "@/components/icons"; import { ProviderWizardModal } from "@/components/providers/wizard"; +import { + ORG_WIZARD_INTENT, + OrgWizardInitialData, +} from "@/components/providers/wizard/types"; import { Button } from "@/components/shadcn"; import { ActionDropdown, @@ -16,14 +22,19 @@ import { Modal } from "@/components/shadcn/modal"; import { useToast } from "@/components/ui"; import { runWithConcurrencyLimit } from "@/lib/concurrency"; import { testProviderConnection } from "@/lib/provider-helpers"; +import { ORG_SETUP_PHASE, ORG_WIZARD_STEP } from "@/types/organizations"; import { PROVIDER_WIZARD_MODE } from "@/types/provider-wizard"; import { isProvidersOrganizationRow, + PROVIDERS_GROUP_KIND, + PROVIDERS_ROW_TYPE, + ProvidersOrganizationRow, ProvidersTableRow, } from "@/types/providers-table"; -import { EditForm } from "../forms"; import { DeleteForm } from "../forms/delete-form"; +import { DeleteOrganizationForm } from "../forms/delete-organization-form"; +import { EditNameForm } from "../forms/edit-name-form"; interface DataTableRowActionsProps { row: Row; @@ -37,6 +48,165 @@ interface DataTableRowActionsProps { onClearSelection: () => void; } +function collectTestableChildProviderIds(rows: ProvidersTableRow[]): string[] { + const ids: string[] = []; + for (const row of rows) { + if (row.rowType === PROVIDERS_ROW_TYPE.PROVIDER) { + if (row.relationships.secret.data) { + ids.push(row.id); + } + } else if (row.subRows) { + ids.push(...collectTestableChildProviderIds(row.subRows)); + } + } + return ids; +} + +interface OrgGroupDropdownActionsProps { + rowData: ProvidersOrganizationRow; + loading: boolean; + hasSelection: boolean; + testableProviderIds: string[]; + childTestableIds: string[]; + onClearSelection: () => void; + onBulkTest: (ids: string[]) => Promise; + onTestChildConnections: () => Promise; +} + +function OrgGroupDropdownActions({ + rowData, + loading, + hasSelection, + testableProviderIds, + childTestableIds, + onClearSelection, + onBulkTest, + onTestChildConnections, +}: OrgGroupDropdownActionsProps) { + const [isDeleteOrgOpen, setIsDeleteOrgOpen] = useState(false); + const [isEditNameOpen, setIsEditNameOpen] = useState(false); + const [isOrgWizardOpen, setIsOrgWizardOpen] = useState(false); + const [orgWizardData, setOrgWizardData] = + useState(null); + + const isOrgKind = rowData.groupKind === PROVIDERS_GROUP_KIND.ORGANIZATION; + const testIds = hasSelection ? testableProviderIds : childTestableIds; + const testCount = testIds.length; + const entityLabel = isOrgKind ? "organization" : "organizational unit"; + + const openOrgWizardAt = ( + targetStep: OrgWizardInitialData["targetStep"], + targetPhase: OrgWizardInitialData["targetPhase"], + intent?: OrgWizardInitialData["intent"], + ) => { + setOrgWizardData({ + organizationId: rowData.id, + organizationName: rowData.name, + externalId: rowData.externalId ?? "", + targetStep, + targetPhase, + intent, + }); + setIsOrgWizardOpen(true); + }; + + return ( + <> + {isOrgKind && ( + <> + + updateOrganizationName(rowData.id, name)} + /> + + + + )} + + + + +
+ + + + } + > + {isOrgKind && ( + <> + } + label="Edit Organization Name" + onSelect={() => setIsEditNameOpen(true)} + /> + } + label="Update Credentials" + onSelect={() => + openOrgWizardAt( + ORG_WIZARD_STEP.SETUP, + ORG_SETUP_PHASE.ACCESS, + ORG_WIZARD_INTENT.EDIT_CREDENTIALS, + ) + } + /> + + )} + } + label={loading ? "Testing..." : `Test Connections (${testCount})`} + onSelect={(e) => { + e.preventDefault(); + if (hasSelection) { + onBulkTest(testableProviderIds); + onClearSelection(); + } else { + onTestChildConnections(); + } + }} + disabled={testCount === 0 || loading} + /> + + } + label={ + isOrgKind ? "Delete Organization" : "Delete Organization Unit" + } + destructive + onSelect={() => setIsDeleteOrgOpen(true)} + /> + + +
+ + ); +} + export function DataTableRowActions({ row, hasSelection, @@ -60,41 +230,46 @@ export function DataTableRowActions({ const providerSecretId = provider?.relationships.secret.data?.id ?? null; const hasSecret = Boolean(provider?.relationships.secret.data); + const orgGroupKind = isOrganizationRow ? rowData.groupKind : null; + const childTestableIds = isOrganizationRow + ? collectTestableChildProviderIds(rowData.subRows) + : []; + + const handleBulkTest = async (ids: string[]) => { + if (ids.length === 0) return; + setLoading(true); + + const results = await runWithConcurrencyLimit(ids, 10, async (id) => { + try { + return await testProviderConnection(id); + } catch { + return { connected: false, error: "Unexpected error" }; + } + }); + + const succeeded = results.filter((r) => r.connected).length; + const failed = results.length - succeeded; + + if (failed === 0) { + toast({ + title: "Connection test completed", + description: `${succeeded} ${succeeded === 1 ? "provider" : "providers"} tested successfully.`, + }); + } else { + toast({ + variant: "destructive", + title: "Connection test completed", + description: `${succeeded} succeeded, ${failed} failed out of ${results.length} providers.`, + }); + } + + setLoading(false); + }; + const handleTestConnection = async () => { if (hasSelection && isRowSelected) { // Bulk: test all selected providers - if (testableProviderIds.length === 0) return; - setLoading(true); - - const results = await runWithConcurrencyLimit( - testableProviderIds, - 10, - async (id) => { - try { - return await testProviderConnection(id); - } catch { - return { connected: false, error: "Unexpected error" }; - } - }, - ); - - const succeeded = results.filter((r) => r.connected).length; - const failed = results.length - succeeded; - - if (failed === 0) { - toast({ - title: "Connection test completed", - description: `${succeeded} ${succeeded === 1 ? "provider" : "providers"} tested successfully.`, - }); - } else { - toast({ - variant: "destructive", - title: "Connection test completed", - description: `${succeeded} succeeded, ${failed} failed out of ${results.length} providers.`, - }); - } - - setLoading(false); + await handleBulkTest(testableProviderIds); onClearSelection(); } else { // Single: test only this provider @@ -118,6 +293,10 @@ export function DataTableRowActions({ } }; + const handleTestChildConnections = async () => { + await handleBulkTest(childTestableIds); + }; + // When this row is part of the selection, only show "Test Connection" if (hasSelection && isRowSelected) { const bulkCount = @@ -139,16 +318,30 @@ export function DataTableRowActions({ e.preventDefault(); handleTestConnection(); }} - disabled={ - isOrganizationRow || testableProviderIds.length === 0 || loading - } + disabled={testableProviderIds.length === 0 || loading} />
); } - // Normal mode: all actions + // Organization / Organization Unit row actions + if (isProvidersOrganizationRow(rowData) && orgGroupKind) { + return ( + + ); + } + + // Provider row actions (unchanged) return ( <> {provider && ( - { + if (alias !== "" && alias.length < 3) { + return "The alias must be empty or have at least 3 characters."; + } + if (alias === (providerAlias ?? "")) { + return "The new alias must be different from the current one."; + } + return null; + }} + onSave={async (alias) => { + const formData = new FormData(); + formData.append("providerId", providerId); + formData.append("providerAlias", alias); + return updateProvider(formData); + }} /> )} @@ -201,13 +410,11 @@ export function DataTableRowActions({ icon={} label="Edit Provider Alias" onSelect={() => setIsEditOpen(true)} - disabled={isOrganizationRow} /> } label="Update Credentials" onSelect={() => setIsWizardOpen(true)} - disabled={isOrganizationRow} /> } @@ -216,7 +423,7 @@ export function DataTableRowActions({ e.preventDefault(); handleTestConnection(); }} - disabled={isOrganizationRow || !hasSecret || loading} + disabled={!hasSecret || loading} /> setIsDeleteOpen(true)} - disabled={isOrganizationRow} /> diff --git a/ui/components/providers/wizard/hooks/use-provider-wizard-controller.ts b/ui/components/providers/wizard/hooks/use-provider-wizard-controller.ts index a5b6d6e188..111427131e 100644 --- a/ui/components/providers/wizard/hooks/use-provider-wizard-controller.ts +++ b/ui/components/providers/wizard/hooks/use-provider-wizard-controller.ts @@ -23,7 +23,7 @@ import { WIZARD_FOOTER_ACTION_TYPE, WizardFooterConfig, } from "../steps/footer-controls"; -import type { ProviderWizardInitialData } from "../types"; +import type { OrgWizardInitialData, ProviderWizardInitialData } from "../types"; const WIZARD_VARIANT = { PROVIDER: "provider", @@ -48,12 +48,14 @@ interface UseProviderWizardControllerProps { open: boolean; onOpenChange: (open: boolean) => void; initialData?: ProviderWizardInitialData; + orgInitialData?: OrgWizardInitialData; } export function useProviderWizardController({ open, onOpenChange, initialData, + orgInitialData, }: UseProviderWizardControllerProps) { const initialProviderId = initialData?.providerId ?? null; const initialProviderType = initialData?.providerType ?? null; @@ -90,7 +92,7 @@ export function useProviderWizardController({ mode, providerType, } = useProviderWizardStore(); - const { reset: resetOrgWizard } = useOrgSetupStore(); + const { reset: resetOrgWizard, setOrganization } = useOrgSetupStore(); useEffect(() => { if (!open) { @@ -103,6 +105,21 @@ export function useProviderWizardController({ } hasHydratedForCurrentOpenRef.current = true; + if (orgInitialData) { + setWizardVariant(WIZARD_VARIANT.ORGANIZATIONS); + resetOrgWizard(); + setOrganization( + orgInitialData.organizationId, + orgInitialData.organizationName, + orgInitialData.externalId, + ); + setOrgCurrentStep(orgInitialData.targetStep); + setOrgSetupPhase(orgInitialData.targetPhase); + setFooterConfig(EMPTY_FOOTER_CONFIG); + setProviderTypeHint(null); + return; + } + if (initialProviderId && initialProviderType && initialProviderUid) { setWizardVariant(WIZARD_VARIANT.PROVIDER); setProvider({ @@ -144,14 +161,18 @@ export function useProviderWizardController({ initialSecretId, initialVia, open, + orgInitialData, resetOrgWizard, resetProviderWizard, setMode, + setOrganization, setProvider, setSecretId, setVia, ]); + const isOrgDirectEntry = Boolean(orgInitialData); + const handleClose = () => { resetProviderWizard(); resetOrgWizard(); @@ -212,6 +233,7 @@ export function useProviderWizardController({ handleClose, handleDialogOpenChange, handleTestSuccess, + isOrgDirectEntry, isProviderFlow, modalTitle, openOrganizationsFlow, diff --git a/ui/components/providers/wizard/provider-wizard-modal.tsx b/ui/components/providers/wizard/provider-wizard-modal.tsx index 76b2a6ea77..9157ca74ce 100644 --- a/ui/components/providers/wizard/provider-wizard-modal.tsx +++ b/ui/components/providers/wizard/provider-wizard-modal.tsx @@ -22,19 +22,21 @@ import { CredentialsStep } from "./steps/credentials-step"; import { WIZARD_FOOTER_ACTION_TYPE } from "./steps/footer-controls"; import { LaunchStep } from "./steps/launch-step"; import { TestConnectionStep } from "./steps/test-connection-step"; -import type { ProviderWizardInitialData } from "./types"; +import type { OrgWizardInitialData, ProviderWizardInitialData } from "./types"; import { WizardStepper } from "./wizard-stepper"; interface ProviderWizardModalProps { open: boolean; onOpenChange: (open: boolean) => void; initialData?: ProviderWizardInitialData; + orgInitialData?: OrgWizardInitialData; } export function ProviderWizardModal({ open, onOpenChange, initialData, + orgInitialData, }: ProviderWizardModalProps) { const { backToProviderFlow, @@ -43,6 +45,7 @@ export function ProviderWizardModal({ handleClose, handleDialogOpenChange, handleTestSuccess, + isOrgDirectEntry, isProviderFlow, modalTitle, openOrganizationsFlow, @@ -59,6 +62,7 @@ export function ProviderWizardModal({ open, onOpenChange, initialData, + orgInitialData, }); const scrollHintRefreshToken = `${wizardVariant}-${currentStep}-${orgCurrentStep}-${orgSetupPhase}`; const { containerRef, sentinelRef, showScrollHint } = useScrollHint({ @@ -149,13 +153,23 @@ export function ProviderWizardModal({ {!isProviderFlow && orgCurrentStep === ORG_WIZARD_STEP.SETUP && ( { setOrgCurrentStep(ORG_WIZARD_STEP.VALIDATE); }} onFooterChange={setFooterConfig} onPhaseChange={setOrgSetupPhase} initialPhase={orgSetupPhase} + initialValues={ + orgInitialData + ? { + organizationName: orgInitialData.organizationName, + awsOrgId: orgInitialData.externalId, + } + : undefined + } + intent={orgInitialData?.intent} /> )} diff --git a/ui/components/providers/wizard/types.ts b/ui/components/providers/wizard/types.ts index ab48b7fc70..92df95dbbb 100644 --- a/ui/components/providers/wizard/types.ts +++ b/ui/components/providers/wizard/types.ts @@ -1,3 +1,4 @@ +import { OrgSetupPhase, OrgWizardStep } from "@/types/organizations"; import { ProviderWizardMode } from "@/types/provider-wizard"; import { ProviderType } from "@/types/providers"; @@ -10,3 +11,21 @@ export interface ProviderWizardInitialData { via?: string | null; mode?: ProviderWizardMode; } + +export const ORG_WIZARD_INTENT = { + FULL: "full", + EDIT_NAME: "edit-name", + EDIT_CREDENTIALS: "edit-credentials", +} as const; + +export type OrgWizardIntent = + (typeof ORG_WIZARD_INTENT)[keyof typeof ORG_WIZARD_INTENT]; + +export interface OrgWizardInitialData { + organizationId: string; + organizationName: string; + externalId: string; + targetStep: OrgWizardStep; + targetPhase: OrgSetupPhase; + intent?: OrgWizardIntent; +} diff --git a/ui/types/providers-table.ts b/ui/types/providers-table.ts index cad6f37ec7..553355b75b 100644 --- a/ui/types/providers-table.ts +++ b/ui/types/providers-table.ts @@ -25,8 +25,6 @@ export type ProvidersGroupKind = export const PROVIDERS_PAGE_FILTER = { PROVIDER: "provider__in", PROVIDER_TYPE: "provider_type__in", - ORGANIZATION: "organization_id__in", - ACCOUNT_GROUP: "provider_group_id__in", STATUS: "connected", } as const; From c9284f80034554f95a4e0e45529a5aec781d3862 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Mon, 16 Mar 2026 09:36:08 +0000 Subject: [PATCH 003/366] chore(models): add pydantic validators for `CheckMetadata` (#8583) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rubén De la Torre Vico Co-authored-by: Daniel Barranquero Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com> --- prowler/CHANGELOG.md | 1 + prowler/lib/check/models.py | 206 +- ...ontrail_multi_region_enabled.metadata.json | 2 +- ...ernetes_cloudmonitor_enabled.metadata.json | 4 +- ...ernetes_cluster_check_recent.metadata.json | 4 +- ...ernetes_cluster_check_weekly.metadata.json | 4 +- ...ubernetes_dashboard_disabled.metadata.json | 4 +- ...etes_eni_multiple_ip_enabled.metadata.json | 4 +- ...bernetes_log_service_enabled.metadata.json | 4 +- ...netes_network_policy_enabled.metadata.json | 4 +- ...etes_private_cluster_enabled.metadata.json | 4 +- .../cs_kubernetes_rbac_enabled.metadata.json | 4 +- .../ecs_attached_disk_encrypted.metadata.json | 2 +- ...ndpoint_protection_installed.metadata.json | 2 +- .../ram_no_root_access_key.metadata.json | 4 +- ...word_policy_max_password_age.metadata.json | 4 +- ...ssword_policy_minimum_length.metadata.json | 2 +- ...cy_password_reuse_prevention.metadata.json | 2 +- ...ached_only_to_group_or_roles.metadata.json | 4 +- ...no_administrative_privileges.metadata.json | 2 +- ...m_user_console_access_unused.metadata.json | 2 +- ...r_mfa_enabled_console_access.metadata.json | 4 +- ...s_instance_sql_audit_enabled.metadata.json | 2 +- ...rewall_changes_alert_enabled.metadata.json | 4 +- ...ed_cmk_changes_alert_enabled.metadata.json | 4 +- ...ls_logstore_retention_period.metadata.json | 2 +- ...ation_failures_alert_enabled.metadata.json | 4 +- ...in_without_mfa_alert_enabled.metadata.json | 4 +- ...policy_changes_alert_enabled.metadata.json | 4 +- ...ission_changes_alert_enabled.metadata.json | 4 +- ...m_role_changes_alert_enabled.metadata.json | 4 +- ...ration_changes_alert_enabled.metadata.json | 4 +- ..._account_usage_alert_enabled.metadata.json | 4 +- ..._group_changes_alert_enabled.metadata.json | 4 +- ...ized_api_calls_alert_enabled.metadata.json | 4 +- ...ls_vpc_changes_alert_enabled.metadata.json | 4 +- ..._route_changes_alert_enabled.metadata.json | 4 +- prowler/providers/aws/config/check_types.json | 88 + ...t_is_not_publicly_accessible.metadata.json | 4 +- ...dwatch_alarm_actions_enabled.metadata.json | 2 +- ...ipeline_project_repo_private.metadata.json | 6 +- ...o_user_pool_waf_acl_attached.metadata.json | 2 +- ...supported_mfa_radius_enabled.metadata.json | 2 +- ...entdb_cluster_backup_enabled.metadata.json | 2 +- ...rt_mysql_exposed_to_internet.metadata.json | 2 +- .../ec2_networkacl_unused.metadata.json | 2 +- ...e_cluster_uses_public_subnet.metadata.json | 2 +- ...dge_bus_cross_account_access.metadata.json | 2 +- ...ed_admin_enabled_all_regions.metadata.json | 2 +- ...no_administrative_privileges.metadata.json | 4 +- ...policy_no_full_access_to_kms.metadata.json | 2 +- ...r_with_temporary_credentials.metadata.json | 2 +- ...ector2_active_findings_exist.metadata.json | 2 +- ..._encryption_at_rest_uses_cmk.metadata.json | 6 +- ..._key_not_publicly_accessible.metadata.json | 2 +- ..._cluster_deletion_protection.metadata.json | 2 +- .../rds_cluster_multi_az.metadata.json | 2 +- .../s3_bucket_object_versioning.metadata.json | 4 +- ...container_encryption_enabled.metadata.json | 2 +- ...on_in_associated_elastic_ips.metadata.json | 2 +- ...rver_side_encryption_enabled.metadata.json | 2 +- ...connections_trust_boundaries.metadata.json | 2 +- ...quire_mfa_for_management_api.metadata.json | 6 +- ...ns_record_cname_target_valid.metadata.json | 2 +- .../zone_record_caa_exists.metadata.json | 2 +- .../zone_record_dkim_exists.metadata.json | 2 +- .../zone_record_dmarc_exists.metadata.json | 2 +- .../zone_record_spf_exists.metadata.json | 2 +- .../zone_ssl_strict.metadata.json | 2 +- ...te_image_not_publicly_shared.metadata.json | 2 +- ...ce_group_autohealing_enabled.metadata.json | 2 +- ...nstance_group_multiple_zones.metadata.json | 2 +- ...ded_without_persistent_disks.metadata.json | 2 +- ...onfiguration_changes_enabled.metadata.json | 2 +- ...fender_zap_for_teams_enabled.metadata.json | 2 +- ...gnature_verification_enabled.metadata.json | 2 +- ...ws_all_ingress_from_internet.metadata.json | 2 +- ...twork_security_group_changes.metadata.json | 2 +- tests/lib/check/check_loader_test.py | 93 +- tests/lib/check/check_test.py | 116 +- tests/lib/check/compliance_check_test.py | 22 +- .../lib/check/custom_checks_metadata_test.py | 8 +- .../check/fixtures/bulk_checks_metadata.py | 56 +- tests/lib/check/fixtures/metadata.json | 12 +- tests/lib/check/models_test.py | 1708 ++++++++++++++++- tests/lib/outputs/asff/asff_test.py | 4 +- tests/lib/outputs/csv/csv_test.py | 21 +- tests/lib/outputs/finding_test.py | 65 +- tests/lib/outputs/fixtures/fixtures.py | 14 +- tests/lib/outputs/fixtures/metadata.json | 12 +- tests/lib/outputs/ocsf/ocsf_test.py | 12 +- tests/lib/scan/scan_test.py | 6 +- tests/providers/aws/lib/s3/s3_test.py | 6 +- 93 files changed, 2365 insertions(+), 305 deletions(-) create mode 100644 prowler/providers/aws/config/check_types.json diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index c9d154ec7e..aa1b7b67d4 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 - `entra_conditional_access_policy_device_code_flow_blocked` check for M365 provider [(#10218)](https://github.com/prowler-cloud/prowler/pull/10218) +- CheckMetadata Pydantic validators [(#8584)](https://github.com/prowler-cloud/prowler/pull/8583) ### 🔄 Changed diff --git a/prowler/lib/check/models.py b/prowler/lib/check/models.py index efe294c1d4..6711026023 100644 --- a/prowler/lib/check/models.py +++ b/prowler/lib/check/models.py @@ -1,4 +1,5 @@ import functools +import json import os import re import sys @@ -15,6 +16,115 @@ from prowler.lib.check.compliance_models import Compliance from prowler.lib.check.utils import recover_checks_from_provider from prowler.lib.logger import logger +# Valid ResourceGroup values as defined in the RFC +VALID_RESOURCE_GROUPS = frozenset( + { + "compute", + "container", + "serverless", + "database", + "storage", + "network", + "IAM", + "messaging", + "security", + "monitoring", + "api_gateway", + "ai_ml", + "governance", + "collaboration", + "devops", + "analytics", + } +) + +# Valid Categories as defined in the RFC +VALID_CATEGORIES = frozenset( + { + "encryption", + "internet-exposed", + "logging", + "secrets", + "resilience", + "threat-detection", + "trust-boundaries", + "vulnerabilities", + "cluster-security", + "container-security", + "node-security", + "gen-ai", + "ci-cd", + "identity-access", + "email-security", + "forensics-ready", + "software-supply-chain", + "e3", + "e5", + "privilege-escalation", + "ec2-imdsv1", + } +) + + +@functools.lru_cache(maxsize=1) +def _load_aws_check_types_hierarchy() -> dict: + """ + Load and cache the AWS CheckTypes hierarchy from the JSON config file. + + Returns: + dict: The CheckTypes hierarchy, or empty dict if file not found. + """ + try: + current_dir = os.path.dirname(os.path.abspath(__file__)) + check_types_file = os.path.normpath( + os.path.join( + current_dir, + "..", + "..", + "providers", + "aws", + "config", + "check_types.json", + ) + ) + + if not os.path.exists(check_types_file): + return {} + + with open(check_types_file, "r") as f: + return json.load(f) + + except (FileNotFoundError, json.JSONDecodeError): + return {} + + +def _validate_aws_check_type_in_config(check_type: str) -> bool: + """ + Validate if a CheckType exists in the AWS config using direct lookups. + Supports partial paths: namespace, namespace/category, namespace/category/classifier + + Args: + check_type: The CheckType string to validate (e.g., "TTPs/Initial Access") + + Returns: + bool: True if the CheckType path exists in the config hierarchy + """ + if not check_type: + return False + + hierarchy = _load_aws_check_types_hierarchy() + if not hierarchy: + return False + + path_parts = check_type.split("/") + current_level = hierarchy + for part in path_parts: + if not isinstance(current_level, dict) or part not in current_level: + return False + current_level = current_level[part] + + return True + class Code(BaseModel): """ @@ -94,11 +204,19 @@ class CheckMetadata(BaseModel): Compliance (list, optional): The compliance information for the check. Defaults to None. Validators: - valid_category(value): Validator function to validate the categories of the check. + valid_category(value): Validator function to validate the categories of the check against predefined values. severity_to_lower(severity): Validator function to convert the severity to lowercase. - valid_severity(severity): Validator function to validate the severity of the check. valid_cli_command(remediation): Validator function to validate the CLI command is not an URL. valid_resource_type(resource_type): Validator function to validate the resource type is not empty. + validate_service_name(service_name, values): Validator function to validate the service name matches CheckID. + valid_check_id(check_id): Validator function to validate the CheckID format. + validate_check_title(check_title): Validator function to validate CheckTitle max length (150 chars) and not starting with 'Ensure'. + validate_related_url(related_url): Validator function to validate RelatedUrl is empty (deprecated field). + validate_recommendation_url(remediation): Validator function to validate Recommendation URL points to Prowler Hub. + validate_check_type(check_type, values): Validator function to validate CheckType - must be empty for non-AWS providers, no empty strings and predefined types validation for AWS. + validate_description(description): Validator function to validate Description max length (400 chars). + validate_risk(risk): Validator function to validate Risk max length (400 chars). + validate_resource_group(resource_group): Validator function to validate ResourceGroup against predefined values. validate_additional_urls(additional_urls): Validator function to ensure AdditionalURLs contains no duplicates. """ @@ -135,6 +253,10 @@ class CheckMetadata(BaseModel): raise ValueError( f"Invalid category: {value}. Categories can only contain lowercase letters, numbers and hyphen '-'" ) + if value_lower not in VALID_CATEGORIES: + raise ValueError( + f"Invalid category: '{value_lower}'. Must be one of: {', '.join(sorted(VALID_CATEGORIES))}." + ) return value_lower @validator("Severity", pre=True, always=True) @@ -183,6 +305,86 @@ class CheckMetadata(BaseModel): return check_id + @validator("CheckTitle", pre=True, always=True) + def validate_check_title(cls, check_title): + if len(check_title) > 150: + raise ValueError( + f"CheckTitle must not exceed 150 characters, got {len(check_title)} characters" + ) + if check_title.startswith("Ensure"): + raise ValueError( + "CheckTitle must not start with 'Ensure'. Use a descriptive title that focuses on the security state." + ) + return check_title + + @validator("RelatedUrl", pre=True, always=True) + def validate_related_url(cls, related_url): + if related_url: + raise ValueError("RelatedUrl must be empty. This field is deprecated.") + return related_url + + @validator("Remediation") + def validate_recommendation_url(remediation): + url = remediation.Recommendation.Url + if url and not url.startswith("https://hub.prowler.com/"): + raise ValueError( + f"Remediation Recommendation URL must point to Prowler Hub (https://hub.prowler.com/...), got '{url}'." + ) + return remediation + + @validator("CheckType", pre=True, always=True) + def validate_check_type(cls, check_type, values): + provider = values.get("Provider", "").lower() + + # Non-AWS providers must have an empty CheckType list + if provider != "aws" and provider not in EXTERNAL_TOOL_PROVIDERS: + if check_type: + raise ValueError( + f"CheckType must be empty for non-AWS providers. Got {check_type} for provider '{provider}'." + ) + return check_type + + # Check for empty strings in the list - applies to AWS + for i, check_type_item in enumerate(check_type): + if not check_type_item or check_type_item.strip() == "": + raise ValueError( + f"CheckType list cannot contain empty strings. Found empty string at index {i}." + ) + + # For AWS provider, validate against config hierarchy + if provider == "aws": + for check_type_item in check_type: + if not _validate_aws_check_type_in_config(check_type_item): + raise ValueError( + f"Invalid CheckType: '{check_type_item}'. Must be a valid path in the AWS CheckType hierarchy. See prowler/providers/aws/config/check_types.json for valid values." + ) + + return check_type + + @validator("Description", pre=True, always=True) + def validate_description(cls, description): + if len(description) > 400: + raise ValueError( + f"Description must not exceed 400 characters, got {len(description)} characters" + ) + return description + + @validator("Risk", pre=True, always=True) + def validate_risk(cls, risk): + if len(risk) > 400: + raise ValueError( + f"Risk must not exceed 400 characters, got {len(risk)} characters" + ) + return risk + + @validator("ResourceGroup", pre=True, always=True) + def validate_resource_group(cls, resource_group): + if resource_group and resource_group not in VALID_RESOURCE_GROUPS: + raise ValueError( + f"Invalid ResourceGroup: '{resource_group}'. Must be one of: {', '.join(sorted(VALID_RESOURCE_GROUPS))} or empty string." + ) + return resource_group + @validator("AdditionalURLs", pre=True, always=True) def validate_additional_urls(cls, additional_urls): if not isinstance(additional_urls, list): diff --git a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.metadata.json b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.metadata.json index ed8ff162b0..bb760f450e 100644 --- a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.metadata.json @@ -9,7 +9,7 @@ "Severity": "critical", "ResourceType": "ALIYUN::ACTIONTRAIL::Trail", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud ActionTrail** is a service that records API calls made to your account and delivers log files containing the identity of the API caller, the time and source IP of the call, the request parameters, and the response elements returned by the service. Ensuring that a **multi-region trail** exists guarantees that management operations performed across all regions and global services are captured, enabling detection of unexpected activities in otherwise unused regions.", + "Description": "**Alibaba Cloud ActionTrail** records API calls made to your account, including caller identity, time, source IP, request parameters, and response elements. Ensuring a **multi-region trail** exists guarantees that operations across all regions and global services are captured, enabling detection of unexpected activities in unused regions.", "Risk": "Without a **multi-region trail** enabled, API calls made in regions outside the primary trail's scope will not be recorded. This creates blind spots in **security analysis**, **resource change tracking**, and **compliance auditing**, potentially allowing unauthorized or malicious activity to go undetected across your Alibaba Cloud account.", "RelatedUrl": "", "AdditionalURLs": [ diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.metadata.json index 6809db19ec..79317727bf 100644 --- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster", "ResourceGroup": "container", - "Description": "**Alibaba Cloud CloudMonitor** agent provides visibility into system metrics for **Kubernetes Engine clusters**, including CPU utilization, disk traffic, network traffic, and disk IO information. Without the CloudMonitor agent enabled, operators lack critical observability into node and pod health, making it difficult to detect performance degradation or anomalous resource consumption. Enabling CloudMonitor ensures that a monitor controller is created to periodically connect to each node, collect metrics about its Pods and containers, and send them to the **CloudMonitor** server for analysis and alerting.", - "Risk": "Without **CloudMonitor** enabled on Kubernetes Engine clusters, there is no automated collection of system-level metrics such as CPU, memory, disk, and network usage. This lack of visibility can delay detection of **resource exhaustion**, **node failures**, and **abnormal workload behavior**, increasing the risk of undetected **availability** and **performance** issues. In a security context, the absence of monitoring data impairs the ability to identify **denial-of-service conditions** or **cryptojacking** activities running on cluster nodes.", + "Description": "**CloudMonitor** agent provides visibility into system metrics for **Kubernetes Engine clusters**, including CPU, disk, network, and IO. Without it, operators lack observability into node and pod health. Enabling CloudMonitor creates a controller that collects metrics from each node's Pods and containers for analysis and alerting.", + "Risk": "Without **CloudMonitor**, there is no automated collection of system metrics (CPU, memory, disk, network). This delays detection of **resource exhaustion**, **node failures**, and **abnormal workloads**, increasing risk of undetected **availability** issues. It also impairs identification of **denial-of-service** or **cryptojacking** on cluster nodes.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/ack/", diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.metadata.json index 948ca7539f..d805ca1629 100644 --- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.metadata.json +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster", "ResourceGroup": "container", - "Description": "**Alibaba Cloud Kubernetes Engine** provides a cluster health check feature that validates the health state of each node and verifies the cluster configuration, including `kubelet`, `docker daemon`, `kernel`, and network `iptables` settings. Running these checks regularly ensures that cloud resources such as **VPC/VSwitch**, **SLB**, and every **ECS node** are functioning correctly. If consecutive health check failures are detected, diagnostic reports are generated for administrators to take corrective action.", - "Risk": "Without regular cluster health checks within the configured period, potential issues such as **node failures**, **misconfigured network rules**, or **degraded system components** may go undetected. This increases the risk of **cluster instability**, **service outages**, and **security vulnerabilities** that could be exploited by attackers. Delayed detection of unhealthy nodes or misconfigured components can also impact the **integrity** and **availability** of workloads running on the cluster.", + "Description": "**Alibaba Cloud Kubernetes Engine** provides a cluster health check that validates node health and cluster configuration, including `kubelet`, `docker daemon`, `kernel`, and `iptables` settings. Running checks regularly ensures **VPC/VSwitch**, **SLB**, and **ECS nodes** function correctly. Consecutive failures generate diagnostic reports for corrective action.", + "Risk": "Without regular cluster health checks, **node failures**, **misconfigured network rules**, or **degraded components** may go undetected, increasing the risk of **cluster instability**, **service outages**, and exploitable **security vulnerabilities**. Delayed detection of unhealthy nodes can impact the **integrity** and **availability** of workloads running on the cluster.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/ack/", diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.metadata.json index 9e7bb1af6a..a18b4a34c6 100644 --- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.metadata.json +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster", "ResourceGroup": "container", - "Description": "**Alibaba Cloud Kubernetes Engine** provides a cluster health check feature that validates the health state of each node and verifies the cluster configuration, including `kubelet`, `docker daemon`, `kernel`, and network `iptables` settings. Running these checks at least once per week ensures that cloud resources such as **VPC/VSwitch**, **SLB**, and every **ECS node** are functioning correctly. If consecutive health check failures are detected, diagnostic reports are generated for administrators to take corrective action.", - "Risk": "Without weekly cluster health checks, potential issues such as **node failures**, **misconfigured network rules**, or **degraded system components** may go undetected for extended periods. This increases the risk of **cluster instability**, **service outages**, and **security vulnerabilities** that could be exploited by attackers. Delayed detection of unhealthy nodes or misconfigured components can also impact the **integrity** and **availability** of workloads running on the cluster.", + "Description": "**Alibaba Cloud Kubernetes Engine** provides a cluster health check that validates node health and cluster configuration, including `kubelet`, `docker daemon`, `kernel`, and `iptables` settings. Weekly checks ensure **VPC/VSwitch**, **SLB**, and **ECS nodes** function correctly. Consecutive failures generate diagnostic reports for corrective action.", + "Risk": "Without weekly health checks, **node failures**, **misconfigured network rules**, or **degraded components** may go undetected for extended periods, increasing the risk of **cluster instability**, **service outages**, and exploitable **security vulnerabilities**. Delayed detection can impact the **integrity** and **availability** of workloads on the cluster.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/ack/", diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.metadata.json index 162f75c79f..e933e258d2 100644 --- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.metadata.json +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "high", "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster", "ResourceGroup": "container", - "Description": "**Alibaba Cloud Kubernetes Engine** clusters should not have the **Kubernetes Dashboard** (web UI) enabled. The Dashboard is backed by a highly privileged Kubernetes Service Account that can perform administrative operations across the cluster. It is recommended to use the **ACK Console** instead, which provides fine-grained access control through RAM policies and RBAC integration, avoiding the risk of privilege escalation through a compromised dashboard.", - "Risk": "The **Kubernetes Dashboard** is backed by a highly privileged Service Account that grants broad access to cluster resources. If the Dashboard is compromised through a vulnerability or unauthorized access, an attacker could gain **full control** over the cluster, deploy malicious workloads, exfiltrate **secrets**, and **escalate privileges**. This directly impacts the **confidentiality**, **integrity**, and **availability** of all workloads and data within the cluster.", + "Description": "**Alibaba Cloud Kubernetes Engine** clusters should not have the **Kubernetes Dashboard** (web UI) enabled. The Dashboard uses a highly privileged Service Account that can perform administrative operations across the cluster. Use the **ACK Console** instead, which provides fine-grained access control through RAM policies and RBAC integration.", + "Risk": "The **Kubernetes Dashboard** uses a highly privileged Service Account with broad cluster access. If compromised through a vulnerability or unauthorized access, an attacker could gain **full control** over the cluster, deploy malicious workloads, exfiltrate **secrets**, and **escalate privileges**, impacting **confidentiality**, **integrity**, and **availability** of all workloads and data.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/doc-detail/86494.html", diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.metadata.json index d0c4e8cc92..4bb208b4a4 100644 --- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster", "ResourceGroup": "container", - "Description": "**Alibaba Cloud Elastic Network Interface (ENI)** supports assigning multiple IP addresses to a single virtual machine's network interface. With **ENI multiple IP mode** provided by the **Terway** network plugin, Kubernetes Engine clusters can allocate pod IP addresses from the VPC CIDR block, enabling better scalability and native integration with other Alibaba Cloud services. This mode allows pods to have their own security group associations, providing granular network-level access control independently from their host nodes.", - "Risk": "Without **ENI multiple IP mode** (provided by the **Terway** network plugin), pods share the node's network interface in a less scalable manner and cannot have independent security group associations. This limits the ability to apply **granular firewall controls** at the pod level, increasing the risk of **lateral movement** if a pod is compromised. The inability to isolate pod networking from node networking weakens **network segmentation** and reduces the overall **security posture** of the cluster.", + "Description": "With **ENI multiple IP mode** provided by the **Terway** network plugin, Kubernetes Engine clusters allocate pod IPs from the VPC CIDR block, enabling better scalability and native integration with Alibaba Cloud services. This mode allows pods to have their own security group associations, providing granular network-level access control independently from host nodes.", + "Risk": "Without **ENI multiple IP mode** (**Terway** plugin), pods share the node's network interface and cannot have independent security group associations. This limits **granular firewall controls** at the pod level, increasing **lateral movement** risk if a pod is compromised. The inability to isolate pod from node networking weakens **network segmentation** and the cluster's **security posture**.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/ack/ack-managed-and-ack-dedicated/user-guide/associate-multiple-security-groups-with-an-eni", diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.metadata.json index b0d683ca31..78e2433f0c 100644 --- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "high", "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster", "ResourceGroup": "container", - "Description": "**Alibaba Cloud Log Service** is a real-time data logging service that supports collection, shipping, search, storage, and analysis for container and audit logs in **Kubernetes Engine clusters**. When enabled, Log Service automatically collects `kube-apiserver` audit logs, ingress visiting logs, and standard output/error logs from containerized processes. These logs are stored in a dedicated, persistent datastore and are essential for operational visibility, security monitoring, and compliance auditing.", - "Risk": "Without **Log Service** enabled, there is no centralized collection of container logs, audit trails, or cluster events. This severely impairs the ability to perform **incident investigation**, **compliance auditing**, and **security monitoring**. Attackers could operate undetected within the cluster, as there would be no audit trail of API server calls, pod lifecycle events, or container output. The lack of logging directly impacts the **confidentiality** and **integrity** of the cluster by removing a critical detection and forensic capability.", + "Description": "**Alibaba Cloud Log Service** supports collection, search, storage, and analysis for container and audit logs in **Kubernetes Engine clusters**. When enabled, it automatically collects `kube-apiserver` audit logs, ingress logs, and stdout/stderr from containers. These logs are stored persistently and are essential for operational visibility, security monitoring, and compliance auditing.", + "Risk": "Without **Log Service**, there is no centralized collection of container logs or cluster events, impairing **incident investigation**, **compliance auditing**, and **security monitoring**. Attackers could operate undetected with no audit trail of API server calls or pod events, impacting cluster **confidentiality** and **integrity**.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/ack/", diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.metadata.json index 830d62e716..a0a40210cb 100644 --- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster", "ResourceGroup": "container", - "Description": "**Alibaba Cloud Kubernetes Engine** clusters should have **Network Policy** support enabled through the **Terway** network plugin. A `NetworkPolicy` is a Kubernetes specification that defines how groups of pods are allowed to communicate with each other and other network endpoints using label-based selection rules. By default, pods are non-isolated and accept traffic from any source; applying NetworkPolicy resources restricts traffic to only explicitly allowed connections, enforcing the principle of least privilege at the network level.", - "Risk": "Without **Network Policies**, all pods in a Kubernetes cluster can communicate with each other freely, creating an unrestricted flat network. This allows an attacker who compromises a single pod to move **laterally** within the cluster, accessing sensitive services, databases, and secrets without restriction. The absence of network segmentation undermines **defense in depth** and increases the blast radius of any compromise, directly impacting the **confidentiality** and **integrity** of workloads.", + "Description": "**Alibaba Cloud Kubernetes Engine** clusters should enable **Network Policy** via the **Terway** plugin. A `NetworkPolicy` defines how pods communicate using label-based rules. By default, pods accept traffic from any source; NetworkPolicy restricts traffic to explicitly allowed connections, enforcing least privilege at the network level.", + "Risk": "Without **Network Policies**, all pods communicate freely, creating an unrestricted flat network. An attacker who compromises a single pod can move **laterally**, accessing sensitive services, databases, and secrets. The absence of network segmentation undermines **defense in depth** and increases the blast radius of any compromise, impacting **confidentiality** and **integrity** of workloads.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/ack/", diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.metadata.json index 92db7daa7a..26eb8ea727 100644 --- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster", "ResourceGroup": "container", - "Description": "**Alibaba Cloud Kubernetes Engine** clusters should be configured as **private clusters** to ensure that the API server endpoint is not accessible from the public internet. In a private cluster, nodes do not have public IP addresses and all communication between nodes and the master occurs privately through **VPC peering**. This significantly reduces the attack surface by eliminating direct internet exposure of the cluster's control plane and worker nodes.", - "Risk": "Exposing the **API server endpoint** to the public internet increases the attack surface of the cluster, allowing attackers to probe for vulnerabilities, perform **brute force attacks** against authentication, or exploit misconfigurations. A publicly accessible API server can be targeted by automated scanning tools and botnets, increasing the risk of unauthorized access. This directly impacts the **confidentiality** and **integrity** of the cluster by potentially allowing attackers to execute commands, deploy malicious workloads, or exfiltrate sensitive data.", + "Description": "**Alibaba Cloud Kubernetes Engine** clusters should be configured as **private clusters** so the API server endpoint is not publicly accessible. In a private cluster, nodes lack public IPs and all node-to-master communication occurs through **VPC peering**. This reduces the attack surface by eliminating direct internet exposure of the control plane and worker nodes.", + "Risk": "A public **API server endpoint** allows attackers to probe for vulnerabilities, perform **brute force attacks**, or exploit misconfigurations. Automated scanners and botnets can target it, increasing risk of unauthorized access. This impacts **confidentiality** and **integrity** by potentially allowing attackers to execute commands, deploy malicious workloads, or exfiltrate data.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/ack/", diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.metadata.json index c329225cb7..67312022de 100644 --- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "high", "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster", "ResourceGroup": "container", - "Description": "**Alibaba Cloud Kubernetes Engine** clusters should have **Role-Based Access Control (RBAC)** enabled to enforce fine-grained authorization for cluster resources. RBAC allows administrators to define roles with specific permissions at both the cluster and namespace level, ensuring that users and service accounts only have access to the resources they need. The legacy **ABAC** (Attribute-Based Access Control) authorizer grants broad, statically defined permissions and should be disabled in favor of RBAC for improved security.", - "Risk": "Without **RBAC** enabled, Kubernetes clusters may rely on legacy authorization mechanisms such as **ABAC**, which grant **overly broad permissions** that cannot be scoped to specific namespaces or resource types. This increases the risk of **unauthorized access** and **privilege escalation**, where a compromised service account or user could gain access to sensitive resources across the entire cluster. The lack of granular access control directly impacts the **confidentiality** and **integrity** of workloads and secrets stored in the cluster.", + "Description": "**Alibaba Cloud Kubernetes Engine** clusters should have **RBAC** enabled for fine-grained authorization. RBAC lets administrators define roles with specific permissions at cluster and namespace level, ensuring users and service accounts access only needed resources. Legacy **ABAC** grants broad, static permissions and should be disabled in favor of RBAC.", + "Risk": "Without **RBAC**, clusters may rely on legacy **ABAC** which grants **overly broad permissions** that cannot be scoped to specific namespaces or resources. This increases the risk of **unauthorized access** and **privilege escalation**, where a compromised account could access sensitive resources across the cluster, impacting **confidentiality** and **integrity**.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/ack/", diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.metadata.json b/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.metadata.json index 2a9f51c0b9..cc8acd56c8 100644 --- a/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.metadata.json +++ b/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.metadata.json @@ -9,7 +9,7 @@ "Severity": "high", "ResourceType": "ALIYUN::ECS::Disk", "ResourceGroup": "storage", - "Description": "**Alibaba Cloud ECS cloud disk encryption** protects data at rest by automatically encrypting data when it is transferred from ECS instances to disks and decrypting it when read. Ensuring that all attached disks are encrypted helps prevent unauthorized access to sensitive data stored on the disk. This check verifies that **disk encryption** is enabled on all ECS disks attached to instances, using **KMS** (Key Management Service) for key management.", + "Description": "**Alibaba Cloud ECS disk encryption** protects data at rest by automatically encrypting data transferred to disks and decrypting when read. Ensuring all attached disks are encrypted prevents unauthorized access to stored data. This check verifies **disk encryption** is enabled on all ECS disks attached to instances, using **KMS** for key management.", "Risk": "**Unencrypted disks** attached to ECS instances pose a significant security risk, as sensitive data could be exposed if the disk is compromised, improperly decommissioned, or accessed by unauthorized parties. Data at rest without encryption is vulnerable to **unauthorized access**, impacting **confidentiality** and potentially leading to **data breaches** or **regulatory non-compliance**.", "RelatedUrl": "", "AdditionalURLs": [ diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.metadata.json b/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.metadata.json index d018b97768..5ae20ef78a 100644 --- a/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.metadata.json +++ b/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.metadata.json @@ -9,7 +9,7 @@ "Severity": "high", "ResourceType": "ALIYUN::ECS::Instance", "ResourceGroup": "compute", - "Description": "**Alibaba Cloud Security Center** provides endpoint protection for ECS instances, offering real-time detection and removal of viruses, spyware, and other malicious software. This check verifies that the **Security Center agent** is installed and active on all ECS instances, ensuring configurable alerts notify administrators when known malicious software attempts to install itself or execute on the instance.", + "Description": "**Alibaba Cloud Security Center** provides endpoint protection for ECS instances with real-time detection and removal of malicious software. This check verifies the **Security Center agent** is installed and active on all ECS instances, ensuring alerts notify administrators when malicious software attempts to install or execute.", "Risk": "ECS instances without **endpoint protection** are vulnerable to **malware**, **viruses**, **webshells**, and other security threats that can compromise **confidentiality**, **integrity**, and **availability**. Without real-time monitoring, security incidents may go undetected, allowing attackers to maintain persistent access and exfiltrate sensitive data.", "RelatedUrl": "", "AdditionalURLs": [ diff --git a/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.metadata.json index d0ce95a9a1..233117c54c 100644 --- a/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.metadata.json +++ b/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.metadata.json @@ -9,8 +9,8 @@ "Severity": "critical", "ResourceType": "ALIYUN::RAM::User", "ResourceGroup": "IAM", - "Description": "**Alibaba Cloud RAM** access keys provide programmatic access to a given account. The **root account** is the most privileged user in an Alibaba Cloud account and should not have access keys associated with it. It is recommended that all access keys associated with the root account be removed to limit vectors by which the account can be compromised and encourage the creation of **role-based accounts** that follow the principle of least privilege.", - "Risk": "The **root account** has unrestricted access to all resources and services within the Alibaba Cloud account. If access keys for the root account are compromised, an attacker gains **full administrative control** over the entire account, including the ability to create, modify, or delete any resource. This poses a critical risk to the **confidentiality**, **integrity**, and **availability** of all cloud resources and data.", + "Description": "**Alibaba Cloud RAM** access keys provide programmatic access to an account. The **root account** is the most privileged user and should not have access keys. All root access keys should be removed to limit compromise vectors and encourage **role-based accounts** following the principle of least privilege.", + "Risk": "The **root account** has unrestricted access to all resources and services. If its access keys are compromised, an attacker gains **full administrative control**, including ability to create, modify, or delete any resource. This poses critical risk to the **confidentiality**, **integrity**, and **availability** of all cloud resources and data.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/doc-detail/102600.htm", diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.metadata.json index 88e9292d37..b365325db0 100644 --- a/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.metadata.json +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::RAM::SecurityPreference", "ResourceGroup": "IAM", - "Description": "**Alibaba Cloud RAM** password policies can require passwords to be expired after a given number of days. It is recommended that the password policy expire passwords after **365 days** or less to ensure periodic credential rotation. The CIS benchmark recommends an **annual password reset** as a balanced approach that avoids forcing overly frequent changes while still ensuring compromised credentials have a limited lifespan.", - "Risk": "Without a maximum password age policy, compromised passwords can remain valid **indefinitely**, giving attackers persistent access to cloud resources. While overly frequent password changes can encourage users to choose weak variants, a reasonable maximum age of **365 days** ensures that any compromised credentials are eventually invalidated, reducing the window of opportunity for unauthorized access and protecting the **confidentiality** and **integrity** of account data.", + "Description": "**Alibaba Cloud RAM** password policies can require passwords to expire after a given number of days. It is recommended to expire passwords after **365 days** or less for periodic credential rotation. The CIS benchmark recommends an **annual reset** as a balanced approach that avoids overly frequent changes while ensuring compromised credentials have a limited lifespan.", + "Risk": "Without a maximum password age, compromised passwords remain valid **indefinitely**, giving attackers persistent access. A reasonable maximum of **365 days** ensures compromised credentials are eventually invalidated, reducing the window for unauthorized access and protecting **confidentiality** and **integrity** of account data.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/doc-detail/116413.htm", diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.metadata.json index fd1be8c195..e8c3ec01ab 100644 --- a/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.metadata.json +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "ALIYUN::RAM::SecurityPreference", "ResourceGroup": "IAM", "Description": "**Alibaba Cloud RAM** password policies can be used to enforce password complexity requirements. It is recommended that the password policy require a minimum of **14 or greater characters** for any password. Longer passwords provide exponentially more security against automated password cracking, as the keyspace increases dramatically with each additional character.", - "Risk": "Allowing short passwords significantly reduces the effort required for **brute force attacks** to succeed. Passwords shorter than **14 characters** can be cracked in a fraction of the time compared to longer passwords, potentially compromising the **confidentiality** of user accounts. This can lead to unauthorized access to cloud resources and sensitive data, affecting the overall **integrity** and **availability** of the environment.", + "Risk": "Short passwords significantly reduce the effort required for **brute force attacks**. Passwords shorter than **14 characters** can be cracked much faster, potentially compromising **confidentiality** of user accounts. This can lead to unauthorized access to cloud resources and sensitive data, affecting the **integrity** and **availability** of the environment.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/doc-detail/116413.htm", diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.metadata.json index 43b3f7ae5d..199900a958 100644 --- a/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.metadata.json +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "ALIYUN::RAM::SecurityPreference", "ResourceGroup": "IAM", "Description": "**Alibaba Cloud RAM** password policies can be configured to prevent the reuse of previously used passwords. It is recommended that the password policy prevent the reuse of passwords to ensure users cannot cycle back to previously compromised credentials. This increases account resiliency against **brute force logon attempts** and reduces the risk of credential reuse attacks.", - "Risk": "Without **password reuse prevention**, users may cycle back to previously compromised passwords. If a password was compromised in the past and is later reused, attackers with knowledge of old credentials can regain access to the account, threatening the **confidentiality** and **integrity** of cloud resources. This significantly weakens the overall security posture of the Alibaba Cloud environment.", + "Risk": "Without **password reuse prevention**, users may cycle back to previously compromised passwords. Attackers with knowledge of old credentials can regain access, threatening the **confidentiality** and **integrity** of cloud resources. This weakens the overall security posture of the Alibaba Cloud environment.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/doc-detail/116413.htm", diff --git a/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.metadata.json index 5c895dc0ca..20afdf2dca 100644 --- a/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.metadata.json +++ b/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.metadata.json @@ -9,8 +9,8 @@ "Severity": "low", "ResourceType": "ALIYUN::RAM::ManagedPolicy", "ResourceGroup": "IAM", - "Description": "**Alibaba Cloud RAM** users, groups, and roles have no access to resources by default. RAM policies are the means by which privileges are granted to users, groups, or roles. It is recommended that RAM policies be applied directly to **groups and roles** but not to individual users. This simplifies access management and reduces the likelihood of granting unintended permissions as the number of users grows.", - "Risk": "Assigning privileges directly to individual users instead of **groups or roles** increases the complexity of access management. As the number of users grows, this complexity can lead to principals inadvertently receiving or retaining **excessive privileges**, threatening the **confidentiality** and **integrity** of cloud resources. It also makes auditing and compliance reviews significantly more difficult.", + "Description": "**Alibaba Cloud RAM** users, groups, and roles have no access by default. RAM policies grant privileges to these principals. It is recommended to apply policies to **groups and roles** rather than individual users, simplifying access management and reducing unintended permissions as the number of users grows.", + "Risk": "Assigning privileges directly to users instead of **groups or roles** increases access management complexity. As users grow, this can lead to principals receiving **excessive privileges**, threatening **confidentiality** and **integrity** of cloud resources. It also makes auditing and compliance reviews significantly harder.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/doc-detail/116820.htm", diff --git a/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.metadata.json index 27fbeff47c..739d97a51b 100644 --- a/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.metadata.json +++ b/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.metadata.json @@ -9,7 +9,7 @@ "Severity": "critical", "ResourceType": "ALIYUN::RAM::ManagedPolicy", "ResourceGroup": "IAM", - "Description": "**Alibaba Cloud RAM** policies represent permissions that can be granted to users, groups, or roles. It is recommended to follow the principle of **least privilege** by granting only the permissions required to perform specific tasks. RAM policies with `\"Effect\": \"Allow\"`, `\"Action\": \"*\"`, and `\"Resource\": \"*\"` should be avoided as they grant full administrative access to all resources and services.", + "Description": "**Alibaba Cloud RAM** policies grant permissions to users, groups, or roles. Follow the principle of **least privilege** by granting only required permissions. Policies with `\"Effect\": \"Allow\"`, `\"Action\": \"*\"`, and `\"Resource\": \"*\"` should be avoided as they grant full administrative access to all resources.", "Risk": "RAM policies granting **full administrative privileges** (`*:*`) expose all cloud resources to potentially unwanted actions. If such a policy is attached to a compromised user, group, or role, an attacker gains unrestricted access to create, modify, or delete any resource, severely impacting the **confidentiality**, **integrity**, and **availability** of the entire Alibaba Cloud environment.", "RelatedUrl": "", "AdditionalURLs": [ diff --git a/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.metadata.json index 3d920c351e..6cb920bc55 100644 --- a/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.metadata.json +++ b/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "ALIYUN::RAM::User", "ResourceGroup": "IAM", "Description": "**Alibaba Cloud RAM** users can log on to the console by using their username and password. If a user has not logged on for **90 days or longer**, it is recommended to disable the console access of the user. Disabling unused console access reduces the attack surface by removing unnecessary logon capabilities from potentially abandoned or dormant accounts.", - "Risk": "Inactive accounts with console access enabled are common targets for attackers attempting **account takeover**. An abandoned user account or one with a **compromised password** that has not been used in over 90 days may go unmonitored, allowing unauthorized access to go undetected. This poses a significant risk to the **confidentiality** and **integrity** of cloud resources accessible through the compromised account.", + "Risk": "Inactive accounts with console access are common targets for **account takeover**. An abandoned account or one with a **compromised password** unused for over 90 days may go unmonitored, allowing undetected unauthorized access. This risks the **confidentiality** and **integrity** of cloud resources accessible through the account.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/doc-detail/116820.htm", diff --git a/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.metadata.json index 92b537df54..13b738620d 100644 --- a/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.metadata.json +++ b/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.metadata.json @@ -9,8 +9,8 @@ "Severity": "high", "ResourceType": "ALIYUN::RAM::User", "ResourceGroup": "IAM", - "Description": "**Alibaba Cloud RAM** supports **Multi-Factor Authentication (MFA)**, which adds an extra layer of protection on top of a username and password. With MFA enabled, when a user logs on to the console, they are prompted for their username and password followed by an authentication code from their virtual MFA device. It is recommended that MFA be enabled for all RAM users that have a console password to significantly strengthen account security.", - "Risk": "Without **MFA** enabled, RAM user accounts rely solely on passwords for authentication. If a password is compromised through phishing, credential stuffing, or other attacks, an attacker gains full access to the account. Enabling MFA requires an additional authentication factor, making it significantly harder for attackers to gain unauthorized access even with compromised credentials, thereby protecting the **confidentiality**, **integrity**, and **availability** of cloud resources.", + "Description": "**Alibaba Cloud RAM** supports **MFA**, adding protection on top of username and password. With MFA enabled, console logon requires an authentication code from a virtual MFA device after entering credentials. MFA should be enabled for all RAM users with console passwords to strengthen account security.", + "Risk": "Without **MFA**, RAM accounts rely solely on passwords. If compromised through phishing or credential stuffing, an attacker gains full account access. MFA requires an additional factor, making unauthorized access significantly harder even with compromised credentials, protecting **confidentiality**, **integrity**, and **availability** of cloud resources.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/doc-detail/119555.htm", diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.metadata.json b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.metadata.json index 76ed06d5b5..ee0e937667 100644 --- a/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.metadata.json @@ -9,7 +9,7 @@ "Severity": "medium", "ResourceType": "ALIYUN::RDS::DBInstance", "ResourceGroup": "database", - "Description": "**Alibaba Cloud RDS instances** should have **SQL auditing** (SQL Explorer) enabled to track database events and write them to an audit log. SQL auditing helps maintain **regulatory compliance**, understand database activity, and gain insight into discrepancies and anomalies that could indicate business concerns or suspected security violations. This applies to all RDS engine types except SQL Server 2012/2016/2017 and MariaDB TX.", + "Description": "**Alibaba Cloud RDS instances** should have **SQL auditing** (SQL Explorer) enabled to track database events in an audit log. This helps maintain **regulatory compliance**, understand database activity, and detect anomalies indicating security violations. Applies to all RDS engines except SQL Server 2012/2016/2017 and MariaDB TX.", "Risk": "Without **SQL auditing**, it is difficult to detect **unauthorized access**, **data breaches**, or **malicious activity** within the database. The absence of audit logs hinders **forensic investigations**, compliance reporting, and the ability to identify **data exfiltration** or **privilege escalation** attempts, impacting **confidentiality** and **integrity**.", "RelatedUrl": "", "AdditionalURLs": [ diff --git a/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.metadata.json index 0e64eb6412..a9b7a94dca 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a metric filter and alarm configured for **Cloud Firewall** rule changes. By directing **ActionTrail** logs to SLS and establishing corresponding query and alert rules, real-time monitoring of firewall modifications can be achieved. This ensures that any creation, update, or deletion of Cloud Firewall control policies is promptly detected and reviewed.", - "Risk": "Without monitoring for **Cloud Firewall** changes, unauthorized or accidental modifications to firewall rules may go undetected. This could lead to **network exposure**, allowing malicious traffic to reach protected resources or blocking legitimate traffic. Failure to detect firewall rule changes in a timely manner increases the risk of **data breaches**, **lateral movement**, and **service disruption**.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for **Cloud Firewall** rule changes. By directing **ActionTrail** logs to SLS with alert rules, real-time monitoring of firewall modifications is achieved. This ensures creation, update, or deletion of Cloud Firewall control policies is promptly detected and reviewed.", + "Risk": "Without monitoring for **Cloud Firewall** changes, unauthorized modifications to firewall rules may go undetected, leading to **network exposure** or blocked legitimate traffic. Failure to detect changes timely increases risk of **data breaches**, **lateral movement**, and **service disruption**.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.metadata.json index 0aacaf5541..0d988e971a 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for customer-created **KMS Customer Master Keys (CMKs)** that have changed state to disabled or scheduled for deletion. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that any disabling or deletion of encryption keys is promptly detected, preventing accidental or malicious loss of access to encrypted data.", - "Risk": "Without monitoring for **CMK state changes**, data encrypted with **disabled or deleted keys** will become permanently inaccessible. This could lead to **data loss**, **business disruption**, and **compliance violations**. Malicious actors who gain access to the KMS service could silently disable or schedule deletion of critical encryption keys, rendering protected data unrecoverable if not detected in time.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for customer-created **KMS CMKs** that are disabled or scheduled for deletion. By directing **ActionTrail** logs to SLS with alert rules, disabling or deletion of encryption keys is promptly detected, preventing accidental or malicious loss of access to encrypted data.", + "Risk": "Without monitoring for **CMK state changes**, data encrypted with **disabled or deleted keys** becomes permanently inaccessible, leading to **data loss**, **business disruption**, and **compliance violations**. Malicious actors could silently disable or schedule deletion of encryption keys, rendering data unrecoverable.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.metadata.json index 828a15bfb7..5674c64ac4 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "ALIYUN::SLS::Logstore", "ResourceGroup": "monitoring", "Description": "**Alibaba Cloud Simple Log Service (SLS)** Logstore data retention should be configured for at least **365 days**. The Logstore retention period controls how long activity logs are stored and available for analysis. Ensuring a minimum retention of `365` days provides sufficient time to investigate security incidents, perform forensic analysis, and meet regulatory compliance requirements.", - "Risk": "Insufficient log retention periods may result in the **loss of forensic evidence** needed for security investigations and incident response. If logs are deleted before an incident is detected, it becomes impossible to determine the scope, impact, and root cause of security breaches. Short retention periods may also lead to **compliance violations** with regulations that mandate specific log retention durations, affecting the organization's **integrity** and **accountability**.", + "Risk": "Insufficient log retention may result in **loss of forensic evidence** for security investigations. If logs are deleted before an incident is detected, determining the scope and root cause of breaches becomes impossible. Short retention may also cause **compliance violations** with regulations mandating specific durations, affecting **integrity** and **accountability**.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/doc-detail/48990.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.metadata.json index a99cff9645..6d00bd7813 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for **failed console authentication attempts**. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that repeated login failures are detected promptly, enabling early identification of brute-force attacks or credential stuffing attempts against the Management Console.", - "Risk": "Without monitoring for **failed console authentication attempts**, brute-force attacks and credential stuffing campaigns may go undetected. Failure to identify these patterns increases the risk of **unauthorized access** to the Management Console. Monitoring failed logins provides critical indicators such as source IP addresses that can be used for **threat correlation** and proactive blocking, reducing the time to detect and respond to **account compromise** attempts.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for **failed console authentication attempts**. By directing **ActionTrail** logs to SLS with alert rules, repeated login failures are detected promptly, enabling early identification of brute-force or credential stuffing attacks against the Management Console.", + "Risk": "Without monitoring for **failed console authentication**, brute-force and credential stuffing attacks may go undetected, increasing the risk of **unauthorized access**. Failed login monitoring provides source IP indicators for **threat correlation** and proactive blocking, reducing time to detect and respond to **account compromise** attempts.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.metadata.json index f67c4faf3c..4d8e962d8b 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for console logins that are not protected by **multi-factor authentication (MFA)**. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that any single-factor console sign-in events are detected, helping identify accounts that bypass MFA enforcement policies.", - "Risk": "Without monitoring for **single-factor console logins**, accounts that are not protected by MFA may go unnoticed. This increases the risk of **unauthorized access** through compromised credentials, as passwords alone are insufficient to prevent account takeover. Failure to enforce and monitor MFA compliance weakens the overall **authentication posture** and may lead to **privilege escalation** or **data breaches** if an attacker gains access to an unprotected account.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for console logins not protected by **MFA**. By directing **ActionTrail** logs to SLS with alert rules, single-factor console sign-in events are detected, helping identify accounts that bypass MFA enforcement policies.", + "Risk": "Without monitoring for **single-factor logins**, accounts not protected by MFA may go unnoticed, increasing risk of **unauthorized access** through compromised credentials. Failure to monitor MFA compliance weakens **authentication posture** and may lead to **privilege escalation** or **data breaches** if an attacker accesses an unprotected account.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.metadata.json index df8c4abfa0..7f73a78ab5 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for changes to **OSS bucket policies**. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that any modifications to bucket access policies are detected promptly, enabling quick identification of potentially dangerous permission changes on sensitive storage resources.", - "Risk": "Without monitoring for **OSS bucket policy changes**, permissive or malicious policy modifications may go undetected. This could lead to **unintended data exposure**, allowing unauthorized users to access, modify, or delete sensitive objects stored in OSS buckets. Delayed detection of policy changes increases the risk of **data breaches**, **data exfiltration**, and **compliance violations**, as attackers could silently widen access to critical storage resources.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for **OSS bucket policy** changes. By directing **ActionTrail** logs to SLS with alert rules, modifications to bucket access policies are detected promptly, enabling quick identification of dangerous permission changes on sensitive storage resources.", + "Risk": "Without monitoring for **OSS bucket policy changes**, malicious modifications may go undetected, leading to **unintended data exposure**. Unauthorized users could access or delete sensitive objects. Delayed detection increases risk of **data breaches**, **data exfiltration**, and **compliance violations** as attackers silently widen access to storage resources.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.metadata.json index b6ec2c6aa1..88c8294844 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a metric filter and alarm configured for **OSS Bucket RAM** permission changes. By directing **ActionTrail** logs to SLS and establishing corresponding alert rules, real-time monitoring of OSS permission modifications can be achieved. This ensures that changes to bucket-level access controls and RAM policies affecting OSS resources are promptly detected and reviewed.", - "Risk": "Without monitoring for **OSS permission changes**, unauthorized modifications to bucket access controls may go undetected. This could allow attackers to grant themselves or others **unauthorized access** to sensitive objects stored in OSS buckets. Delayed detection of permission changes increases the risk of **data exfiltration**, **data tampering**, and **compliance violations**, as the confidentiality and integrity of stored data may be compromised.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for **OSS Bucket RAM** permission changes. By directing **ActionTrail** logs to SLS with alert rules, OSS permission modifications are monitored in real time. This ensures changes to bucket access controls and RAM policies affecting OSS are promptly detected.", + "Risk": "Without monitoring for **OSS permission changes**, unauthorized modifications to bucket access controls may go undetected, allowing attackers **unauthorized access** to sensitive objects. Delayed detection increases risk of **data exfiltration**, **data tampering**, and **compliance violations**, compromising confidentiality and integrity of stored data.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.metadata.json index 448cfa58c2..ea1e139646 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for **RAM Role** creation, deletion, and updating activities. By directing **ActionTrail** logs to SLS and establishing corresponding alert rules, real-time monitoring of role changes can be achieved. This ensures that any modifications to RAM roles are detected promptly, enabling early identification of unauthorized privilege escalation attempts.", - "Risk": "Without monitoring for **RAM role changes**, unauthorized creation, modification, or deletion of roles may go undetected. This could lead to **privilege escalation**, where an attacker creates or modifies roles to gain elevated access to cloud resources. Undetected role changes compromise the **integrity** of the identity and access management framework and may result in **unauthorized access** to sensitive data and services across the entire cloud environment.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for **RAM Role** creation, deletion, and update activities. By directing **ActionTrail** logs to SLS with alert rules, role changes are monitored in real time. This ensures RAM role modifications are detected promptly, enabling early identification of unauthorized privilege escalation.", + "Risk": "Without monitoring for **RAM role changes**, unauthorized creation, modification, or deletion of roles may go undetected, enabling **privilege escalation** where attackers gain elevated access. Undetected role changes compromise IAM **integrity** and may result in **unauthorized access** to sensitive data and services across the cloud environment.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.metadata.json index 202a24b167..dd58567318 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a metric filter and alarm configured for **RDS Instance** configuration changes. By directing **ActionTrail** logs to SLS and establishing corresponding alert rules, real-time monitoring of database configuration modifications can be achieved. This ensures that any changes to RDS instance settings, such as security parameters, network configurations, or access controls, are promptly detected and reviewed.", - "Risk": "Without monitoring for **RDS Instance configuration changes**, unauthorized or accidental modifications to database servers may go undetected. This could lead to **security misconfigurations** such as enabling public access, disabling encryption, or weakening authentication settings. Delayed detection of configuration changes increases the risk of **data breaches**, **unauthorized database access**, and **service disruption**, potentially compromising the **confidentiality** and **integrity** of stored data.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for **RDS Instance** configuration changes. By directing **ActionTrail** logs to SLS with alert rules, database configuration modifications are monitored in real time. This ensures changes to security parameters, network settings, or access controls are promptly detected.", + "Risk": "Without monitoring for **RDS configuration changes**, unauthorized modifications may go undetected, leading to **security misconfigurations** such as enabling public access or disabling encryption. Delayed detection increases risk of **data breaches**, **unauthorized database access**, and **service disruption**, compromising **confidentiality** and **integrity** of stored data.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.metadata.json index ff8de28cd6..ddb716a2cf 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for **root account login** attempts. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that any usage of the fully privileged root account is detected promptly, supporting the principle of least privilege and enabling timely review of root-level operations.", - "Risk": "Without monitoring for **root account usage**, activities performed by the most privileged account may go unnoticed. The root account has unrestricted access to all resources and services, making it a high-value target for attackers. Failure to detect unauthorized root account usage increases the risk of **complete account takeover**, **data destruction**, and **irreversible configuration changes** that could compromise the **confidentiality**, **integrity**, and **availability** of all cloud resources.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for **root account login** attempts. By directing **ActionTrail** logs to SLS with alert rules, usage of the fully privileged root account is detected promptly, supporting least privilege and enabling timely review of root-level operations.", + "Risk": "Without monitoring for **root account usage**, activities by the most privileged account may go unnoticed. The root account has unrestricted access to all resources, making it a high-value target. Failure to detect unauthorized usage increases risk of **account takeover**, **data destruction**, and **irreversible changes** compromising **confidentiality**, **integrity**, and **availability**.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.metadata.json index b80e7da84b..1cbf78d7c9 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for changes to **Security Groups**. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. **Security Groups** are stateful packet filters that control ingress and egress traffic within a VPC, and monitoring their changes ensures that network access modifications are promptly detected and reviewed.", - "Risk": "Without monitoring for **security group changes**, unauthorized modifications to network access controls may go undetected. This could lead to resources and services being **unintentionally exposed** to the internet or untrusted networks. Unauthorized security group modifications increase the risk of **network exposure**, **unauthorized access**, and **lateral movement** within the cloud environment, potentially compromising the **confidentiality** and **availability** of critical services.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for **Security Group** changes. By directing **ActionTrail** logs to SLS with alert rules, real-time monitoring is achieved. **Security Groups** are stateful packet filters controlling VPC ingress and egress traffic; monitoring their changes ensures network access modifications are promptly detected.", + "Risk": "Without monitoring for **security group changes**, unauthorized modifications to network access controls may go undetected, leading to resources being **exposed** to untrusted networks. This increases risk of **network exposure**, **unauthorized access**, and **lateral movement**, potentially compromising **confidentiality** and **availability** of critical services.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.metadata.json index fb2d54f3c0..de9b3e4e0f 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for **unauthorized API calls**. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that any API calls resulting in unauthorized errors are detected promptly, helping identify misconfigured applications, compromised credentials, or active reconnaissance by attackers.", - "Risk": "Without monitoring for **unauthorized API calls**, patterns of failed access attempts may go undetected. These patterns often indicate **malicious activity** such as attackers probing for permissions or attempting to exploit misconfigured access controls. Failure to detect unauthorized API calls in a timely manner increases the risk of **security breaches**, as it delays identification of compromised credentials and **privilege escalation** attempts across cloud services.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for **unauthorized API calls**. By directing **ActionTrail** logs to SLS with alert rules, API calls resulting in unauthorized errors are detected promptly, helping identify misconfigured applications, compromised credentials, or active attacker reconnaissance.", + "Risk": "Without monitoring for **unauthorized API calls**, failed access patterns may go undetected. These often indicate **malicious activity** such as permission probing or exploiting misconfigured access controls. Delayed detection increases risk of **security breaches** by delaying identification of compromised credentials and **privilege escalation** attempts.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.metadata.json index fe091fd369..85e0860554 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a log search/analysis query and alarm configured for **VPC** changes. By directing **ActionTrail** logs to SLS and establishing corresponding alert rules, real-time monitoring of VPC modifications can be achieved. This ensures that any creation, deletion, or modification of VPCs and their associated components is promptly detected and reviewed.", - "Risk": "Without monitoring for **VPC changes**, unauthorized or accidental modifications to virtual network infrastructure may go undetected. This could disrupt **network connectivity**, create **security vulnerabilities**, or expose internal resources to untrusted networks. Unauthorized VPC modifications increase the risk of **service disruption**, **data interception**, and **lateral movement** within the cloud environment, potentially compromising the **confidentiality** and **availability** of all connected resources.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for **VPC** changes. By directing **ActionTrail** logs to SLS with alert rules, VPC modifications are monitored in real time. This ensures creation, deletion, or modification of VPCs and associated components is promptly detected.", + "Risk": "Without monitoring for **VPC changes**, unauthorized modifications to network infrastructure may go undetected, disrupting **connectivity**, creating **vulnerabilities**, or exposing internal resources. This increases risk of **service disruption**, **data interception**, and **lateral movement**, compromising **confidentiality** and **availability** of connected resources.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.metadata.json index 626ab7becf..7067f096a6 100644 --- a/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.metadata.json +++ b/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.metadata.json @@ -9,8 +9,8 @@ "Severity": "medium", "ResourceType": "ALIYUN::SLS::Alert", "ResourceGroup": "monitoring", - "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a metric filter and alarm configured for **VPC network route** changes. By directing **ActionTrail** logs to SLS and establishing corresponding alert rules, real-time monitoring of route table modifications can be achieved. This ensures that any creation, deletion, or modification of route entries is promptly detected, helping verify that all VPC traffic flows through the expected network paths.", - "Risk": "Without monitoring for **route table changes**, unauthorized modifications to network routes may go undetected. This could allow attackers to redirect traffic through **malicious intermediaries** for interception or manipulation. Undetected route changes increase the risk of **man-in-the-middle attacks**, **data exfiltration**, and **service disruption**, as traffic may be silently diverted away from intended destinations, compromising the **confidentiality** and **integrity** of data in transit.", + "Description": "**Alibaba Cloud SLS** should have an alarm configured for **VPC network route** changes. By directing **ActionTrail** logs to SLS with alert rules, route table modifications are monitored in real time. This ensures creation, deletion, or modification of route entries is detected, verifying VPC traffic flows through expected paths.", + "Risk": "Without monitoring for **route table changes**, unauthorized route modifications may go undetected, allowing attackers to redirect traffic through **malicious intermediaries**. This increases risk of **man-in-the-middle attacks**, **data exfiltration**, and **service disruption** as traffic is diverted from intended destinations, compromising **confidentiality** and **integrity**.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", diff --git a/prowler/providers/aws/config/check_types.json b/prowler/providers/aws/config/check_types.json new file mode 100644 index 0000000000..ce43c32e23 --- /dev/null +++ b/prowler/providers/aws/config/check_types.json @@ -0,0 +1,88 @@ +{ + "Software and Configuration Checks": { + "Vulnerabilities": { + "CVE": {} + }, + "AWS Security Best Practices": { + "Network Reachability": {}, + "Network Security": {}, + "Runtime Behavior Analysis": {}, + "Data Encryption": {}, + "Encryption at Rest": {}, + "Encryption in Transit": {} + }, + "Industry and Regulatory Standards": { + "AWS Foundational Security Best Practices": {}, + "CIS Host Hardening Benchmarks": {}, + "CIS AWS Foundations Benchmark": {}, + "PCI-DSS": {}, + "Cloud Security Alliance Controls": {}, + "ISO 90001 Controls": {}, + "ISO 27001 Controls": {}, + "ISO 27017 Controls": {}, + "ISO 27018 Controls": {}, + "SOC 1": {}, + "SOC 2": {}, + "HIPAA Controls (USA)": {}, + "NIST 800-53 Controls": {}, + "NIST 800-53 Controls (USA)": {}, + "NIST CSF Controls (USA)": {}, + "IRAP Controls (Australia)": {}, + "K-ISMS Controls (Korea)": {}, + "MTCS Controls (Singapore)": {}, + "FISC Controls (Japan)": {}, + "My Number Act Controls (Japan)": {}, + "ENS Controls (Spain)": {}, + "Cyber Essentials Plus Controls (UK)": {}, + "G-Cloud Controls (UK)": {}, + "C5 Controls (Germany)": {}, + "IT-Grundschutz Controls (Germany)": {}, + "GDPR Controls (Europe)": {}, + "TISAX Controls (Europe)": {} + }, + "Patch Management": {} + }, + "TTPs": { + "Initial Access": { + "Unauthorized Access": {}, + "External Remote Services": {}, + "Valid Accounts": {} + }, + "Execution": {}, + "Persistence": {}, + "Privilege Escalation": {}, + "Defense Evasion": {}, + "Credential Access": {}, + "Discovery": {}, + "Lateral Movement": {}, + "Collection": {}, + "Command and Control": {} + }, + "Effects": { + "Data Exposure": {}, + "Data Exfiltration": {}, + "Data Destruction": {}, + "Denial of Service": {}, + "Resource Consumption": {} + }, + "Unusual Behaviors": { + "Application": {}, + "Network Flow": {}, + "IP address": {}, + "User": {}, + "VM": {}, + "Container": {}, + "Serverless": {}, + "Process": {}, + "Database": {}, + "Data": {} + }, + "Sensitive Data Identifications": { + "PII": {}, + "Passwords": {}, + "Legal": {}, + "Financial": {}, + "Security": {}, + "Business": {} + } +} diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json index 20d7b45a6c..3e671f496d 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_logs_s3_bucket_is_not_publicly_accessible/cloudtrail_logs_s3_bucket_is_not_publicly_accessible.metadata.json @@ -4,8 +4,8 @@ "CheckTitle": "CloudTrail trail S3 bucket is not publicly accessible", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", - "Industry and Regulatory Standards/CIS AWS Foundations Benchmark", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark", "Effects/Data Exposure" ], "ServiceName": "cloudtrail", diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_alarm_actions_enabled/cloudwatch_alarm_actions_enabled.metadata.json b/prowler/providers/aws/services/cloudwatch/cloudwatch_alarm_actions_enabled/cloudwatch_alarm_actions_enabled.metadata.json index 26d1f8114a..f4faec42a1 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_alarm_actions_enabled/cloudwatch_alarm_actions_enabled.metadata.json +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_alarm_actions_enabled/cloudwatch_alarm_actions_enabled.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "CloudWatch metric alarm has actions enabled", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "TTPs/Defense Evasion" ], "ServiceName": "cloudwatch", diff --git a/prowler/providers/aws/services/codepipeline/codepipeline_project_repo_private/codepipeline_project_repo_private.metadata.json b/prowler/providers/aws/services/codepipeline/codepipeline_project_repo_private/codepipeline_project_repo_private.metadata.json index 07ba280f90..d718d125a9 100644 --- a/prowler/providers/aws/services/codepipeline/codepipeline_project_repo_private/codepipeline_project_repo_private.metadata.json +++ b/prowler/providers/aws/services/codepipeline/codepipeline_project_repo_private/codepipeline_project_repo_private.metadata.json @@ -12,7 +12,7 @@ "ResourceType": "AwsCodePipelinePipeline", "ResourceGroup": "devops", "Description": "**CodePipeline pipeline** should configure its **source stage** to use a **private repository** with authenticated connection rather than a public GitHub or GitLab repository. This ensures deployment configurations, build artifacts, and CI/CD logic remain protected from unauthorized access.", - "Risk": "Using **public repositories** as pipeline sources exposes deployment configurations, infrastructure code, and CI/CD workflows to anyone on the internet. \n\nThis increases the risk of **supply chain attacks**, **credential exposure**, and **intellectual property theft**. Adversaries can study deployment patterns, identify security gaps, inject malicious code, or leverage exposed secrets to compromise **confidentiality**, **integrity**, and **availability** of production systems.", + "Risk": "Using **public repositories** as pipeline sources exposes deployment configurations and CI/CD workflows to the internet, increasing risk of **supply chain attacks**, **credential exposure**, and **intellectual property theft**. Adversaries can inject malicious code or leverage exposed secrets to compromise production systems.", "RelatedUrl": "", "AdditionalURLs": [ "https://docs.aws.amazon.com/codepipeline/latest/userguide/welcome.html", @@ -31,8 +31,8 @@ } }, "Categories": [ - "supply-chain-security", - "secrets-management" + "software-supply-chain", + "secrets" ], "DependsOn": [], "RelatedTo": [], diff --git a/prowler/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached.metadata.json b/prowler/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached.metadata.json index 24608af13b..4a4b5dff6d 100644 --- a/prowler/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached.metadata.json +++ b/prowler/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "Amazon Cognito user pool is associated with a WAF Web ACL", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "Effects/Denial of Service" ], "ServiceName": "cognito", diff --git a/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.metadata.json b/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.metadata.json index a022b44f63..4f585e358e 100644 --- a/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.metadata.json +++ b/prowler/providers/aws/services/directoryservice/directoryservice_supported_mfa_radius_enabled/directoryservice_supported_mfa_radius_enabled.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "AWS Directory Service directory has RADIUS-based MFA enabled", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "TTPs/Initial Access", "TTPs/Credential Access" ], diff --git a/prowler/providers/aws/services/documentdb/documentdb_cluster_backup_enabled/documentdb_cluster_backup_enabled.metadata.json b/prowler/providers/aws/services/documentdb/documentdb_cluster_backup_enabled/documentdb_cluster_backup_enabled.metadata.json index d5c0c00cbf..03b49b2801 100644 --- a/prowler/providers/aws/services/documentdb/documentdb_cluster_backup_enabled/documentdb_cluster_backup_enabled.metadata.json +++ b/prowler/providers/aws/services/documentdb/documentdb_cluster_backup_enabled/documentdb_cluster_backup_enabled.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "DocumentDB cluster has automated backups enabled with retention period of at least 7 days", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "Effects/Data Destruction" ], "ServiceName": "documentdb", diff --git a/prowler/providers/aws/services/ec2/ec2_instance_port_mysql_exposed_to_internet/ec2_instance_port_mysql_exposed_to_internet.metadata.json b/prowler/providers/aws/services/ec2/ec2_instance_port_mysql_exposed_to_internet/ec2_instance_port_mysql_exposed_to_internet.metadata.json index 4d0e38cad9..5bb5e44bc3 100644 --- a/prowler/providers/aws/services/ec2/ec2_instance_port_mysql_exposed_to_internet/ec2_instance_port_mysql_exposed_to_internet.metadata.json +++ b/prowler/providers/aws/services/ec2/ec2_instance_port_mysql_exposed_to_internet/ec2_instance_port_mysql_exposed_to_internet.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "EC2 instance does not allow ingress from the Internet to TCP port 3306 (MySQL)", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "TTPs/Initial Access/Unauthorized Access", "Effects/Data Exposure" ], diff --git a/prowler/providers/aws/services/ec2/ec2_networkacl_unused/ec2_networkacl_unused.metadata.json b/prowler/providers/aws/services/ec2/ec2_networkacl_unused/ec2_networkacl_unused.metadata.json index 6f6c696085..13aec73860 100644 --- a/prowler/providers/aws/services/ec2/ec2_networkacl_unused/ec2_networkacl_unused.metadata.json +++ b/prowler/providers/aws/services/ec2/ec2_networkacl_unused/ec2_networkacl_unused.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "Non-default network ACL is associated with a subnet", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices" + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices" ], "ServiceName": "ec2", "SubServiceName": "", diff --git a/prowler/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet.metadata.json b/prowler/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet.metadata.json index fd03b1ed54..297649291f 100644 --- a/prowler/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet.metadata.json +++ b/prowler/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "ElastiCache cluster is not using public subnets", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "Effects/Data Exposure" ], "ServiceName": "elasticache", diff --git a/prowler/providers/aws/services/eventbridge/eventbridge_bus_cross_account_access/eventbridge_bus_cross_account_access.metadata.json b/prowler/providers/aws/services/eventbridge/eventbridge_bus_cross_account_access/eventbridge_bus_cross_account_access.metadata.json index 5628f6ec62..2b2ae8e87c 100644 --- a/prowler/providers/aws/services/eventbridge/eventbridge_bus_cross_account_access/eventbridge_bus_cross_account_access.metadata.json +++ b/prowler/providers/aws/services/eventbridge/eventbridge_bus_cross_account_access/eventbridge_bus_cross_account_access.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "AWS EventBridge event bus does not allow cross-account access", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "TTPs/Initial Access/Unauthorized Access", "Effects/Data Exposure" ], diff --git a/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.metadata.json b/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.metadata.json index b08114efa8..6ebfa056ad 100644 --- a/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.metadata.json +++ b/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.metadata.json @@ -13,7 +13,7 @@ "ResourceType": "AwsGuardDutyDetector", "ResourceGroup": "security", "Description": "**Amazon GuardDuty** has a delegated administrator configured at the organization level, detectors are enabled in all opted-in regions, and organization auto-enable is active for new member accounts.", - "Risk": "Without org-wide **Amazon GuardDuty** configuration, gaps can occur where **GuardDuty** may be enabled in some regions but not others. Delegated admin may not be set consistently, and new accounts may not be automatically enrolled. This fragments **threat visibility**, delays **incident response**, and allows adversaries to exploit unmonitored regions or accounts for **lateral movement**, **persistence**, and **data exfiltration**.", + "Risk": "Without org-wide **Amazon GuardDuty** configuration, gaps can occur where detectors are enabled in some regions but not others, delegated admin is inconsistent, and new accounts are not auto-enrolled. This fragments **threat visibility**, delays **incident response**, and allows adversaries to exploit unmonitored regions or accounts for **lateral movement** and **data exfiltration**.", "RelatedUrl": "", "AdditionalURLs": [ "https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_organizations.html", diff --git a/prowler/providers/aws/services/iam/iam_aws_attached_policy_no_administrative_privileges/iam_aws_attached_policy_no_administrative_privileges.metadata.json b/prowler/providers/aws/services/iam/iam_aws_attached_policy_no_administrative_privileges/iam_aws_attached_policy_no_administrative_privileges.metadata.json index 9c1b95c551..6bad6e8b0e 100644 --- a/prowler/providers/aws/services/iam/iam_aws_attached_policy_no_administrative_privileges/iam_aws_attached_policy_no_administrative_privileges.metadata.json +++ b/prowler/providers/aws/services/iam/iam_aws_attached_policy_no_administrative_privileges/iam_aws_attached_policy_no_administrative_privileges.metadata.json @@ -4,8 +4,8 @@ "CheckTitle": "Attached AWS-managed IAM policy does not allow '*:*' administrative privileges", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", - "Industry and Regulatory Standards/CIS AWS Foundations Benchmark", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark", "TTPs/Privilege Escalation" ], "ServiceName": "iam", diff --git a/prowler/providers/aws/services/iam/iam_inline_policy_no_full_access_to_kms/iam_inline_policy_no_full_access_to_kms.metadata.json b/prowler/providers/aws/services/iam/iam_inline_policy_no_full_access_to_kms/iam_inline_policy_no_full_access_to_kms.metadata.json index 36b2875172..b49eb0a914 100644 --- a/prowler/providers/aws/services/iam/iam_inline_policy_no_full_access_to_kms/iam_inline_policy_no_full_access_to_kms.metadata.json +++ b/prowler/providers/aws/services/iam/iam_inline_policy_no_full_access_to_kms/iam_inline_policy_no_full_access_to_kms.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "Inline IAM policy does not allow kms:* privileges", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "TTPs/Privilege Escalation", "Effects/Data Exposure" ], diff --git a/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.metadata.json b/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.metadata.json index 9bf82258ab..e7fcf72387 100644 --- a/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.metadata.json +++ b/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "IAM user does not use long-lived credentials to access services other than IAM or STS", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Runtime Behavior Analysis", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "TTPs/Credential Access" ], "ServiceName": "iam", diff --git a/prowler/providers/aws/services/inspector2/inspector2_active_findings_exist/inspector2_active_findings_exist.metadata.json b/prowler/providers/aws/services/inspector2/inspector2_active_findings_exist/inspector2_active_findings_exist.metadata.json index 66785d6c32..c3efa80e97 100644 --- a/prowler/providers/aws/services/inspector2/inspector2_active_findings_exist/inspector2_active_findings_exist.metadata.json +++ b/prowler/providers/aws/services/inspector2/inspector2_active_findings_exist/inspector2_active_findings_exist.metadata.json @@ -9,7 +9,7 @@ "Software and Configuration Checks/Vulnerabilities/CVE", "Software and Configuration Checks/Patch Management", "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices" + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices" ], "ServiceName": "inspector2", "SubServiceName": "", diff --git a/prowler/providers/aws/services/kafka/kafka_cluster_encryption_at_rest_uses_cmk/kafka_cluster_encryption_at_rest_uses_cmk.metadata.json b/prowler/providers/aws/services/kafka/kafka_cluster_encryption_at_rest_uses_cmk/kafka_cluster_encryption_at_rest_uses_cmk.metadata.json index 2d28190572..bc8313049e 100644 --- a/prowler/providers/aws/services/kafka/kafka_cluster_encryption_at_rest_uses_cmk/kafka_cluster_encryption_at_rest_uses_cmk.metadata.json +++ b/prowler/providers/aws/services/kafka/kafka_cluster_encryption_at_rest_uses_cmk/kafka_cluster_encryption_at_rest_uses_cmk.metadata.json @@ -4,9 +4,9 @@ "CheckTitle": "Kafka cluster has encryption at rest enabled with a customer managed key (CMK) or is serverless", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Data Encryption", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", - "Industry and Regulatory Standards/NIST 800-53 Controls (USA)", - "Industry and Regulatory Standards/PCI-DSS", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls (USA)", + "Software and Configuration Checks/Industry and Regulatory Standards/PCI-DSS", "Effects/Data Exposure" ], "ServiceName": "kafka", diff --git a/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.metadata.json b/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.metadata.json index 2beb86eb11..5bbb4b0750 100644 --- a/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.metadata.json +++ b/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "Cloud KMS key does not grant access to allUsers or allAuthenticatedUsers", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "TTPs/Initial Access/Unauthorized Access", "Effects/Data Exposure" ], diff --git a/prowler/providers/aws/services/neptune/neptune_cluster_deletion_protection/neptune_cluster_deletion_protection.metadata.json b/prowler/providers/aws/services/neptune/neptune_cluster_deletion_protection/neptune_cluster_deletion_protection.metadata.json index dcfd7730b1..cce180b583 100644 --- a/prowler/providers/aws/services/neptune/neptune_cluster_deletion_protection/neptune_cluster_deletion_protection.metadata.json +++ b/prowler/providers/aws/services/neptune/neptune_cluster_deletion_protection/neptune_cluster_deletion_protection.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "Neptune cluster has deletion protection enabled", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "Effects/Data Destruction" ], "ServiceName": "neptune", diff --git a/prowler/providers/aws/services/rds/rds_cluster_multi_az/rds_cluster_multi_az.metadata.json b/prowler/providers/aws/services/rds/rds_cluster_multi_az/rds_cluster_multi_az.metadata.json index 78a4e0c0cd..c2d4c654c4 100644 --- a/prowler/providers/aws/services/rds/rds_cluster_multi_az/rds_cluster_multi_az.metadata.json +++ b/prowler/providers/aws/services/rds/rds_cluster_multi_az/rds_cluster_multi_az.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "RDS cluster has Multi-AZ enabled", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices" + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices" ], "ServiceName": "rds", "SubServiceName": "", diff --git a/prowler/providers/aws/services/s3/s3_bucket_object_versioning/s3_bucket_object_versioning.metadata.json b/prowler/providers/aws/services/s3/s3_bucket_object_versioning/s3_bucket_object_versioning.metadata.json index 0b5b42e789..f80f847545 100644 --- a/prowler/providers/aws/services/s3/s3_bucket_object_versioning/s3_bucket_object_versioning.metadata.json +++ b/prowler/providers/aws/services/s3/s3_bucket_object_versioning/s3_bucket_object_versioning.metadata.json @@ -4,8 +4,8 @@ "CheckTitle": "S3 bucket has object versioning enabled", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", - "Industry and Regulatory Standards/CIS AWS Foundations Benchmark", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark", "Effects/Data Destruction" ], "ServiceName": "s3", diff --git a/prowler/providers/aws/services/sagemaker/sagemaker_training_jobs_intercontainer_encryption_enabled/sagemaker_training_jobs_intercontainer_encryption_enabled.metadata.json b/prowler/providers/aws/services/sagemaker/sagemaker_training_jobs_intercontainer_encryption_enabled/sagemaker_training_jobs_intercontainer_encryption_enabled.metadata.json index db16d83a2b..5069c18fef 100644 --- a/prowler/providers/aws/services/sagemaker/sagemaker_training_jobs_intercontainer_encryption_enabled/sagemaker_training_jobs_intercontainer_encryption_enabled.metadata.json +++ b/prowler/providers/aws/services/sagemaker/sagemaker_training_jobs_intercontainer_encryption_enabled/sagemaker_training_jobs_intercontainer_encryption_enabled.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "Amazon SageMaker training job has inter-container traffic encryption enabled", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Security", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "Effects/Data Exposure" ], "ServiceName": "sagemaker", diff --git a/prowler/providers/aws/services/shield/shield_advanced_protection_in_associated_elastic_ips/shield_advanced_protection_in_associated_elastic_ips.metadata.json b/prowler/providers/aws/services/shield/shield_advanced_protection_in_associated_elastic_ips/shield_advanced_protection_in_associated_elastic_ips.metadata.json index 31720c530c..c365bf17f0 100644 --- a/prowler/providers/aws/services/shield/shield_advanced_protection_in_associated_elastic_ips/shield_advanced_protection_in_associated_elastic_ips.metadata.json +++ b/prowler/providers/aws/services/shield/shield_advanced_protection_in_associated_elastic_ips/shield_advanced_protection_in_associated_elastic_ips.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "Elastic IP address is protected by AWS Shield Advanced", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/Network Reachability", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "Effects/Denial of Service" ], "ServiceName": "shield", diff --git a/prowler/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.metadata.json b/prowler/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.metadata.json index 4e6d7b833b..46d7fd2109 100644 --- a/prowler/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.metadata.json +++ b/prowler/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "SQS queue has server-side encryption enabled", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "Effects/Data Exposure" ], "ServiceName": "sqs", diff --git a/prowler/providers/aws/services/vpc/vpc_endpoint_connections_trust_boundaries/vpc_endpoint_connections_trust_boundaries.metadata.json b/prowler/providers/aws/services/vpc/vpc_endpoint_connections_trust_boundaries/vpc_endpoint_connections_trust_boundaries.metadata.json index 9460f24bc1..5e76f071fa 100644 --- a/prowler/providers/aws/services/vpc/vpc_endpoint_connections_trust_boundaries/vpc_endpoint_connections_trust_boundaries.metadata.json +++ b/prowler/providers/aws/services/vpc/vpc_endpoint_connections_trust_boundaries/vpc_endpoint_connections_trust_boundaries.metadata.json @@ -4,7 +4,7 @@ "CheckTitle": "VPC endpoint policy allows access only from trusted AWS accounts", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices", - "Industry and Regulatory Standards/AWS Foundational Security Best Practices", + "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices", "TTPs/Initial Access" ], "ServiceName": "vpc", diff --git a/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_management_api/entra_conditional_access_policy_require_mfa_for_management_api.metadata.json b/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_management_api/entra_conditional_access_policy_require_mfa_for_management_api.metadata.json index c26c982256..5d0d2cacb7 100644 --- a/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_management_api/entra_conditional_access_policy_require_mfa_for_management_api.metadata.json +++ b/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_management_api/entra_conditional_access_policy_require_mfa_for_management_api.metadata.json @@ -1,7 +1,7 @@ { "Provider": "azure", "CheckID": "entra_conditional_access_policy_require_mfa_for_management_api", - "CheckTitle": "Ensure Multifactor Authentication is Required for Windows Azure Service Management API", + "CheckTitle": "Multifactor Authentication is required for Windows Azure Service Management API", "CheckType": [], "ServiceName": "entra", "SubServiceName": "", @@ -11,7 +11,7 @@ "ResourceGroup": "IAM", "Description": "This recommendation ensures that users accessing the Windows Azure Service Management API (i.e. Azure Powershell, Azure CLI, Azure Resource Manager API, etc.) are required to use multifactor authentication (MFA) credentials when accessing resources through the Windows Azure Service Management API.", "Risk": "Administrative access to the Windows Azure Service Management API should be secured with a higher level of scrutiny to authenticating mechanisms. Enabling multifactor authentication is recommended to reduce the potential for abuse of Administrative actions, and to prevent intruders or compromised admin credentials from changing administrative settings.", - "RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-conditional-access-policy-azure-management", + "RelatedUrl": "", "Remediation": { "Code": { "CLI": "", @@ -21,7 +21,7 @@ }, "Recommendation": { "Text": "1. From the Azure Admin Portal dashboard, open Microsoft Entra ID. 2. Click Security in the Entra ID blade. 3. Click Conditional Access in the Security blade. 4. Click Policies in the Conditional Access blade. 5. Click + New policy. 6. Enter a name for the policy. 7. Click the blue text under Users. 8. Under Include, select All users. 9. Under Exclude, check Users and groups. 10. Select users or groups to be exempted from this policy (e.g. break-glass emergency accounts, and non-interactive service accounts) then click the Select button. 11. Click the blue text under Target Resources. 12. Under Include, click the Select apps radio button. 13. Click the blue text under Select. 14. Check the box next to Windows Azure Service Management APIs then click the Select button. 15. Click the blue text under Grant. 16. Under Grant access check the box for Require multifactor authentication then click the Select button. 17. Before creating, set Enable policy to Report-only. 18. Click Create. After testing the policy in report-only mode, update the Enable policy setting from Report-only to On.", - "Url": "https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-cloud-apps" + "Url": "https://hub.prowler.com/check/entra_conditional_access_policy_require_mfa_for_management_api" } }, "Categories": [ diff --git a/prowler/providers/cloudflare/services/dns/dns_record_cname_target_valid/dns_record_cname_target_valid.metadata.json b/prowler/providers/cloudflare/services/dns/dns_record_cname_target_valid/dns_record_cname_target_valid.metadata.json index ff043c2122..e17c3b7308 100644 --- a/prowler/providers/cloudflare/services/dns/dns_record_cname_target_valid/dns_record_cname_target_valid.metadata.json +++ b/prowler/providers/cloudflare/services/dns/dns_record_cname_target_valid/dns_record_cname_target_valid.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "DNSRecord", "ResourceGroup": "network", "Description": "**Cloudflare DNS records** (CNAME, MX, NS, SRV) that point to hostnames are assessed for **dangling record** vulnerabilities by checking if the target domain resolves to a valid address, preventing **subdomain takeover**, **mail interception**, and **service hijacking** attacks.", - "Risk": "Dangling **DNS records** pointing to non-existent targets create multiple vulnerabilities.\n- **Confidentiality**: dangling CNAME/NS allows subdomain takeover; dangling MX allows mail interception\n- **Integrity**: attackers can impersonate your organization, intercept emails, or hijack services\n- **Availability**: legitimate services may be disrupted or redirected to attacker-controlled infrastructure", + "Risk": "Dangling **DNS records** pointing to non-existent targets create multiple vulnerabilities. Dangling CNAME/NS allows **subdomain takeover**; dangling MX allows **mail interception**. Attackers can impersonate your organization, hijack services, or redirect legitimate traffic to attacker-controlled infrastructure.", "RelatedUrl": "", "AdditionalURLs": [ "https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/" diff --git a/prowler/providers/cloudflare/services/zone/zone_record_caa_exists/zone_record_caa_exists.metadata.json b/prowler/providers/cloudflare/services/zone/zone_record_caa_exists/zone_record_caa_exists.metadata.json index 07e9181831..b7568badb8 100644 --- a/prowler/providers/cloudflare/services/zone/zone_record_caa_exists/zone_record_caa_exists.metadata.json +++ b/prowler/providers/cloudflare/services/zone/zone_record_caa_exists/zone_record_caa_exists.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "Zone", "ResourceGroup": "network", "Description": "**Cloudflare zones** are assessed for **CAA (Certificate Authority Authorization)** DNS records by checking if they exist with **`issue` or `issuewild` tags** that specify which **certificate authorities** are permitted to issue SSL/TLS certificates for the domain.", - "Risk": "Without **CAA** records or without `issue`/`issuewild` tags, any certificate authority can issue certificates for your domain.\n- **Confidentiality**: unauthorized certificates enable man-in-the-middle attacks\n- **Integrity**: attackers can impersonate your domain with fraudulently obtained certificates\n- **Trust**: CA compromise or social engineering can result in unauthorized certificate issuance\n- **Missing tags**: CAA records without `issue` or `issuewild` tags (e.g., only `iodef`) do not restrict certificate issuance", + "Risk": "Without **CAA** records or `issue`/`issuewild` tags, any certificate authority can issue certificates for your domain. Unauthorized certificates enable **man-in-the-middle attacks** and domain impersonation. CAA records with only `iodef` tags do not restrict certificate issuance.", "RelatedUrl": "", "AdditionalURLs": [ "https://developers.cloudflare.com/ssl/edge-certificates/caa-records/" diff --git a/prowler/providers/cloudflare/services/zone/zone_record_dkim_exists/zone_record_dkim_exists.metadata.json b/prowler/providers/cloudflare/services/zone/zone_record_dkim_exists/zone_record_dkim_exists.metadata.json index a8811febbf..aaf204e29f 100644 --- a/prowler/providers/cloudflare/services/zone/zone_record_dkim_exists/zone_record_dkim_exists.metadata.json +++ b/prowler/providers/cloudflare/services/zone/zone_record_dkim_exists/zone_record_dkim_exists.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "Zone", "ResourceGroup": "network", "Description": "**Cloudflare zones** are assessed for **DKIM (DomainKeys Identified Mail)** records by checking if TXT records exist at `*._domainkey` subdomains containing a **cryptographically valid public key** in the `p=` parameter used to **verify email signatures**.", - "Risk": "Without **DKIM** or with a revoked/empty public key, email recipients cannot verify that messages were sent by authorized servers.\n- **Confidentiality**: attackers can forge emails appearing to come from your domain\n- **Integrity**: no cryptographic proof that email content hasn't been modified in transit\n- **Reputation**: DMARC policies relying on DKIM will fail, affecting email deliverability\n- **Revoked keys**: A DKIM record with `p=` empty (e.g., `p=;`) indicates a revoked key that cannot authenticate emails", + "Risk": "Without **DKIM** or with a revoked/empty public key, recipients cannot verify messages were sent by authorized servers. Attackers can forge emails from your domain, and content integrity cannot be proven. DMARC policies relying on DKIM will fail, affecting deliverability. A DKIM record with empty `p=` indicates a revoked key.", "RelatedUrl": "", "AdditionalURLs": [ "https://developers.cloudflare.com/dns/manage-dns-records/how-to/email-records/" diff --git a/prowler/providers/cloudflare/services/zone/zone_record_dmarc_exists/zone_record_dmarc_exists.metadata.json b/prowler/providers/cloudflare/services/zone/zone_record_dmarc_exists/zone_record_dmarc_exists.metadata.json index 2aa572da36..93ccedacfd 100644 --- a/prowler/providers/cloudflare/services/zone/zone_record_dmarc_exists/zone_record_dmarc_exists.metadata.json +++ b/prowler/providers/cloudflare/services/zone/zone_record_dmarc_exists/zone_record_dmarc_exists.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "Zone", "ResourceGroup": "network", "Description": "**Cloudflare zones** are assessed for **DMARC (Domain-based Message Authentication, Reporting, and Conformance)** records by checking if a TXT record exists at `_dmarc` subdomain with an **enforcement policy (`p=reject` or `p=quarantine`)** to actively block or quarantine spoofed emails.", - "Risk": "Without **DMARC** or with a monitoring-only policy (`p=none`), there is no active protection against email spoofing.\n- **Confidentiality**: attackers can spoof emails for phishing campaigns to steal credentials\n- **Integrity**: no visibility into email authentication failures or abuse attempts\n- **Reputation**: domain reputation damage from spoofed emails sent in your name\n- **Monitoring-only policy**: `p=none` only generates reports but does not block or quarantine spoofed emails, providing no real protection", + "Risk": "Without **DMARC** or with `p=none`, there is no active protection against email spoofing. Attackers can spoof emails for **phishing campaigns** to steal credentials. Domain reputation is damaged by spoofed emails. The `p=none` policy only generates reports but does not block or quarantine spoofed emails.", "RelatedUrl": "", "AdditionalURLs": [ "https://developers.cloudflare.com/dns/manage-dns-records/how-to/email-records/" diff --git a/prowler/providers/cloudflare/services/zone/zone_record_spf_exists/zone_record_spf_exists.metadata.json b/prowler/providers/cloudflare/services/zone/zone_record_spf_exists/zone_record_spf_exists.metadata.json index 07e2d4ee5f..d043a44136 100644 --- a/prowler/providers/cloudflare/services/zone/zone_record_spf_exists/zone_record_spf_exists.metadata.json +++ b/prowler/providers/cloudflare/services/zone/zone_record_spf_exists/zone_record_spf_exists.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "Zone", "ResourceGroup": "network", "Description": "**Cloudflare zones** are assessed for **SPF (Sender Policy Framework)** records by checking if a TXT record exists that specifies which mail servers are **authorized to send email** on behalf of the domain, and verifies that the record uses a **strict policy (`-all`)** to reject unauthorized senders.", - "Risk": "Without **SPF** or with a permissive policy (`~all`, `?all`, `+all`), attackers can forge emails appearing to come from your domain.\n- **Confidentiality**: phishing attacks can harvest sensitive information from recipients who trust spoofed emails\n- **Integrity**: brand reputation damage from fraudulent emails sent in your name\n- **Availability**: email deliverability issues as receiving servers may reject or quarantine legitimate emails\n- **Permissive policies**: `~all` (softfail) only marks emails as suspicious but does not reject them, `?all` (neutral) provides no protection", + "Risk": "Without **SPF** or with a permissive policy (`~all`, `?all`, `+all`), attackers can forge emails from your domain. Phishing attacks can harvest sensitive information from recipients who trust spoofed emails. Brand reputation is damaged by fraudulent emails. `~all` only marks emails as suspicious without rejecting them.", "RelatedUrl": "", "AdditionalURLs": [ "https://developers.cloudflare.com/dns/manage-dns-records/how-to/email-records/" diff --git a/prowler/providers/cloudflare/services/zone/zone_ssl_strict/zone_ssl_strict.metadata.json b/prowler/providers/cloudflare/services/zone/zone_ssl_strict/zone_ssl_strict.metadata.json index d236895198..8a3f2f31c2 100644 --- a/prowler/providers/cloudflare/services/zone/zone_ssl_strict/zone_ssl_strict.metadata.json +++ b/prowler/providers/cloudflare/services/zone/zone_ssl_strict/zone_ssl_strict.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "Zone", "ResourceGroup": "network", "Description": "**Cloudflare zones** are assessed for **SSL/TLS encryption mode** by checking if the mode is set to `Full (Strict)` to ensure **end-to-end encryption** with certificate validation.", - "Risk": "Without **strict SSL mode**, traffic between Cloudflare and origin may use unvalidated or unencrypted connections.\n- **Confidentiality**: sensitive data can be intercepted in transit via man-in-the-middle attacks\n- **Integrity**: responses can be modified without detection between Cloudflare and origin\n- **Compliance**: may violate PCI-DSS, HIPAA, and other regulatory requirements for encrypted transport", + "Risk": "Without **strict SSL mode**, traffic between Cloudflare and origin may use unvalidated or unencrypted connections. Sensitive data can be intercepted via **man-in-the-middle attacks**, responses can be modified without detection, and this may violate PCI-DSS, HIPAA, and other regulatory requirements.", "RelatedUrl": "", "AdditionalURLs": [ "https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/" diff --git a/prowler/providers/gcp/services/compute/compute_image_not_publicly_shared/compute_image_not_publicly_shared.metadata.json b/prowler/providers/gcp/services/compute/compute_image_not_publicly_shared/compute_image_not_publicly_shared.metadata.json index a24d47ec73..541002b8b0 100644 --- a/prowler/providers/gcp/services/compute/compute_image_not_publicly_shared/compute_image_not_publicly_shared.metadata.json +++ b/prowler/providers/gcp/services/compute/compute_image_not_publicly_shared/compute_image_not_publicly_shared.metadata.json @@ -9,7 +9,7 @@ "Severity": "medium", "ResourceType": "compute.googleapis.com/Image", "ResourceGroup": "compute", - "Description": "Custom disk images should not be shared publicly with **allAuthenticatedUsers**.\n\nNote: Per Google Cloud API restrictions, **allUsers** cannot be assigned to Compute Engine images. The security concern is **allAuthenticatedUsers**, which grants access to anyone with a Google account.\n\nPublicly shared disk images can expose application snapshots and sensitive data to anyone with a Google Cloud account, potentially leading to unauthorized access and data breaches.", + "Description": "Custom disk images should not be shared publicly with **allAuthenticatedUsers**. Per Google Cloud API restrictions, **allUsers** cannot be assigned to Compute Engine images. The concern is **allAuthenticatedUsers**, which grants access to anyone with a Google account, potentially exposing application snapshots and sensitive data.", "Risk": "Publicly shared disk images can expose **sensitive data** and application configurations to unauthorized users.\n\n- Any authenticated GCP user can access the image content\n- Could lead to **data breaches** if images contain secrets or proprietary code\n- Attackers may use exposed images to understand application architecture", "RelatedUrl": "", "AdditionalURLs": [ diff --git a/prowler/providers/gcp/services/compute/compute_instance_group_autohealing_enabled/compute_instance_group_autohealing_enabled.metadata.json b/prowler/providers/gcp/services/compute/compute_instance_group_autohealing_enabled/compute_instance_group_autohealing_enabled.metadata.json index f364573e2e..acdf48ed00 100644 --- a/prowler/providers/gcp/services/compute/compute_instance_group_autohealing_enabled/compute_instance_group_autohealing_enabled.metadata.json +++ b/prowler/providers/gcp/services/compute/compute_instance_group_autohealing_enabled/compute_instance_group_autohealing_enabled.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "compute.googleapis.com/InstanceGroupManager", "ResourceGroup": "compute", "Description": "Managed Instance Groups (MIGs) should have **autohealing** enabled with a valid health check configured. Autohealing automatically recreates unhealthy instances based on application-level health checks, ensuring continuous availability.", - "Risk": "Without autohealing, MIGs cannot detect application-level failures such as crashes, freezes, or memory issues. Instances that are technically running but experiencing problems will remain undetected and unreplaced, leading to:\n\n- **Service degradation** from unhealthy instances\n- **Extended downtime** during application failures\n- **Manual intervention** required to detect and replace failed instances", + "Risk": "Without autohealing, MIGs cannot detect application-level failures such as crashes, freezes, or memory issues. Instances experiencing problems remain undetected and unreplaced, leading to **service degradation**, **extended downtime**, and requiring **manual intervention** to detect and replace failed instances.", "RelatedUrl": "", "AdditionalURLs": [ "https://cloud.google.com/compute/docs/instance-groups/autohealing-instances-in-migs" diff --git a/prowler/providers/gcp/services/compute/compute_instance_group_multiple_zones/compute_instance_group_multiple_zones.metadata.json b/prowler/providers/gcp/services/compute/compute_instance_group_multiple_zones/compute_instance_group_multiple_zones.metadata.json index 927e72874a..3b7442bf2a 100644 --- a/prowler/providers/gcp/services/compute/compute_instance_group_multiple_zones/compute_instance_group_multiple_zones.metadata.json +++ b/prowler/providers/gcp/services/compute/compute_instance_group_multiple_zones/compute_instance_group_multiple_zones.metadata.json @@ -1,7 +1,7 @@ { "Provider": "gcp", "CheckID": "compute_instance_group_multiple_zones", - "CheckTitle": "Ensure Managed Instance Groups span multiple zones for high availability", + "CheckTitle": "Managed Instance Groups span multiple zones for high availability", "CheckType": [], "ServiceName": "compute", "SubServiceName": "", diff --git a/prowler/providers/gcp/services/compute/compute_instance_suspended_without_persistent_disks/compute_instance_suspended_without_persistent_disks.metadata.json b/prowler/providers/gcp/services/compute/compute_instance_suspended_without_persistent_disks/compute_instance_suspended_without_persistent_disks.metadata.json index 3e65d6f844..aa2f15a157 100644 --- a/prowler/providers/gcp/services/compute/compute_instance_suspended_without_persistent_disks/compute_instance_suspended_without_persistent_disks.metadata.json +++ b/prowler/providers/gcp/services/compute/compute_instance_suspended_without_persistent_disks/compute_instance_suspended_without_persistent_disks.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "compute.googleapis.com/Instance", "ResourceGroup": "compute", "Description": "This check identifies VM instances in a **SUSPENDED** or **SUSPENDING** state with persistent disks still attached.\n\nPersistent disks on suspended VMs remain accessible through the GCP API and could contain **sensitive data** while the instance is inactive, potentially creating security blind spots in long-forgotten infrastructure.", - "Risk": "Persistent disks on suspended VM instances remain accessible through the GCP API and may contain **sensitive data**, creating potential security risks:\n\n- **Unauthorized data access** if credentials are compromised or permissions are misconfigured\n- **Data exposure** from forgotten infrastructure that is no longer actively monitored\n- **Security blind spots** where suspended resources are overlooked during security reviews and audits", + "Risk": "Persistent disks on suspended VM instances remain accessible through the GCP API and may contain **sensitive data**. This creates risks of **unauthorized data access** if permissions are misconfigured, **data exposure** from forgotten unmonitored infrastructure, and **security blind spots** where suspended resources are overlooked during reviews.", "RelatedUrl": "", "AdditionalURLs": [ "https://cloud.google.com/icompute/docs/instances/suspend-resume-instance" diff --git a/prowler/providers/gcp/services/logging/logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled/logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.metadata.json b/prowler/providers/gcp/services/logging/logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled/logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.metadata.json index d2eb0c8ef3..6155533a33 100644 --- a/prowler/providers/gcp/services/logging/logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled/logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.metadata.json +++ b/prowler/providers/gcp/services/logging/logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled/logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "MetricFilter", "ResourceGroup": "monitoring", "Description": "Log metric filters and alerts for **Compute Engine configuration changes** provide visibility into modifications to instances, disks, networks, firewalls, and routes. These monitoring controls enable security teams to detect unauthorized changes and investigate suspicious infrastructure modifications.", - "Risk": "Without monitoring for Compute Engine configuration changes, **unauthorized modifications** to compute resources may go undetected. Attackers can establish **persistence** through instance modifications, escalate privileges via IAM policy changes, disable security controls, or pivot to other resources. This compromises **confidentiality**, **integrity**, and **availability** of workloads and may enable **data exfiltration** or **lateral movement**.", + "Risk": "Without monitoring for Compute Engine configuration changes, **unauthorized modifications** may go undetected. Attackers can establish **persistence**, escalate privileges, disable security controls, or pivot to other resources, compromising **confidentiality**, **integrity**, and **availability** of workloads.", "RelatedUrl": "", "AdditionalURLs": [ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/gcp/ComputeEngine/gcp-compute-engine-configuration-changes.html", diff --git a/prowler/providers/m365/services/defender/defender_zap_for_teams_enabled/defender_zap_for_teams_enabled.metadata.json b/prowler/providers/m365/services/defender/defender_zap_for_teams_enabled/defender_zap_for_teams_enabled.metadata.json index d19f3c5229..ea46672ece 100644 --- a/prowler/providers/m365/services/defender/defender_zap_for_teams_enabled/defender_zap_for_teams_enabled.metadata.json +++ b/prowler/providers/m365/services/defender/defender_zap_for_teams_enabled/defender_zap_for_teams_enabled.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "NotDefined", "ResourceGroup": "collaboration", "Description": "**Microsoft Defender for Office 365 Zero-hour auto purge (ZAP)** is a protection feature that retroactively detects and neutralizes **malware** and **high confidence phishing** in Teams messages.\n\nWhen ZAP blocks a message, it is blocked for everyone in the chat. The initial block happens right after delivery, but ZAP can occur up to 48 hours after delivery.", - "Risk": "Without ZAP enabled, malicious content delivered to Teams chats remains accessible to users for up to 48 hours after delivery, even after being identified as harmful.\n\nThis extended exposure window could lead to:\n- **Malware infections** from weaponized attachments or links\n- **Phishing attacks** compromising user credentials and MFA tokens\n- **Lateral movement** as attackers exploit compromised accounts within the organization", + "Risk": "Without ZAP enabled, malicious content in Teams chats remains accessible for up to 48 hours after delivery, even after being identified as harmful. This extended exposure enables **malware infections**, **phishing attacks** compromising credentials and MFA tokens, and **lateral movement** via compromised accounts.", "RelatedUrl": "", "AdditionalURLs": [ "https://learn.microsoft.com/en-us/defender-office-365/zero-hour-auto-purge?view=o365-worldwide#zero-hour-auto-purge-zap-in-microsoft-teams", diff --git a/prowler/providers/openstack/services/image/image_signature_verification_enabled/image_signature_verification_enabled.metadata.json b/prowler/providers/openstack/services/image/image_signature_verification_enabled/image_signature_verification_enabled.metadata.json index c9630e75aa..84b0e20799 100644 --- a/prowler/providers/openstack/services/image/image_signature_verification_enabled/image_signature_verification_enabled.metadata.json +++ b/prowler/providers/openstack/services/image/image_signature_verification_enabled/image_signature_verification_enabled.metadata.json @@ -9,7 +9,7 @@ "Severity": "high", "ResourceType": "OS::Glance::WebImage", "ResourceGroup": "storage", - "Description": "**OpenStack images** are evaluated to verify that all **four signature properties** are configured: `img_signature`, `img_signature_hash_method`, `img_signature_key_type`, and `img_signature_certificate_uuid`. Signed images allow Nova to verify image integrity before booting, detecting tampering or corruption. Best practices recommend signing all production images using Barbican-managed certificates.", + "Description": "**OpenStack images** are evaluated to verify that all **four signature properties** are configured: `img_signature`, `img_signature_hash_method`, `img_signature_key_type`, and `img_signature_certificate_uuid`. Signed images allow Nova to verify integrity before booting, detecting tampering or corruption.", "Risk": "Unsigned images can be tampered with to inject backdoors, malware, or rootkits without detection. Without signature verification, compromised storage backends or man-in-the-middle attacks can modify images between upload and boot. Nova cannot verify the integrity of unsigned images, allowing corrupted or malicious images to be launched.", "RelatedUrl": "", "AdditionalURLs": [ diff --git a/prowler/providers/openstack/services/networking/networking_security_group_allows_all_ingress_from_internet/networking_security_group_allows_all_ingress_from_internet.metadata.json b/prowler/providers/openstack/services/networking/networking_security_group_allows_all_ingress_from_internet/networking_security_group_allows_all_ingress_from_internet.metadata.json index 473dc9602d..79998af96c 100644 --- a/prowler/providers/openstack/services/networking/networking_security_group_allows_all_ingress_from_internet/networking_security_group_allows_all_ingress_from_internet.metadata.json +++ b/prowler/providers/openstack/services/networking/networking_security_group_allows_all_ingress_from_internet/networking_security_group_allows_all_ingress_from_internet.metadata.json @@ -10,7 +10,7 @@ "ResourceType": "OS::Neutron::SecurityGroup", "ResourceGroup": "network", "Description": "**OpenStack security groups** are evaluated to verify that no rule allows **all ingress traffic** (any protocol, any port) from the Internet (0.0.0.0/0 or ::/0). A rule with no protocol and no port restriction is effectively a \"permit any\" firewall rule, completely bypassing network-level access controls. This is the most permissive possible configuration and should never be used in production.", - "Risk": "Allowing all inbound traffic from the Internet exposes every service running on the instance to unauthorized access. Attackers can discover and exploit any listening service including databases, management interfaces, internal APIs, and debugging tools. This bypasses defense-in-depth and is equivalent to having no firewall. Combined with misconfigurations or unpatched services, it enables initial access, lateral movement, data exfiltration, and full infrastructure compromise.", + "Risk": "Allowing all inbound traffic from the Internet exposes every service on the instance to unauthorized access. Attackers can exploit any listening service including databases, management interfaces, and internal APIs. This bypasses defense-in-depth and enables initial access, lateral movement, data exfiltration, and infrastructure compromise.", "RelatedUrl": "", "AdditionalURLs": [ "https://docs.openstack.org/neutron/latest/admin/intro-os-networking.html", diff --git a/prowler/providers/oraclecloud/services/events/events_rule_network_security_group_changes/events_rule_network_security_group_changes.metadata.json b/prowler/providers/oraclecloud/services/events/events_rule_network_security_group_changes/events_rule_network_security_group_changes.metadata.json index f19b28e311..f8ce61ada9 100644 --- a/prowler/providers/oraclecloud/services/events/events_rule_network_security_group_changes/events_rule_network_security_group_changes.metadata.json +++ b/prowler/providers/oraclecloud/services/events/events_rule_network_security_group_changes/events_rule_network_security_group_changes.metadata.json @@ -9,7 +9,7 @@ "Severity": "medium", "ResourceType": "EventRule", "ResourceGroup": "messaging", - "Description": "**OCI Events rules** targeting **Network Security Group (NSG)** changes are evaluated for the presence of **notification actions**. Monitored event types: `com.oraclecloud.virtualnetwork.createnetworksecuritygroup`, `com.oraclecloud.virtualnetwork.updatenetworksecuritygroup`, `com.oraclecloud.virtualnetwork.deletenetworksecuritygroup`, `com.oraclecloud.virtualnetwork.changenetworksecuritygroupcompartment`.", + "Description": "**OCI Events rules** targeting **Network Security Group (NSG)** changes are evaluated for **notification actions**. Monitored events: `createnetworksecuritygroup`, `updatenetworksecuritygroup`, `deletenetworksecuritygroup`, and `changenetworksecuritygroupcompartment` under `com.oraclecloud.virtualnetwork`.", "Risk": "Absent notifications for NSG changes enable silent policy drift.\n- **Confidentiality**: permissive edits can expose services and drive data exfiltration.\n- **Integrity**: attackers can reroute traffic or bypass micro-segmentation.\n- **Availability**: deletions/misconfigurations may isolate workloads or widen DDoS exposure.", "RelatedUrl": "", "AdditionalURLs": [ diff --git a/tests/lib/check/check_loader_test.py b/tests/lib/check/check_loader_test.py index 740ed67844..140dbdaaa7 100644 --- a/tests/lib/check/check_loader_test.py +++ b/tests/lib/check/check_loader_test.py @@ -31,7 +31,9 @@ class TestCheckLoader: Provider="aws", CheckID=S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME, CheckTitle="Check S3 Bucket Level Public Access Block.", - CheckType=["Data Protection"], + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], CheckAliases=[S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME_CUSTOM_ALIAS], ServiceName=S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME_SERVICE, SubServiceName="", @@ -40,7 +42,7 @@ class TestCheckLoader: ResourceType="AwsS3Bucket", Description="Check S3 Bucket Level Public Access Block.", Risk="Public access policies may be applied to sensitive data buckets.", - RelatedUrl="https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html", + RelatedUrl="", Remediation=Remediation( Code=Code( NativeIaC="", @@ -50,7 +52,7 @@ class TestCheckLoader: ), Recommendation=Recommendation( Text="You can enable Public Access Block at the bucket level to prevent the exposure of your data stored in S3.", - Url="https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html", + Url="https://hub.prowler.com/check/s3_bucket_level_public_access_block", ), ), Categories=["internet-exposed"], @@ -65,7 +67,9 @@ class TestCheckLoader: Provider="aws", CheckID=IAM_USER_NO_MFA_NAME, CheckTitle="Check IAM User No MFA.", - CheckType=["Data Protection"], + CheckType=[ + "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" + ], CheckAliases=[IAM_USER_NO_MFA_NAME_CUSTOM_ALIAS], ServiceName=IAM_USER_NO_MFA_NAME_SERVICE, SubServiceName="", @@ -74,7 +78,7 @@ class TestCheckLoader: ResourceType="AwsIamUser", Description="Check IAM User No MFA.", Risk="IAM users should have Multi-Factor Authentication (MFA) enabled.", - RelatedUrl="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_enable_virtual.html", + RelatedUrl="", Remediation=Remediation( Code=Code( NativeIaC="", @@ -84,7 +88,7 @@ class TestCheckLoader: ), Recommendation=Recommendation( Text="You can enable MFA for your IAM user to prevent unauthorized access to your AWS account.", - Url="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_enable_virtual.html", + Url="https://hub.prowler.com/check/iam_user_no_mfa", ), ), Categories=[], @@ -98,8 +102,8 @@ class TestCheckLoader: return CheckMetadata( Provider="aws", CheckID=CLOUDTRAIL_THREAT_DETECTION_ENUMERATION_NAME, - CheckTitle="Ensure there are no potential enumeration threats in CloudTrail", - CheckType=[], + CheckTitle="CloudTrail should not have potential enumeration threats", + CheckType=["TTPs/Discovery"], ServiceName="cloudtrail", SubServiceName="", ResourceIdTemplate="arn:partition:service:region:account-id:resource-id", @@ -112,7 +116,7 @@ class TestCheckLoader: Code=Code(CLI="", NativeIaC="", Other="", Terraform=""), Recommendation=Recommendation( Text="To remediate this issue, ensure that there are no potential enumeration threats in CloudTrail.", - Url="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-concepts.html#cloudtrail-concepts-logging-data-events", + Url="https://hub.prowler.com/check/cloudtrail_threat_detection_enumeration", ), ), Categories=["threat-detection"], @@ -123,55 +127,55 @@ class TestCheckLoader: ) def test_load_checks_to_execute(self): - bulk_checks_metatada = { + bulk_checks_metadata = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() } assert {S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME} == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, + bulk_checks_metadata=bulk_checks_metadata, provider=self.provider, ) def test_load_checks_to_execute_with_check_list(self): - bulk_checks_metatada = { + bulk_checks_metadata = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() } check_list = [S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME] assert {S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME} == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, + bulk_checks_metadata=bulk_checks_metadata, check_list=check_list, provider=self.provider, ) def test_load_checks_to_execute_with_severities(self): - bulk_checks_metatada = { + bulk_checks_metadata = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() } severities = [S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_SEVERITY] assert {S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME} == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, + bulk_checks_metadata=bulk_checks_metadata, severities=severities, provider=self.provider, ) def test_load_checks_to_execute_with_severities_and_services(self): - bulk_checks_metatada = { + bulk_checks_metadata = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() } service_list = [S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME_SERVICE] severities = [S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_SEVERITY] assert {S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME} == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, + bulk_checks_metadata=bulk_checks_metadata, service_list=service_list, severities=severities, provider=self.provider, ) def test_load_checks_to_execute_with_severities_and_services_multiple(self): - bulk_checks_metatada = { + bulk_checks_metadata = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata(), IAM_USER_NO_MFA_NAME: self.get_custom_check_iam_metadata(), } @@ -182,7 +186,7 @@ class TestCheckLoader: S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME, IAM_USER_NO_MFA_NAME, } == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, + bulk_checks_metadata=bulk_checks_metadata, service_list=service_list, severities=severities, provider=self.provider, @@ -211,7 +215,7 @@ class TestCheckLoader: def test_load_checks_to_execute_with_checks_file( self, ): - bulk_checks_metatada = { + bulk_checks_metadata = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() } checks_file = "path/to/test_file" @@ -220,7 +224,7 @@ class TestCheckLoader: return_value={S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME}, ): assert {S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME} == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, + bulk_checks_metadata=bulk_checks_metadata, checks_file=checks_file, provider=self.provider, ) @@ -228,13 +232,13 @@ class TestCheckLoader: def test_load_checks_to_execute_with_service_list( self, ): - bulk_checks_metatada = { + bulk_checks_metadata = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() } service_list = [S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME_SERVICE] assert {S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME} == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, + bulk_checks_metadata=bulk_checks_metadata, service_list=service_list, provider=self.provider, ) @@ -242,7 +246,7 @@ class TestCheckLoader: def test_load_checks_to_execute_with_compliance_frameworks( self, ): - bulk_checks_metatada = { + bulk_checks_metadata = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() } bulk_compliance_frameworks = { @@ -264,34 +268,39 @@ class TestCheckLoader: } compliance_frameworks = ["soc2_aws"] - assert {S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME} == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, - bulk_compliance_frameworks=bulk_compliance_frameworks, - compliance_frameworks=compliance_frameworks, - provider=self.provider, - ) + # Mock get_bulk to prevent loading real metadata files that may fail validation + with patch( + "prowler.lib.check.checks_loader.CheckMetadata.get_bulk", + return_value=bulk_checks_metadata, + ): + assert {S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME} == load_checks_to_execute( + bulk_checks_metadata=bulk_checks_metadata, + bulk_compliance_frameworks=bulk_compliance_frameworks, + compliance_frameworks=compliance_frameworks, + provider=self.provider, + ) def test_load_checks_to_execute_with_categories( self, ): - bulk_checks_metatada = { + bulk_checks_metadata = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() } categories = {"internet-exposed"} assert {S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME} == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, + bulk_checks_metadata=bulk_checks_metadata, categories=categories, provider=self.provider, ) def test_load_checks_to_execute_no_bulk_checks_metadata(self): - bulk_checks_metatada = { + bulk_checks_metadata = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() } with patch( "prowler.lib.check.checks_loader.CheckMetadata.get_bulk", - return_value=bulk_checks_metatada, + return_value=bulk_checks_metadata, ): assert {S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME} == load_checks_to_execute( provider=self.provider, @@ -318,13 +327,13 @@ class TestCheckLoader: compliance_frameworks = ["soc2_aws"] - bulk_checks_metatada = { + bulk_checks_metadata = { S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata() } with ( patch( "prowler.lib.check.checks_loader.CheckMetadata.get_bulk", - return_value=bulk_checks_metatada, + return_value=bulk_checks_metadata, ), patch( "prowler.lib.check.checks_loader.Compliance.get_bulk", @@ -351,38 +360,38 @@ class TestCheckLoader: ) def test_threat_detection_category(self): - bulk_checks_metatada = { + bulk_checks_metadata = { CLOUDTRAIL_THREAT_DETECTION_ENUMERATION_NAME: self.get_threat_detection_check_metadata() } categories = {"threat-detection"} assert {CLOUDTRAIL_THREAT_DETECTION_ENUMERATION_NAME} == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, + bulk_checks_metadata=bulk_checks_metadata, categories=categories, provider=self.provider, ) def test_discard_threat_detection_checks(self): - bulk_checks_metatada = { + bulk_checks_metadata = { CLOUDTRAIL_THREAT_DETECTION_ENUMERATION_NAME: self.get_threat_detection_check_metadata() } categories = {} assert set() == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, + bulk_checks_metadata=bulk_checks_metadata, categories=categories, provider=self.provider, ) def test_threat_detection_single_check(self): - bulk_checks_metatada = { + bulk_checks_metadata = { CLOUDTRAIL_THREAT_DETECTION_ENUMERATION_NAME: self.get_threat_detection_check_metadata() } categories = {} check_list = [CLOUDTRAIL_THREAT_DETECTION_ENUMERATION_NAME] assert {CLOUDTRAIL_THREAT_DETECTION_ENUMERATION_NAME} == load_checks_to_execute( - bulk_checks_metadata=bulk_checks_metatada, + bulk_checks_metadata=bulk_checks_metadata, check_list=check_list, categories=categories, provider=self.provider, diff --git a/tests/lib/check/check_test.py b/tests/lib/check/check_test.py index 868d4c66fe..b6332deb25 100644 --- a/tests/lib/check/check_test.py +++ b/tests/lib/check/check_test.py @@ -24,7 +24,7 @@ from prowler.lib.check.check import ( remove_custom_checks_module, update_audit_metadata, ) -from prowler.lib.check.models import load_check_metadata +from prowler.lib.check.models import CheckMetadata, load_check_metadata from prowler.lib.check.utils import ( list_modules, recover_checks_from_provider, @@ -412,7 +412,7 @@ class TestCheck: }, "expected": { "CheckID": "iam_user_accesskey_unused", - "CheckTitle": "Ensure Access Keys unused are disabled", + "CheckTitle": "Access Keys unused should be disabled", "ServiceName": "iam", "Severity": "low", }, @@ -502,7 +502,7 @@ class TestCheck: "ResourceType": "AwsCustomResource", "Description": "A test custom check", "Risk": "Test risk", - "RelatedUrl": "https://example.com", + "RelatedUrl": "", "Remediation": { "Code": {"CLI": "", "NativeIaC": "", "Other": "", "Terraform": ""}, "Recommendation": {"Text": "", "Url": ""}, @@ -614,7 +614,7 @@ class TestCheck: "forensics-ready", "encryption", "internet-exposed", - "trustboundaries", + "trust-boundaries", } listed_categories = list_categories(test_bulk_checks_metadata) assert listed_categories == expected_categories @@ -958,7 +958,98 @@ class TestCheck: ) self.verify_metadata_check_id(base_directory) + def test_alibabacloud_checks_metadata_is_valid(self): + base_directory = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../", + "prowler/providers/alibabacloud/services", + ) + ) + self.verify_metadata_check_id(base_directory) + + def test_cloudflare_checks_metadata_is_valid(self): + base_directory = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../", + "prowler/providers/cloudflare/services", + ) + ) + self.verify_metadata_check_id(base_directory) + + def test_github_checks_metadata_is_valid(self): + base_directory = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../", + "prowler/providers/github/services", + ) + ) + self.verify_metadata_check_id(base_directory) + + def test_googleworkspace_checks_metadata_is_valid(self): + base_directory = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../", + "prowler/providers/googleworkspace/services", + ) + ) + self.verify_metadata_check_id(base_directory) + + def test_m365_checks_metadata_is_valid(self): + base_directory = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../", + "prowler/providers/m365/services", + ) + ) + self.verify_metadata_check_id(base_directory) + + def test_mongodbatlas_checks_metadata_is_valid(self): + base_directory = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../", + "prowler/providers/mongodbatlas/services", + ) + ) + self.verify_metadata_check_id(base_directory) + + def test_nhn_checks_metadata_is_valid(self): + base_directory = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../", + "prowler/providers/nhn/services", + ) + ) + self.verify_metadata_check_id(base_directory) + + def test_openstack_checks_metadata_is_valid(self): + base_directory = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../", + "prowler/providers/openstack/services", + ) + ) + self.verify_metadata_check_id(base_directory) + + def test_oraclecloud_checks_metadata_is_valid(self): + base_directory = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../", + "prowler/providers/oraclecloud/services", + ) + ) + self.verify_metadata_check_id(base_directory) + def verify_metadata_check_id(self, provider_path): + errors = [] # Walk through the base directory to find all service directories for root, dirs, _ in os.walk(provider_path): # We only want to look at directories that are direct children of the base directory @@ -984,9 +1075,20 @@ class TestCheck: check_id = data.get("CheckID", None) # Compare CheckID to the check name - assert ( - check_id == check_dir - ), f"CheckID in metadata does not match the check name in {check_directory}. Found CheckID: {check_id}" + if check_id != check_dir: + errors.append( + f"CheckID in metadata does not match the check name in {check_directory}. Found CheckID: {check_id}" + ) + + # Validate metadata against Pydantic validators + try: + CheckMetadata.parse_file(metadata_file_path) + except Exception as e: + errors.append( + f"Metadata validation failed for {metadata_file_path}: {e}" + ) + + assert not errors, "\n\n".join(errors) def test_execute_check_exception_only_logs(self, caplog): caplog.set_level(ERROR) diff --git a/tests/lib/check/compliance_check_test.py b/tests/lib/check/compliance_check_test.py index 3fae02fa87..73f93e3b38 100644 --- a/tests/lib/check/compliance_check_test.py +++ b/tests/lib/check/compliance_check_test.py @@ -197,7 +197,7 @@ class TestCompliance: Provider="aws", CheckID="accessanalyzer_enabled", CheckTitle="Check 1", - CheckType=["type1"], + CheckType=["TTPs/Initial Access"], ServiceName="accessanalyzer", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -205,7 +205,7 @@ class TestCompliance: ResourceType="resource1", Description="Description 1", Risk="risk1", - RelatedUrl="url1", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", @@ -213,9 +213,12 @@ class TestCompliance: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/accessanalyzer_enabled", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -225,7 +228,7 @@ class TestCompliance: Provider="aws", CheckID="iam_user_mfa_enabled_console_access", CheckTitle="Check 2", - CheckType=["type2"], + CheckType=["TTPs/Credential Access"], ServiceName="iam", SubServiceName="subservice2", ResourceIdTemplate="template2", @@ -233,7 +236,7 @@ class TestCompliance: ResourceType="resource2", Description="Description 2", Risk="risk2", - RelatedUrl="url2", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli2", @@ -241,9 +244,12 @@ class TestCompliance: "Other": "other2", "Terraform": "terraform2", }, - "Recommendation": {"Text": "text2", "Url": "url2"}, + "Recommendation": { + "Text": "text2", + "Url": "https://hub.prowler.com/check/iam_user_mfa_enabled_console_access", + }, }, - Categories=["categorytwo"], + Categories=["logging"], DependsOn=["dependency2"], RelatedTo=["related2"], Notes="notes2", diff --git a/tests/lib/check/custom_checks_metadata_test.py b/tests/lib/check/custom_checks_metadata_test.py index 69f27880a8..9037131675 100644 --- a/tests/lib/check/custom_checks_metadata_test.py +++ b/tests/lib/check/custom_checks_metadata_test.py @@ -27,7 +27,9 @@ S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_REMEDIATION_OTHER = "https://github.com/clou S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_REMEDIATION_TEXT = ( "Enable the S3 bucket level public access block." ) -S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_REMEDIATION_URL = "https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html" +S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_REMEDIATION_URL = ( + "https://hub.prowler.com/check/s3_bucket_level_public_access_block" +) class TestCustomChecksMetadata: @@ -36,7 +38,7 @@ class TestCustomChecksMetadata: Provider="aws", CheckID=S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME, CheckTitle="Check S3 Bucket Level Public Access Block.", - CheckType=["Data Protection"], + CheckType=["Sensitive Data Identifications/PII"], CheckAliases=[], ServiceName="s3", SubServiceName="", @@ -45,7 +47,7 @@ class TestCustomChecksMetadata: ResourceType="AwsS3Bucket", Description="Check S3 Bucket Level Public Access Block.", Risk="Public access policies may be applied to sensitive data buckets.", - RelatedUrl="https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html", + RelatedUrl="", Remediation=Remediation( Code=Code( NativeIaC="", diff --git a/tests/lib/check/fixtures/bulk_checks_metadata.py b/tests/lib/check/fixtures/bulk_checks_metadata.py index d0eee8bc66..5cd8c649e4 100644 --- a/tests/lib/check/fixtures/bulk_checks_metadata.py +++ b/tests/lib/check/fixtures/bulk_checks_metadata.py @@ -4,14 +4,16 @@ test_bulk_checks_metadata = { "vpc_peering_routing_tables_with_least_privilege": CheckMetadata( Provider="aws", CheckID="vpc_peering_routing_tables_with_least_privilege", - CheckTitle="Ensure routing tables for VPC peering are least access.", - CheckType=["Infrastructure Security"], + CheckTitle="VPC peering routing tables should follow least access.", + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], ServiceName="vpc", SubServiceName="route_table", ResourceIdTemplate="arn:partition:service:region:account-id:resource-id", Severity="medium", ResourceType="AwsEc2VpcPeeringConnection", - Description="Ensure routing tables for VPC peering are least access.", + Description="VPC peering routing tables should follow least access.", Risk="Being highly selective in peering routing tables is a very effective way of minimizing the impact of breach as resources outside of these routes are inaccessible to the peered VPC.", RelatedUrl="", Remediation=Remediation( @@ -23,7 +25,7 @@ test_bulk_checks_metadata = { ), Recommendation=Recommendation( Text="Review routing tables of peered VPCs for whether they route all subnets of each VPC and whether that is necessary to accomplish the intended purposes for peering the VPCs.", - Url="https://docs.aws.amazon.com/vpc/latest/peering/peering-configurations-partial-access.html", + Url="https://hub.prowler.com/check/vpc_peering_routing_tables_with_least_privilege", ), ), Categories=["forensics-ready"], @@ -35,8 +37,10 @@ test_bulk_checks_metadata = { "vpc_subnet_different_az": CheckMetadata( Provider="aws", CheckID="vpc_subnet_different_az", - CheckTitle="Ensure all vpc has subnets in more than one availability zone", - CheckType=["Infrastructure Security"], + CheckTitle="VPC should have subnets in more than one availability zone", + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], ServiceName="vpc", SubServiceName="subnet", ResourceIdTemplate="arn:partition:service:region:account-id:resource-id", @@ -44,13 +48,13 @@ test_bulk_checks_metadata = { ResourceType="AwsEc2Vpc", Description="Ensure all vpc has subnets in more than one availability zone", Risk="", - RelatedUrl="https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Scenario2.html", + RelatedUrl="", Remediation=Remediation( Code=Code( NativeIaC="", Terraform="", CLI="aws ec2 create-subnet", Other="" ), Recommendation=Recommendation( - Text="Ensure all vpc has subnets in more than one availability zone", + Text="VPC should have subnets in more than one availability zone", Url="", ), ), @@ -63,8 +67,10 @@ test_bulk_checks_metadata = { "vpc_subnet_separate_private_public": CheckMetadata( Provider="aws", CheckID="vpc_subnet_separate_private_public", - CheckTitle="Ensure all vpc has public and private subnets defined", - CheckType=["Infrastructure Security"], + CheckTitle="VPC should have public and private subnets defined", + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], ServiceName="vpc", SubServiceName="subnet", ResourceIdTemplate="arn:partition:service:region:account-id:resource-id", @@ -72,16 +78,16 @@ test_bulk_checks_metadata = { ResourceType="AwsEc2Vpc", Description="Ensure all vpc has public and private subnets defined", Risk="", - RelatedUrl="https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Scenario2.html", + RelatedUrl="", Remediation=Remediation( Code=Code( NativeIaC="", Terraform="", CLI="aws ec2 create-subnet", Other="" ), Recommendation=Recommendation( - Text="Ensure all vpc has public and private subnets defined", Url="" + Text="VPC should have public and private subnets defined", Url="" ), ), - Categories=["internet-exposed", "trustboundaries"], + Categories=["internet-exposed", "trust-boundaries"], DependsOn=[], RelatedTo=[], Notes="", @@ -90,16 +96,18 @@ test_bulk_checks_metadata = { "workspaces_volume_encryption_enabled": CheckMetadata( Provider="aws", CheckID="workspaces_volume_encryption_enabled", - CheckTitle="Ensure that your Amazon WorkSpaces storage volumes are encrypted in order to meet security and compliance requirements", - CheckType=[], + CheckTitle="Amazon WorkSpaces storage volumes should be encrypted", + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices/Runtime Behavior Analysis" + ], ServiceName="workspaces", SubServiceName="", ResourceIdTemplate="arn:aws:workspaces:region:account-id:workspace", Severity="high", ResourceType="AwsWorkspaces", - Description="Ensure that your Amazon WorkSpaces storage volumes are encrypted in order to meet security and compliance requirements", + Description="Amazon WorkSpaces storage volumes should be encrypted to meet security and compliance requirements", Risk="If the value listed in the Volume Encryption column is Disabled the selected AWS WorkSpaces instance volumes (root and user volumes) are not encrypted. Therefore your data-at-rest is not protected from unauthorized access and does not meet the compliance requirements regarding data encryption.", - RelatedUrl="https://docs.aws.amazon.com/workspaces/latest/adminguide/encrypt-workspaces.html", + RelatedUrl="", Remediation=Remediation( Code=Code( NativeIaC="https://docs.prowler.com/checks/ensure-that-workspace-root-volumes-are-encrypted#cloudformation", @@ -109,7 +117,7 @@ test_bulk_checks_metadata = { ), Recommendation=Recommendation( Text="WorkSpaces is integrated with the AWS Key Management Service (AWS KMS). This enables you to encrypt storage volumes of WorkSpaces using AWS KMS Key. When you launch a WorkSpace you can encrypt the root volume (for Microsoft Windows - the C drive; for Linux - /) and the user volume (for Windows - the D drive; for Linux - /home). Doing so ensures that the data stored at rest - disk I/O to the volume - and snapshots created from the volumes are all encrypted", - Url="https://docs.aws.amazon.com/workspaces/latest/adminguide/encrypt-workspaces.html", + Url="https://hub.prowler.com/check/workspaces_volume_encryption_enabled", ), ), Categories=["encryption"], @@ -121,21 +129,23 @@ test_bulk_checks_metadata = { "workspaces_vpc_2private_1public_subnets_nat": CheckMetadata( Provider="aws", CheckID="workspaces_vpc_2private_1public_subnets_nat", - CheckTitle="Ensure that the Workspaces VPC are deployed following the best practices using 1 public subnet and 2 private subnets with a NAT Gateway attached", - CheckType=[], + CheckTitle="Workspaces VPC should use 1 public and 2 private subnets with NAT Gateway", + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices/Runtime Behavior Analysis" + ], ServiceName="workspaces", SubServiceName="", ResourceIdTemplate="arn:aws:workspaces:region:account-id:workspace", Severity="medium", ResourceType="AwsWorkspaces", - Description="Ensure that the Workspaces VPC are deployed following the best practices using 1 public subnet and 2 private subnets with a NAT Gateway attached", + Description="Workspaces VPC should be deployed with 1 public subnet and 2 private subnets with a NAT Gateway attached", Risk="Proper network segmentation is a key security best practice. Workspaces VPC should be deployed using 1 public subnet and 2 private subnets with a NAT Gateway attached", - RelatedUrl="https://docs.aws.amazon.com/workspaces/latest/adminguide/amazon-workspaces-vpc.html", + RelatedUrl="", Remediation=Remediation( Code=Code(NativeIaC="", Terraform="", CLI="", Other=""), Recommendation=Recommendation( Text="Follow the documentation and deploy Workspaces VPC using 1 public subnet and 2 private subnets with a NAT Gateway attached", - Url="https://docs.aws.amazon.com/workspaces/latest/adminguide/amazon-workspaces-vpc.html", + Url="https://hub.prowler.com/check/workspaces_vpc_2private_1public_subnets_nat", ), ), Categories=[], diff --git a/tests/lib/check/fixtures/metadata.json b/tests/lib/check/fixtures/metadata.json index a376d491a9..b26438aac8 100644 --- a/tests/lib/check/fixtures/metadata.json +++ b/tests/lib/check/fixtures/metadata.json @@ -1,10 +1,10 @@ { "Categories": [ - "cat-one", - "cat-two" + "encryption", + "logging" ], "CheckID": "iam_user_accesskey_unused", - "CheckTitle": "Ensure Access Keys unused are disabled", + "CheckTitle": "Access Keys unused should be disabled", "CheckType": [ "Software and Configuration Checks" ], @@ -25,14 +25,14 @@ "othercheck1", "othercheck2" ], - "Description": "Ensure Access Keys unused are disabled", + "Description": "Access Keys unused should be disabled", "Notes": "additional information", "Provider": "aws", "RelatedTo": [ "othercheck3", "othercheck4" ], - "RelatedUrl": "https://serviceofficialsiteorpageforthissubject", + "RelatedUrl": "", "Remediation": { "Code": { "CLI": "cli command or URL to the cli command location.", @@ -42,7 +42,7 @@ }, "Recommendation": { "Text": "Run sudo yum update and cross your fingers and toes.", - "Url": "https://myfp.com/recommendations/dangerous_things_and_how_to_fix_them.html" + "Url": "https://hub.prowler.com/check/iam_user_accesskey_unused" } }, "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", diff --git a/tests/lib/check/models_test.py b/tests/lib/check/models_test.py index 3c490e20c5..8efa307c54 100644 --- a/tests/lib/check/models_test.py +++ b/tests/lib/check/models_test.py @@ -11,7 +11,7 @@ mock_metadata = CheckMetadata( Provider="aws", CheckID="accessanalyzer_enabled", CheckTitle="Check 1", - CheckType=["type1"], + CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="accessanalyzer", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -19,7 +19,7 @@ mock_metadata = CheckMetadata( ResourceType="resource1", Description="Description 1", Risk="risk1", - RelatedUrl="url1", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", @@ -27,9 +27,12 @@ mock_metadata = CheckMetadata( "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/accessanalyzer_enabled", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -40,7 +43,7 @@ mock_metadata_lambda = CheckMetadata( Provider="aws", CheckID="awslambda_function_url_public", CheckTitle="Check 1", - CheckType=["type1"], + CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="awslambda", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -48,7 +51,7 @@ mock_metadata_lambda = CheckMetadata( ResourceType="resource1", Description="Description 1", Risk="risk1", - RelatedUrl="url1", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", @@ -56,9 +59,12 @@ mock_metadata_lambda = CheckMetadata( "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/awslambda_function_url_public", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -175,7 +181,7 @@ class TestCheckMetada: bulk_metadata = CheckMetadata.get_bulk(provider="aws") result = CheckMetadata.list( - bulk_checks_metadata=bulk_metadata, category="categoryone" + bulk_checks_metadata=bulk_metadata, category="encryption" ) # Assertions @@ -330,13 +336,1473 @@ class TestCheckMetada: result = CheckMetadata.list(bulk_checks_metadata=bulk_metadata) assert result == set() + +class TestCheckMetadataValidators: + """Test class for CheckMetadata validators""" + + def test_valid_category_success(self): + """Test valid category validation with valid categories""" + valid_metadata = { + "Provider": "azure", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption", "logging", "secrets"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + # Should not raise any validation error + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.Categories == ["encryption", "logging", "secrets"] + + def test_valid_category_failure_non_string(self): + """Test valid category validation fails with non-string category""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": [123], # Invalid: number instead of string + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "Categories must be a list of strings" in str(exc_info.value) + + def test_valid_category_failure_invalid_format(self): + """Test valid category validation fails with invalid format""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["invalid_category!"], # Invalid: contains special character + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert ( + "Categories can only contain lowercase letters, numbers and hyphen" + in str(exc_info.value) + ) + + def test_valid_category_failure_not_predefined(self): + """Test valid category validation fails with non-predefined category""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["not-a-real-category"], # Invalid: not in predefined list + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "Invalid category: 'not-a-real-category'. Must be one of:" in str( + exc_info.value + ) + + def test_valid_category_all_predefined_values(self): + """Test that all predefined categories are accepted""" + from prowler.lib.check.models import VALID_CATEGORIES + + for category in VALID_CATEGORIES: + valid_metadata = { + "Provider": "azure", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": [category], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + check_metadata = CheckMetadata(**valid_metadata) + assert category in check_metadata.Categories + + def test_severity_to_lower_success(self): + """Test severity validation converts to lowercase""" + valid_metadata = { + "Provider": "azure", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "HIGH", # Uppercase - should be converted to lowercase + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.Severity == "high" + + def test_valid_cli_command_success(self): + """Test CLI command validation with valid command""" + valid_metadata = { + "Provider": "azure", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws iam create-role --role-name test", # Valid CLI command + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + # Should not raise any validation error + check_metadata = CheckMetadata(**valid_metadata) + assert "aws iam create-role" in check_metadata.Remediation.Code.CLI + + def test_valid_cli_command_failure_url(self): + """Test CLI command validation fails with URL""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "https://example.com/command", # Invalid: URL instead of command + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "CLI command cannot be an URL" in str(exc_info.value) + + def test_valid_resource_type_success(self): + """Test resource type validation with valid resource type""" + valid_metadata = { + "Provider": "azure", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "AWS::IAM::Role", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.ResourceType == "AWS::IAM::Role" + + def test_valid_resource_type_failure_empty(self): + """Test resource type validation fails with empty string""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "", # Invalid: empty string + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "ResourceType must be a non-empty string" in str(exc_info.value) + + def test_validate_service_name_success(self): + """Test service name validation with valid service name matching CheckID""" + valid_metadata = { + "Provider": "azure", + "CheckID": "s3_bucket_public_read", + "CheckTitle": "Test Check", + "CheckType": [], + "ServiceName": "s3", # Matches first part of CheckID + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "AWS::S3::Bucket", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.ServiceName == "s3" + + def test_validate_service_name_failure_mismatch(self): + """Test service name validation fails when not matching CheckID""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "s3_bucket_public_read", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "ec2", # Does not match first part of CheckID + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "AWS::S3::Bucket", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert ( + "ServiceName ec2 does not belong to CheckID s3_bucket_public_read" + in str(exc_info.value) + ) + + def test_validate_service_name_failure_uppercase(self): + """Test service name validation fails with uppercase""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "S3_bucket_public_read", + "CheckTitle": "Test Check", + "CheckType": ["TTPs/Discovery"], + "ServiceName": "S3", # Invalid: uppercase + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "AWS::S3::Bucket", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "ServiceName S3 must be in lowercase" in str(exc_info.value) + + def test_validate_service_name_iac_provider_success(self): + """Test service name validation allows any service name for IAC provider""" + valid_metadata = { + "Provider": "iac", + "CheckID": "custom_check_id", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "CustomService", # Valid for IAC provider + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.ServiceName == "CustomService" + + def test_valid_check_id_success(self): + """Test CheckID validation with valid check ID""" + valid_metadata = { + "Provider": "azure", + "CheckID": "s3_bucket_public_read_check", + "CheckTitle": "Test Check", + "CheckType": [], + "ServiceName": "s3", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "AWS::S3::Bucket", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.CheckID == "s3_bucket_public_read_check" + + def test_valid_check_id_failure_empty(self): + """Test CheckID validation fails with empty string""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "", # Invalid: empty string + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "CheckID must be a non-empty string" in str(exc_info.value) + + def test_valid_check_id_failure_hyphen(self): + """Test CheckID validation fails with hyphen""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "s3-bucket-public-read", # Invalid: contains hyphens + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "s3", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "AWS::S3::Bucket", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert ( + "CheckID s3-bucket-public-read contains a hyphen, which is not allowed" + in str(exc_info.value) + ) + + def test_validate_check_title_success(self): + """Test CheckTitle validation with valid title""" + valid_metadata = { + "Provider": "azure", + "CheckID": "test_check", + "CheckTitle": "A" * 150, # Exactly 150 characters + "CheckType": [], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert len(check_metadata.CheckTitle) == 150 + + def test_validate_check_title_failure_too_long(self): + """Test CheckTitle validation fails when too long""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "A" * 151, # Too long: 151 characters + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "CheckTitle must not exceed 150 characters, got 151 characters" in str( + exc_info.value + ) + + def test_validate_check_title_failure_starts_with_ensure(self): + """Test CheckTitle validation fails when starting with 'Ensure'""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Ensure S3 buckets have encryption enabled", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "CheckTitle must not start with 'Ensure'" in str(exc_info.value) + + def test_validate_related_url_must_be_empty(self): + """Test RelatedUrl validation fails when not empty""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "https://example.com", # Invalid: must be empty + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "RelatedUrl must be empty" in str(exc_info.value) + + def test_validate_related_url_empty_is_valid(self): + """Test RelatedUrl validation passes when empty""" + valid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.RelatedUrl == "" + + def test_validate_recommendation_url_must_be_hub(self): + """Test Recommendation URL validation fails when not pointing to Prowler Hub""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://docs.aws.amazon.com/some-page", # Invalid: not HUB + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "Remediation Recommendation URL must point to Prowler Hub" in str( + exc_info.value + ) + + def test_validate_recommendation_url_hub_is_valid(self): + """Test Recommendation URL validation passes with Prowler Hub URL""" + valid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert ( + check_metadata.Remediation.Recommendation.Url + == "https://hub.prowler.com/check/test_check" + ) + + def test_validate_recommendation_url_empty_is_valid(self): + """Test Recommendation URL validation passes when empty""" + valid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.Remediation.Recommendation.Url == "" + + def test_validate_check_type_non_aws_must_be_empty(self): + """Test CheckType must be empty for non-AWS providers""" + invalid_metadata = { + "Provider": "azure", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": ["SomeType"], # Invalid: non-AWS must be empty + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "CheckType must be empty for non-AWS providers" in str(exc_info.value) + + def test_validate_check_type_success(self): + """Test CheckType validation with valid check types""" + valid_metadata = { + "Provider": "azure", # Using non-AWS provider to avoid config validation + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.CheckType == [] + + def test_validate_check_type_failure_empty_string(self): + """Test CheckType validation fails with empty string in list""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": ["TTPs/Discovery", ""], # Invalid: empty string in list + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "CheckType list cannot contain empty strings" in str(exc_info.value) + + def test_validate_description_success(self): + """Test Description validation with valid description""" + valid_metadata = { + "Provider": "azure", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "A" * 400, # Exactly 400 characters + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert len(check_metadata.Description) == 400 + + def test_validate_description_failure_too_long(self): + """Test Description validation fails when too long""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "A" * 401, # Too long: 401 characters + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "Description must not exceed 400 characters, got 401 characters" in str( + exc_info.value + ) + + def test_validate_risk_success(self): + """Test Risk validation with valid risk""" + valid_metadata = { + "Provider": "azure", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "A" * 400, # Exactly 400 characters + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert len(check_metadata.Risk) == 400 + + def test_validate_risk_failure_too_long(self): + """Test Risk validation fails when too long""" + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "A" * 401, # Too long: 401 characters + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "Risk must not exceed 400 characters, got 401 characters" in str( + exc_info.value + ) + + def test_validate_check_type_aws_invalid_type(self): + """Test CheckType validation fails with invalid AWS CheckType""" + + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": ["InvalidType"], # Invalid: not in AWS config + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "Invalid CheckType: 'InvalidType'" in str(exc_info.value) + + def test_validate_check_type_aws_valid_hierarchy_path(self): + """Test CheckType validation succeeds with valid AWS CheckType hierarchy path""" + + valid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": ["TTPs/Initial Access"], # Valid: partial path in hierarchy + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.CheckType == ["TTPs/Initial Access"] + + def test_validate_check_type_non_aws_provider(self): + """Test CheckType validation requires empty list for non-AWS providers""" + valid_metadata = { + "Provider": "azure", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [], # Non-AWS providers must have empty CheckType + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.CheckType == [] + + def test_validate_check_type_aws_validation_called(self): + """Test that AWS CheckType validation function works for AWS provider""" + + valid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": ["Effects/Data Exposure"], # Valid AWS CheckType + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.CheckType == ["Effects/Data Exposure"] + + def test_validate_check_type_multiple_types_all_valid(self): + """Test CheckType validation with multiple valid types for AWS provider""" + valid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "TTPs/Discovery", + "Effects/Data Exposure", + ], # Multiple valid AWS types + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + check_metadata = CheckMetadata(**valid_metadata) + assert check_metadata.CheckType == ["TTPs/Discovery", "Effects/Data Exposure"] + + def test_validate_check_type_aws_multiple_types_mixed_validity(self): + """Test CheckType validation with multiple types where one is invalid for AWS""" + + invalid_metadata = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": ["TTPs/Discovery", "InvalidType"], # One valid, one invalid + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**invalid_metadata) + assert "Invalid CheckType: 'InvalidType'" in str(exc_info.value) + def test_additional_urls_valid_empty_list(self): """Test AdditionalURLs with valid empty list (default)""" metadata = CheckMetadata( Provider="aws", CheckID="test_check", CheckTitle="Test Check", - CheckType=["type1"], + CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -344,7 +1810,7 @@ class TestCheckMetada: ResourceType="resource1", Description="Description 1", Risk="risk1", - RelatedUrl="url1", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", @@ -352,9 +1818,12 @@ class TestCheckMetada: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/test_check", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -374,7 +1843,7 @@ class TestCheckMetada: Provider="aws", CheckID="test_check", CheckTitle="Test Check", - CheckType=["type1"], + CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -382,7 +1851,7 @@ class TestCheckMetada: ResourceType="resource1", Description="Description 1", Risk="risk1", - RelatedUrl="url1", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", @@ -390,9 +1859,12 @@ class TestCheckMetada: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/test_check", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -408,7 +1880,9 @@ class TestCheckMetada: Provider="aws", CheckID="test_check", CheckTitle="Test Check", - CheckType=["type1"], + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices" + ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -416,7 +1890,7 @@ class TestCheckMetada: ResourceType="resource1", Description="Description 1", Risk="risk1", - RelatedUrl="url1", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", @@ -424,9 +1898,12 @@ class TestCheckMetada: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/test_check", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -442,7 +1919,9 @@ class TestCheckMetada: Provider="aws", CheckID="test_check", CheckTitle="Test Check", - CheckType=["type1"], + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices" + ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -450,7 +1929,7 @@ class TestCheckMetada: ResourceType="resource1", Description="Description 1", Risk="risk1", - RelatedUrl="url1", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", @@ -458,9 +1937,12 @@ class TestCheckMetada: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/test_check", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -476,7 +1958,9 @@ class TestCheckMetada: Provider="aws", CheckID="test_check", CheckTitle="Test Check", - CheckType=["type1"], + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices" + ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -484,7 +1968,7 @@ class TestCheckMetada: ResourceType="resource1", Description="Description 1", Risk="risk1", - RelatedUrl="url1", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", @@ -492,9 +1976,12 @@ class TestCheckMetada: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/test_check", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -510,7 +1997,9 @@ class TestCheckMetada: Provider="aws", CheckID="test_check", CheckTitle="Test Check", - CheckType=["type1"], + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices" + ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -518,7 +2007,7 @@ class TestCheckMetada: ResourceType="resource1", Description="Description 1", Risk="risk1", - RelatedUrl="url1", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", @@ -526,9 +2015,12 @@ class TestCheckMetada: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/test_check", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -547,7 +2039,7 @@ class TestCheckMetada: Provider="aws", CheckID="test_check_empty_fields", CheckTitle="Test Check with Empty Fields", - CheckType=["type1"], + CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -563,9 +2055,12 @@ class TestCheckMetada: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/test_check", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -583,7 +2078,7 @@ class TestCheckMetada: Provider="aws", CheckID="test_check_defaults", CheckTitle="Test Check with Default Fields", - CheckType=["type1"], + CheckType=["Software and Configuration Checks/AWS Security Best Practices"], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -599,9 +2094,12 @@ class TestCheckMetada: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/test_check", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -620,7 +2118,9 @@ class TestCheckMetada: Provider="aws", CheckID="test_check_none_related_url", CheckTitle="Test Check with None RelatedUrl", - CheckType=["type1"], + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices" + ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -636,9 +2136,12 @@ class TestCheckMetada: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/test_check", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -655,7 +2158,9 @@ class TestCheckMetada: Provider="aws", CheckID="test_check_none_additional_urls", CheckTitle="Test Check with None AdditionalURLs", - CheckType=["type1"], + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices" + ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -663,7 +2168,7 @@ class TestCheckMetada: ResourceType="resource1", Description="Description 1", Risk="risk1", - RelatedUrl="https://example.com", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", @@ -671,9 +2176,12 @@ class TestCheckMetada: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/test_check", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -690,7 +2198,9 @@ class TestCheckMetada: Provider="aws", CheckID="test_check_invalid_additional_urls", CheckTitle="Test Check with Invalid AdditionalURLs", - CheckType=["type1"], + CheckType=[ + "Software and Configuration Checks/AWS Security Best Practices" + ], ServiceName="test", SubServiceName="subservice1", ResourceIdTemplate="template1", @@ -698,7 +2208,7 @@ class TestCheckMetada: ResourceType="resource1", Description="Description 1", Risk="risk1", - RelatedUrl="https://example.com", + RelatedUrl="", Remediation={ "Code": { "CLI": "cli1", @@ -706,9 +2216,12 @@ class TestCheckMetada: "Other": "other1", "Terraform": "terraform1", }, - "Recommendation": {"Text": "text1", "Url": "url1"}, + "Recommendation": { + "Text": "text1", + "Url": "https://hub.prowler.com/check/test_check", + }, }, - Categories=["categoryone"], + Categories=["encryption"], DependsOn=["dependency1"], RelatedTo=["related1"], Notes="notes1", @@ -719,6 +2232,101 @@ class TestCheckMetada: assert "AdditionalURLs must be a list" in str(exc_info.value) +class TestResourceGroupValidator: + """Test class for ResourceGroup validator""" + + def _base_metadata(self, **overrides): + """Helper to build valid metadata with overrides""" + base = { + "Provider": "aws", + "CheckID": "test_check", + "CheckTitle": "Test Check", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "test", + "SubServiceName": "subtest", + "ResourceIdTemplate": "template", + "Severity": "high", + "ResourceType": "TestResource", + "Description": "Test description", + "Risk": "Test risk", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "test command", + "NativeIaC": "test native", + "Other": "test other", + "Terraform": "test terraform", + }, + "Recommendation": { + "Text": "test recommendation", + "Url": "https://hub.prowler.com/check/test_check", + }, + }, + "Categories": ["encryption"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Test notes", + } + base.update(overrides) + return base + + @pytest.mark.parametrize( + "resource_group", + [ + "compute", + "container", + "serverless", + "database", + "storage", + "network", + "IAM", + "messaging", + "security", + "monitoring", + "api_gateway", + "ai_ml", + "governance", + "collaboration", + "devops", + "analytics", + ], + ) + def test_valid_resource_group(self, resource_group): + """Test all valid ResourceGroup values are accepted""" + metadata = CheckMetadata(**self._base_metadata(ResourceGroup=resource_group)) + assert metadata.ResourceGroup == resource_group + + def test_resource_group_empty_string_allowed(self): + """Test that empty string (default) is allowed for ResourceGroup""" + metadata = CheckMetadata(**self._base_metadata(ResourceGroup="")) + assert metadata.ResourceGroup == "" + + def test_resource_group_default_is_empty(self): + """Test that ResourceGroup defaults to empty string when not provided""" + metadata = CheckMetadata(**self._base_metadata()) + assert metadata.ResourceGroup == "" + + def test_resource_group_invalid_value(self): + """Test that invalid ResourceGroup value raises ValidationError""" + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**self._base_metadata(ResourceGroup="invalid_group")) + assert "Invalid ResourceGroup: 'invalid_group'" in str(exc_info.value) + + def test_resource_group_case_sensitive(self): + """Test that ResourceGroup validation is case-sensitive (IAM, not iam)""" + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**self._base_metadata(ResourceGroup="iam")) + assert "Invalid ResourceGroup: 'iam'" in str(exc_info.value) + + def test_resource_group_typo(self): + """Test that typos in ResourceGroup are rejected""" + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**self._base_metadata(ResourceGroup="computee")) + assert "Invalid ResourceGroup: 'computee'" in str(exc_info.value) + + class TestCheck: @mock.patch("prowler.lib.check.models.CheckMetadata.parse_file") def test_verify_names_consistency_all_match(self, mock_parse_file): diff --git a/tests/lib/outputs/asff/asff_test.py b/tests/lib/outputs/asff/asff_test.py index ed74d60521..7895aeebdf 100644 --- a/tests/lib/outputs/asff/asff_test.py +++ b/tests/lib/outputs/asff/asff_test.py @@ -554,7 +554,9 @@ class TestASFF: }, "GeneratorId": "prowler-service_test_check_id", "AwsAccountId": "123456789012", - "Types": ["test-type"], + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], "FirstObservedAt": timestamp, "UpdatedAt": timestamp, "CreatedAt": timestamp, diff --git a/tests/lib/outputs/csv/csv_test.py b/tests/lib/outputs/csv/csv_test.py index 8eae9f02fa..b2b5d390df 100644 --- a/tests/lib/outputs/csv/csv_test.py +++ b/tests/lib/outputs/csv/csv_test.py @@ -29,15 +29,15 @@ class TestCSV: partition="aws", description="Description of the finding", risk="High", - related_url="http://example.com", + related_url="", remediation_recommendation_text="Recommendation text", - remediation_recommendation_url="http://example.com/remediation", + remediation_recommendation_url="https://hub.prowler.com/check/test_check", remediation_code_nativeiac="native-iac-code", remediation_code_terraform="terraform-code", remediation_code_other="other-code", remediation_code_cli="cli-code", compliance={"compliance_key": "compliance_value"}, - categories=["categorya", "categoryb"], + categories=["encryption", "logging"], depends_on=["dependency"], related_to=["related"], additional_urls=[ @@ -65,7 +65,10 @@ class TestCSV: assert output_data["PROVIDER"] == "aws" assert output_data["CHECK_ID"] == "service_test_check_id" assert output_data["CHECK_TITLE"] == "service_test_check_id" - assert output_data["CHECK_TYPE"] == "test-type" + assert ( + output_data["CHECK_TYPE"] + == "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ) assert isinstance(output_data["STATUS"], str) assert output_data["STATUS"] == "PASS" assert output_data["STATUS_EXTENDED"] == "status-extended" @@ -86,11 +89,11 @@ class TestCSV: assert output_data["REGION"] == AWS_REGION_EU_WEST_1 assert output_data["DESCRIPTION"] == "Description of the finding" assert output_data["RISK"] == "High" - assert output_data["RELATED_URL"] == "http://example.com" + assert output_data["RELATED_URL"] == "" assert output_data["REMEDIATION_RECOMMENDATION_TEXT"] == "Recommendation text" assert ( output_data["REMEDIATION_RECOMMENDATION_URL"] - == "http://example.com/remediation" + == "https://hub.prowler.com/check/test_check" ) assert output_data["REMEDIATION_CODE_NATIVEIAC"] == "native-iac-code" assert output_data["REMEDIATION_CODE_TERRAFORM"] == "terraform-code" @@ -98,7 +101,7 @@ class TestCSV: assert output_data["REMEDIATION_CODE_OTHER"] == "other-code" assert isinstance(output_data["COMPLIANCE"], str) assert output_data["COMPLIANCE"] == "compliance_key: compliance_value" - assert output_data["CATEGORIES"] == "categorya | categoryb" + assert output_data["CATEGORIES"] == "encryption | logging" assert output_data["DEPENDS_ON"] == "dependency" assert output_data["RELATED_TO"] == "related" assert ( @@ -123,7 +126,7 @@ class TestCSV: output.batch_write_data_to_file() mock_file.seek(0) - expected_csv = f"AUTH_METHOD;TIMESTAMP;ACCOUNT_UID;ACCOUNT_NAME;ACCOUNT_EMAIL;ACCOUNT_ORGANIZATION_UID;ACCOUNT_ORGANIZATION_NAME;ACCOUNT_TAGS;FINDING_UID;PROVIDER;CHECK_ID;CHECK_TITLE;CHECK_TYPE;STATUS;STATUS_EXTENDED;MUTED;SERVICE_NAME;SUBSERVICE_NAME;SEVERITY;RESOURCE_TYPE;RESOURCE_UID;RESOURCE_NAME;RESOURCE_DETAILS;RESOURCE_TAGS;PARTITION;REGION;DESCRIPTION;RISK;RELATED_URL;REMEDIATION_RECOMMENDATION_TEXT;REMEDIATION_RECOMMENDATION_URL;REMEDIATION_CODE_NATIVEIAC;REMEDIATION_CODE_TERRAFORM;REMEDIATION_CODE_CLI;REMEDIATION_CODE_OTHER;COMPLIANCE;CATEGORIES;DEPENDS_ON;RELATED_TO;NOTES;PROWLER_VERSION;ADDITIONAL_URLS;ACCOUNT_OU_UID;ACCOUNT_OU_NAME\r\nprofile: default;{datetime.now()};123456789012;123456789012;;test-organization-id;test-organization;test-tag:test-value;test-unique-finding;aws;service_test_check_id;service_test_check_id;test-type;PASS;;False;service;;high;test-resource;;;;;aws;eu-west-1;check description;test-risk;test-url;;;;;;;test-compliance: test-compliance;test-category;test-dependency;test-related-to;test-notes;{prowler_version};https://docs.aws.amazon.com/prescriptive-guidance/latest/migration-operations-integration/best-practices.html | https://docs.aws.amazon.com/prescriptive-guidance/latest/migration-operations-integration/introduction.html;ou-abc1-12345678;Production/WebServices\r\n" + expected_csv = f"AUTH_METHOD;TIMESTAMP;ACCOUNT_UID;ACCOUNT_NAME;ACCOUNT_EMAIL;ACCOUNT_ORGANIZATION_UID;ACCOUNT_ORGANIZATION_NAME;ACCOUNT_TAGS;FINDING_UID;PROVIDER;CHECK_ID;CHECK_TITLE;CHECK_TYPE;STATUS;STATUS_EXTENDED;MUTED;SERVICE_NAME;SUBSERVICE_NAME;SEVERITY;RESOURCE_TYPE;RESOURCE_UID;RESOURCE_NAME;RESOURCE_DETAILS;RESOURCE_TAGS;PARTITION;REGION;DESCRIPTION;RISK;RELATED_URL;REMEDIATION_RECOMMENDATION_TEXT;REMEDIATION_RECOMMENDATION_URL;REMEDIATION_CODE_NATIVEIAC;REMEDIATION_CODE_TERRAFORM;REMEDIATION_CODE_CLI;REMEDIATION_CODE_OTHER;COMPLIANCE;CATEGORIES;DEPENDS_ON;RELATED_TO;NOTES;PROWLER_VERSION;ADDITIONAL_URLS;ACCOUNT_OU_UID;ACCOUNT_OU_NAME\r\nprofile: default;{datetime.now()};123456789012;123456789012;;test-organization-id;test-organization;test-tag:test-value;test-unique-finding;aws;service_test_check_id;service_test_check_id;Software and Configuration Checks/AWS Security Best Practices/Network Reachability;PASS;;False;service;;high;test-resource;;;;;aws;eu-west-1;check description;test-risk;;;;;;;;test-compliance: test-compliance;encryption;test-dependency;test-related-to;test-notes;{prowler_version};https://docs.aws.amazon.com/prescriptive-guidance/latest/migration-operations-integration/best-practices.html | https://docs.aws.amazon.com/prescriptive-guidance/latest/migration-operations-integration/introduction.html;ou-abc1-12345678;Production/WebServices\r\n" content = mock_file.read() assert content == expected_csv @@ -201,7 +204,7 @@ class TestCSV: with patch.object(temp_file, "close", return_value=None): csv.batch_write_data_to_file() - expected_csv = f"AUTH_METHOD;TIMESTAMP;ACCOUNT_UID;ACCOUNT_NAME;ACCOUNT_EMAIL;ACCOUNT_ORGANIZATION_UID;ACCOUNT_ORGANIZATION_NAME;ACCOUNT_TAGS;FINDING_UID;PROVIDER;CHECK_ID;CHECK_TITLE;CHECK_TYPE;STATUS;STATUS_EXTENDED;MUTED;SERVICE_NAME;SUBSERVICE_NAME;SEVERITY;RESOURCE_TYPE;RESOURCE_UID;RESOURCE_NAME;RESOURCE_DETAILS;RESOURCE_TAGS;PARTITION;REGION;DESCRIPTION;RISK;RELATED_URL;REMEDIATION_RECOMMENDATION_TEXT;REMEDIATION_RECOMMENDATION_URL;REMEDIATION_CODE_NATIVEIAC;REMEDIATION_CODE_TERRAFORM;REMEDIATION_CODE_CLI;REMEDIATION_CODE_OTHER;COMPLIANCE;CATEGORIES;DEPENDS_ON;RELATED_TO;NOTES;PROWLER_VERSION;ADDITIONAL_URLS;ACCOUNT_OU_UID;ACCOUNT_OU_NAME\nprofile: default;{datetime.now()};123456789012;123456789012;;test-organization-id;test-organization;test-tag:test-value;test-unique-finding;aws;service_test_check_id;service_test_check_id;test-type;PASS;;False;service;;high;test-resource;;;;;aws;eu-west-1;check description;test-risk;test-url;;;;;;;test-compliance: test-compliance;test-category;test-dependency;test-related-to;test-notes;{prowler_version};https://docs.aws.amazon.com/prescriptive-guidance/latest/migration-operations-integration/best-practices.html | https://docs.aws.amazon.com/prescriptive-guidance/latest/migration-operations-integration/introduction.html;ou-abc1-12345678;Production/WebServices\n" + expected_csv = f"AUTH_METHOD;TIMESTAMP;ACCOUNT_UID;ACCOUNT_NAME;ACCOUNT_EMAIL;ACCOUNT_ORGANIZATION_UID;ACCOUNT_ORGANIZATION_NAME;ACCOUNT_TAGS;FINDING_UID;PROVIDER;CHECK_ID;CHECK_TITLE;CHECK_TYPE;STATUS;STATUS_EXTENDED;MUTED;SERVICE_NAME;SUBSERVICE_NAME;SEVERITY;RESOURCE_TYPE;RESOURCE_UID;RESOURCE_NAME;RESOURCE_DETAILS;RESOURCE_TAGS;PARTITION;REGION;DESCRIPTION;RISK;RELATED_URL;REMEDIATION_RECOMMENDATION_TEXT;REMEDIATION_RECOMMENDATION_URL;REMEDIATION_CODE_NATIVEIAC;REMEDIATION_CODE_TERRAFORM;REMEDIATION_CODE_CLI;REMEDIATION_CODE_OTHER;COMPLIANCE;CATEGORIES;DEPENDS_ON;RELATED_TO;NOTES;PROWLER_VERSION;ADDITIONAL_URLS;ACCOUNT_OU_UID;ACCOUNT_OU_NAME\nprofile: default;{datetime.now()};123456789012;123456789012;;test-organization-id;test-organization;test-tag:test-value;test-unique-finding;aws;service_test_check_id;service_test_check_id;Software and Configuration Checks/AWS Security Best Practices/Network Reachability;PASS;;False;service;;high;test-resource;;;;;aws;eu-west-1;check description;test-risk;;;;;;;;test-compliance: test-compliance;encryption;test-dependency;test-related-to;test-notes;{prowler_version};https://docs.aws.amazon.com/prescriptive-guidance/latest/migration-operations-integration/best-practices.html | https://docs.aws.amazon.com/prescriptive-guidance/latest/migration-operations-integration/introduction.html;ou-abc1-12345678;Production/WebServices\n" temp_file.seek(0) diff --git a/tests/lib/outputs/finding_test.py b/tests/lib/outputs/finding_test.py index 1362e0b7fd..00ce2fe1f7 100644 --- a/tests/lib/outputs/finding_test.py +++ b/tests/lib/outputs/finding_test.py @@ -910,16 +910,19 @@ class TestFinding: "provider": "test_provider", "checkid": "service_check_001", "checktitle": "Test Check", - "checktype": ["type1"], + "checktype": [], "servicename": "service", "subservicename": "SubService", "severity": "high", "resourcetype": "TestResource", "description": "A test check", "risk": "High risk", - "relatedurl": "http://example.com", + "relatedurl": "", "remediation": { - "recommendation": {"text": "Fix it", "url": "http://fix.com"}, + "recommendation": { + "text": "Fix it", + "url": "https://hub.prowler.com/check/service_check_001", + }, "code": { "nativeiac": "iac_code", "terraform": "terraform_code", @@ -928,7 +931,7 @@ class TestFinding: }, }, "resourceidtemplate": "template", - "categories": ["cat-one", "cat-two"], + "categories": ["encryption", "logging"], "dependson": ["dep1"], "relatedto": ["rel1"], "notes": "Some notes", @@ -953,22 +956,25 @@ class TestFinding: assert meta.Provider == "test_provider" assert meta.CheckID == "service_check_001" assert meta.CheckTitle == "Test Check" - assert meta.CheckType == ["type1"] + assert meta.CheckType == [] assert meta.ServiceName == "service" assert meta.SubServiceName == "SubService" assert meta.Severity == "high" assert meta.ResourceType == "TestResource" assert meta.Description == "A test check" assert meta.Risk == "High risk" - assert meta.RelatedUrl == "http://example.com" + assert meta.RelatedUrl == "" assert meta.Remediation.Recommendation.Text == "Fix it" - assert meta.Remediation.Recommendation.Url == "http://fix.com" + assert ( + meta.Remediation.Recommendation.Url + == "https://hub.prowler.com/check/service_check_001" + ) assert meta.Remediation.Code.NativeIaC == "iac_code" assert meta.Remediation.Code.Terraform == "terraform_code" assert meta.Remediation.Code.CLI == "cli_code" assert meta.Remediation.Code.Other == "other_code" assert meta.ResourceIdTemplate == "template" - assert meta.Categories == ["cat-one", "cat-two"] + assert meta.Categories == ["encryption", "logging"] assert meta.DependsOn == ["dep1"] assert meta.RelatedTo == ["rel1"] assert meta.Notes == "Some notes" @@ -1034,11 +1040,11 @@ class TestFinding: "dependson": [], "relatedto": [], "categories": [], - "checktitle": "Ensure that Auto provisioning of 'Log Analytics agent for Azure VMs' is Set to 'On'", + "checktitle": "Auto provisioning of Log Analytics agent for Azure VMs should be On", "compliance": None, - "relatedurl": "https://docs.microsoft.com/en-us/azure/security-center/security-center-data-security", + "relatedurl": "", "description": ( - "Ensure that Auto provisioning of 'Log Analytics agent for Azure VMs' is Set to 'On'. " + "Auto provisioning of Log Analytics agent for Azure VMs should be On. " "The Microsoft Monitoring Agent scans for various security-related configurations and events such as system updates, " "OS vulnerabilities, endpoint protection, and provides alerts." ), @@ -1050,9 +1056,9 @@ class TestFinding: "terraform": "", }, "recommendation": { - "url": "https://learn.microsoft.com/en-us/azure/defender-for-cloud/monitoring-components", + "url": "https://hub.prowler.com/check/defender_auto_provisioning_log_analytics_agent_vms_on", "text": ( - "Ensure comprehensive visibility into possible security vulnerabilities, including missing updates, " + "Comprehensive visibility into possible security vulnerabilities, including missing updates, " "misconfigured operating system security settings, and active threats, allowing for timely mitigation and improved overall security posture" ), }, @@ -1099,16 +1105,16 @@ class TestFinding: assert meta.CheckID == "defender_auto_provisioning_log_analytics_agent_vms_on" assert ( meta.CheckTitle - == "Ensure that Auto provisioning of 'Log Analytics agent for Azure VMs' is Set to 'On'" + == "Auto provisioning of Log Analytics agent for Azure VMs should be On" ) assert meta.Severity == "medium" assert meta.ResourceType == "AzureDefenderPlan" assert ( meta.Remediation.Recommendation.Url - == "https://learn.microsoft.com/en-us/azure/defender-for-cloud/monitoring-components" + == "https://hub.prowler.com/check/defender_auto_provisioning_log_analytics_agent_vms_on" ) assert meta.Remediation.Recommendation.Text.startswith( - "Ensure comprehensive visibility" + "Comprehensive visibility" ) expected_segments = [ @@ -1156,7 +1162,7 @@ class TestFinding: "resourcetype": "GCPResourceType", "description": "GCP check description", "risk": "Medium risk", - "relatedurl": "http://gcp.example.com", + "relatedurl": "", "remediation": { "code": { "nativeiac": "iac_code", @@ -1164,10 +1170,13 @@ class TestFinding: "cli": "cli_code", "other": "other_code", }, - "recommendation": {"text": "Fix it", "url": "http://fix-gcp.com"}, + "recommendation": { + "text": "Fix it", + "url": "https://hub.prowler.com/check/service_gcp_check_001", + }, }, "resourceidtemplate": "template", - "categories": ["cat-one", "cat-two"], + "categories": ["encryption", "logging"], "dependson": ["dep1"], "relatedto": ["rel1"], "notes": "Some notes", @@ -1236,7 +1245,7 @@ class TestFinding: "resourcetype": "K8sResourceType", "description": "K8s check description", "risk": "Low risk", - "relatedurl": "http://k8s.example.com", + "relatedurl": "", "remediation": { "code": { "nativeiac": "iac_code", @@ -1244,10 +1253,13 @@ class TestFinding: "cli": "cli_code", "other": "other_code", }, - "recommendation": {"text": "Fix it", "url": "http://fix-k8s.com"}, + "recommendation": { + "text": "Fix it", + "url": "https://hub.prowler.com/check/service_k8s_check_001", + }, }, "resourceidtemplate": "template", - "categories": ["cat-one"], + "categories": ["encryption"], "dependson": [], "relatedto": [], "notes": "K8s notes", @@ -1302,7 +1314,7 @@ class TestFinding: "resourcetype": "M365ResourceType", "description": "M365 check description", "risk": "High risk", - "relatedurl": "http://m365.example.com", + "relatedurl": "", "remediation": { "code": { "nativeiac": "iac_code", @@ -1310,10 +1322,13 @@ class TestFinding: "cli": "cli_code", "other": "other_code", }, - "recommendation": {"text": "Fix it", "url": "http://fix-m365.com"}, + "recommendation": { + "text": "Fix it", + "url": "https://hub.prowler.com/check/service_m365_check_001", + }, }, "resourceidtemplate": "template", - "categories": ["cat-one"], + "categories": ["encryption"], "dependson": [], "relatedto": [], "notes": "M365 notes", diff --git a/tests/lib/outputs/fixtures/fixtures.py b/tests/lib/outputs/fixtures/fixtures.py index ff9e486299..3163f161c7 100644 --- a/tests/lib/outputs/fixtures/fixtures.py +++ b/tests/lib/outputs/fixtures/fixtures.py @@ -25,14 +25,14 @@ def generate_finding_output( partition: str = "aws", description: str = "check description", risk: str = "test-risk", - related_url: str = "test-url", + related_url: str = "", remediation_recommendation_text: str = "", remediation_recommendation_url: str = "", remediation_code_nativeiac: str = "", remediation_code_terraform: str = "", remediation_code_cli: str = "", remediation_code_other: str = "", - categories: list[str] = ["test-category"], + categories: list[str] = ["encryption"], depends_on: list[str] = ["test-dependency"], related_to: list[str] = ["test-related-to"], notes: str = "test-notes", @@ -43,11 +43,19 @@ def generate_finding_output( service_name: str = "service", check_id: str = "service_test_check_id", check_title: str = "service_test_check_id", - check_type: list[str] = ["test-type"], + check_type: list[str] = None, provider_uid: str = None, account_ou_uid: str = "ou-abc1-12345678", account_ou_name: str = "Production/WebServices", ) -> Finding: + if check_type is None: + check_type = ( + [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ] + if provider == "aws" + else [] + ) return Finding( auth_method="profile: default", timestamp=timestamp if timestamp else datetime.now(), diff --git a/tests/lib/outputs/fixtures/metadata.json b/tests/lib/outputs/fixtures/metadata.json index a376d491a9..b26438aac8 100644 --- a/tests/lib/outputs/fixtures/metadata.json +++ b/tests/lib/outputs/fixtures/metadata.json @@ -1,10 +1,10 @@ { "Categories": [ - "cat-one", - "cat-two" + "encryption", + "logging" ], "CheckID": "iam_user_accesskey_unused", - "CheckTitle": "Ensure Access Keys unused are disabled", + "CheckTitle": "Access Keys unused should be disabled", "CheckType": [ "Software and Configuration Checks" ], @@ -25,14 +25,14 @@ "othercheck1", "othercheck2" ], - "Description": "Ensure Access Keys unused are disabled", + "Description": "Access Keys unused should be disabled", "Notes": "additional information", "Provider": "aws", "RelatedTo": [ "othercheck3", "othercheck4" ], - "RelatedUrl": "https://serviceofficialsiteorpageforthissubject", + "RelatedUrl": "", "Remediation": { "Code": { "CLI": "cli command or URL to the cli command location.", @@ -42,7 +42,7 @@ }, "Recommendation": { "Text": "Run sudo yum update and cross your fingers and toes.", - "Url": "https://myfp.com/recommendations/dangerous_things_and_how_to_fix_them.html" + "Url": "https://hub.prowler.com/check/iam_user_accesskey_unused" } }, "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", diff --git a/tests/lib/outputs/ocsf/ocsf_test.py b/tests/lib/outputs/ocsf/ocsf_test.py index 2512654eb3..16ff1ce317 100644 --- a/tests/lib/outputs/ocsf/ocsf_test.py +++ b/tests/lib/outputs/ocsf/ocsf_test.py @@ -65,7 +65,9 @@ class TestOCSF: assert output_data.finding_info.desc == findings[0].metadata.Description assert output_data.finding_info.title == findings[0].metadata.CheckTitle assert output_data.finding_info.uid == findings[0].uid - assert output_data.finding_info.types == ["test-type"] + assert output_data.finding_info.types == [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ] assert output_data.time == int(findings[0].timestamp.timestamp()) assert output_data.time_dt == findings[0].timestamp assert ( @@ -211,8 +213,8 @@ class TestOCSF: "status_detail": "status extended", "status_id": 1, "unmapped": { - "related_url": "test-url", - "categories": ["test-category"], + "related_url": "", + "categories": ["encryption"], "depends_on": ["test-dependency"], "related_to": ["test-related-to"], "additional_urls": [ @@ -232,7 +234,9 @@ class TestOCSF: "desc": "check description", "title": "service_test_check_id", "uid": "test-unique-finding", - "types": ["test-type"], + "types": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Reachability" + ], }, "resources": [ { diff --git a/tests/lib/scan/scan_test.py b/tests/lib/scan/scan_test.py index e6ff948ed7..b0fe82b7b2 100644 --- a/tests/lib/scan/scan_test.py +++ b/tests/lib/scan/scan_test.py @@ -27,15 +27,15 @@ finding = generate_finding_output( partition="aws", description="Description of the finding", risk="High", - related_url="http://example.com", + related_url="", remediation_recommendation_text="Recommendation text", - remediation_recommendation_url="http://example.com/remediation", + remediation_recommendation_url="https://hub.prowler.com/check/test_check", remediation_code_nativeiac="native-iac-code", remediation_code_terraform="terraform-code", remediation_code_other="other-code", remediation_code_cli="cli-code", compliance={"compliance_key": "compliance_value"}, - categories=["categorya", "categoryb"], + categories=["encryption", "logging"], depends_on=["dependency"], related_to=["related"], notes="Notes about the finding", diff --git a/tests/providers/aws/lib/s3/s3_test.py b/tests/providers/aws/lib/s3/s3_test.py index b639a847cd..6112c467b2 100644 --- a/tests/providers/aws/lib/s3/s3_test.py +++ b/tests/providers/aws/lib/s3/s3_test.py @@ -39,15 +39,15 @@ FINDING = generate_finding_output( partition="aws", description="Description of the finding", risk="High", - related_url="http://example.com", + related_url="", remediation_recommendation_text="Recommendation text", - remediation_recommendation_url="http://example.com/remediation", + remediation_recommendation_url="", remediation_code_nativeiac="native-iac-code", remediation_code_terraform="terraform-code", remediation_code_other="other-code", remediation_code_cli="cli-code", compliance={"compliance_key": "compliance_value"}, - categories=["categorya", "categoryb"], + categories=["encryption", "logging"], depends_on=["dependency"], related_to=["related"], notes="Notes about the finding", From e197ad6fb050de3ef3d0816e866cdba05f44e807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Mon, 16 Mar 2026 10:48:15 +0100 Subject: [PATCH 004/366] chore(gitignore): add .claude (#10340) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4dfb137b89..c9bf8f0f6f 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,6 @@ GEMINI.md .codex/skills .github/skills .gemini/skills + +# Claude Code +.claude/* From b509fdf562e932cadf9da4b83617398c99514658 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Mon, 16 Mar 2026 11:03:39 +0100 Subject: [PATCH 005/366] chore(ui): add changelog entry for org dropdown actions (#10317) (#10342) --- ui/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 0d3931aea1..7f2d29e44e 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to the **Prowler UI** are documented in this file. +## [1.21.0] (Prowler UNRELEASED) + +### 🚀 Added + +- Organization and organizational unit row actions (Edit Name, Update Credentials, Test Connections, Delete) in providers table dropdown [(#10317)](https://github.com/prowler-cloud/prowler/pull/10317) + +--- + ## [1.20.0] (Prowler v5.20.0) ### 🚀 Added From e9860f700293b4fcfbf0e401abb186ee42a8bc38 Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Mon, 16 Mar 2026 10:32:06 +0000 Subject: [PATCH 006/366] chore: zizmor in pre-commit (#10343) --- .pre-commit-config.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eb65669765..1858778f12 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,13 @@ repos: args: [--autofix] files: pyproject.toml + ## GITHUB ACTIONS + - repo: https://github.com/zizmorcore/zizmor-pre-commit + rev: v1.6.0 + hooks: + - id: zizmor + files: ^\.github/ + ## BASH - repo: https://github.com/koalaman/shellcheck-precommit rev: v0.10.0 From 2b7b2623c5e64d019e185df3f6c30aa91afc75f8 Mon Sep 17 00:00:00 2001 From: Prowler Bot Date: Mon, 16 Mar 2026 12:02:57 +0100 Subject: [PATCH 007/366] feat(aws): Update regions for AWS services (#10341) Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com> --- .../providers/aws/aws_regions_by_service.json | 126 +++++++++++++----- 1 file changed, 93 insertions(+), 33 deletions(-) diff --git a/prowler/providers/aws/aws_regions_by_service.json b/prowler/providers/aws/aws_regions_by_service.json index db1e4c05ff..3a414e5f77 100644 --- a/prowler/providers/aws/aws_regions_by_service.json +++ b/prowler/providers/aws/aws_regions_by_service.json @@ -224,6 +224,23 @@ "aws-us-gov": [] } }, + "aiops": { + "regions": { + "aws": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-5", + "ap-southeast-7", + "eu-north-1", + "eu-south-2" + ], + "aws-cn": [], + "aws-eusc": [], + "aws-us-gov": [] + } + }, "aiq": { "regions": { "aws": [ @@ -1567,12 +1584,14 @@ "bedrock-data-automation": { "regions": { "aws": [ + "ap-northeast-1", "ap-south-1", "ap-southeast-2", "eu-central-1", "eu-west-1", "eu-west-2", "us-east-1", + "us-east-2", "us-west-2" ], "aws-cn": [], @@ -1614,6 +1633,16 @@ ] } }, + "billing": { + "regions": { + "aws": [ + "us-east-1" + ], + "aws-cn": [], + "aws-eusc": [], + "aws-us-gov": [] + } + }, "billingconductor": { "regions": { "aws": [ @@ -2727,6 +2756,7 @@ "aws": [ "af-south-1", "ap-east-1", + "ap-east-2", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", @@ -2737,6 +2767,7 @@ "ap-southeast-3", "ap-southeast-4", "ap-southeast-5", + "ap-southeast-6", "ap-southeast-7", "ca-central-1", "ca-west-1", @@ -2821,6 +2852,7 @@ "aws": [ "af-south-1", "ap-east-1", + "ap-east-2", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", @@ -2831,6 +2863,7 @@ "ap-southeast-3", "ap-southeast-4", "ap-southeast-5", + "ap-southeast-6", "ap-southeast-7", "ca-central-1", "ca-west-1", @@ -3729,10 +3762,12 @@ "ap-southeast-1", "ap-southeast-2", "ap-southeast-3", + "ap-southeast-4", "ap-southeast-5", "ap-southeast-7", "ca-central-1", "eu-central-1", + "eu-central-2", "eu-north-1", "eu-south-1", "eu-south-2", @@ -3857,6 +3892,10 @@ "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", + "ap-southeast-2", + "ap-southeast-4", + "ca-central-1", + "ca-west-1", "eu-central-1", "eu-west-1", "eu-west-2", @@ -5080,6 +5119,7 @@ "ap-southeast-3", "ap-southeast-4", "ap-southeast-5", + "ap-southeast-6", "ap-southeast-7", "ca-central-1", "ca-west-1", @@ -5817,6 +5857,7 @@ "ap-southeast-3", "ap-southeast-4", "ap-southeast-5", + "ap-southeast-6", "ap-southeast-7", "ca-central-1", "ca-west-1", @@ -6225,25 +6266,6 @@ "aws-us-gov": [] } }, - "iotanalytics": { - "regions": { - "aws": [ - "ap-northeast-1", - "ap-south-1", - "ap-southeast-2", - "eu-central-1", - "eu-west-1", - "us-east-1", - "us-east-2", - "us-west-2" - ], - "aws-cn": [ - "cn-north-1" - ], - "aws-eusc": [], - "aws-us-gov": [] - } - }, "iotdeviceadvisor": { "regions": { "aws": [ @@ -7059,16 +7081,6 @@ ] } }, - "lex-models": { - "regions": { - "aws": [ - "us-east-1" - ], - "aws-cn": [], - "aws-eusc": [], - "aws-us-gov": [] - } - }, "lex-runtime": { "regions": { "aws": [ @@ -7589,6 +7601,18 @@ ] } }, + "marketplace-agreement": { + "regions": { + "aws": [ + "us-east-1" + ], + "aws-cn": [], + "aws-eusc": [ + "eusc-de-east-1" + ], + "aws-us-gov": [] + } + }, "marketplace-catalog": { "regions": { "aws": [ @@ -7700,6 +7724,7 @@ "ap-southeast-1", "ap-southeast-2", "ap-southeast-4", + "ap-southeast-5", "ca-central-1", "eu-central-1", "eu-north-1", @@ -7733,6 +7758,7 @@ "ap-southeast-1", "ap-southeast-2", "ap-southeast-4", + "ap-southeast-5", "ca-central-1", "eu-central-1", "eu-north-1", @@ -8225,14 +8251,19 @@ "mwaa-serverless": { "regions": { "aws": [ + "af-south-1", + "ap-east-1", "ap-northeast-1", "ap-northeast-2", "ap-south-1", "ap-southeast-1", "ap-southeast-2", + "ap-southeast-7", "ca-central-1", "eu-central-1", + "eu-central-2", "eu-north-1", + "eu-south-2", "eu-west-1", "eu-west-2", "eu-west-3", @@ -8367,7 +8398,9 @@ "cn-north-1", "cn-northwest-1" ], - "aws-eusc": [], + "aws-eusc": [ + "eusc-de-east-1" + ], "aws-us-gov": [ "us-gov-east-1", "us-gov-west-1" @@ -8601,22 +8634,33 @@ "observabilityadmin": { "regions": { "aws": [ + "af-south-1", "ap-east-1", + "ap-east-2", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-south-1", + "ap-south-2", "ap-southeast-1", "ap-southeast-2", + "ap-southeast-3", + "ap-southeast-4", "ap-southeast-5", + "ap-southeast-6", "ap-southeast-7", "ca-central-1", + "ca-west-1", "eu-central-1", + "eu-central-2", "eu-north-1", + "eu-south-1", "eu-south-2", "eu-west-1", "eu-west-2", "eu-west-3", + "il-central-1", + "mx-central-1", "sa-east-1", "us-east-1", "us-east-2", @@ -8632,8 +8676,10 @@ "regions": { "aws": [ "ap-northeast-1", + "ap-southeast-2", "ca-central-1", "eu-central-1", + "eu-west-1", "us-east-1", "us-east-2", "us-west-2" @@ -9056,12 +9102,14 @@ "regions": { "aws": [ "ap-northeast-1", + "ap-south-1", "ap-southeast-1", "ap-southeast-2", "eu-central-1", "eu-north-1", "eu-west-1", "eu-west-2", + "eu-west-3", "us-east-1", "us-east-2", "us-west-2" @@ -9438,7 +9486,10 @@ ], "aws-cn": [], "aws-eusc": [], - "aws-us-gov": [] + "aws-us-gov": [ + "us-gov-east-1", + "us-gov-west-1" + ] } }, "quicksight": { @@ -9451,6 +9502,7 @@ "ap-southeast-1", "ap-southeast-2", "ap-southeast-3", + "ap-southeast-5", "ca-central-1", "eu-central-1", "eu-central-2", @@ -10430,7 +10482,9 @@ "us-west-2" ], "aws-cn": [], - "aws-eusc": [], + "aws-eusc": [ + "eusc-de-east-1" + ], "aws-us-gov": [ "us-gov-east-1", "us-gov-west-1" @@ -10747,6 +10801,7 @@ "ap-southeast-3", "ap-southeast-4", "ap-southeast-5", + "ap-southeast-6", "ap-southeast-7", "ca-central-1", "ca-west-1", @@ -10772,7 +10827,9 @@ "cn-north-1", "cn-northwest-1" ], - "aws-eusc": [], + "aws-eusc": [ + "eusc-de-east-1" + ], "aws-us-gov": [ "us-gov-east-1", "us-gov-west-1" @@ -11984,6 +12041,7 @@ "ap-southeast-3", "ap-southeast-4", "ap-southeast-5", + "ap-southeast-6", "ap-southeast-7", "ca-central-1", "ca-west-1", @@ -12422,6 +12480,7 @@ "eu-west-2", "eu-west-3", "me-central-1", + "sa-east-1", "us-east-1", "us-east-2", "us-west-2" @@ -12503,6 +12562,7 @@ "ap-southeast-2", "ca-central-1", "eu-central-1", + "eu-central-2", "eu-north-1", "eu-west-1", "eu-west-2", From 361f8548bfb45f564c0a36fb6fbe1257d99af354 Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Mon, 16 Mar 2026 12:14:58 +0100 Subject: [PATCH 008/366] feat(azure): add 'entra_conditional_access_policy_require_mfa_for_admin_portals' check and update compliance (#10330) --- prowler/CHANGELOG.md | 1 + prowler/compliance/azure/cis_3.0_azure.json | 2 +- prowler/compliance/azure/cis_4.0_azure.json | 2 +- prowler/compliance/azure/cis_5.0_azure.json | 2 +- .../azure/prowler_threatscore_azure.json | 2 +- prowler/providers/azure/config.py | 5 +- .../__init__.py | 0 ...equire_mfa_for_admin_portals.metadata.json | 38 +++ ...ss_policy_require_mfa_for_admin_portals.py | 44 +++ ...quire_mfa_for_management_api.metadata.json | 12 +- ...licy_require_mfa_for_admin_portals_test.py | 280 ++++++++++++++++++ 11 files changed, 379 insertions(+), 9 deletions(-) create mode 100644 prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/__init__.py create mode 100644 prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals.metadata.json create mode 100644 prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals.py create mode 100644 tests/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index aa1b7b67d4..df49d1fa25 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. - `entra_conditional_access_policy_device_code_flow_blocked` check for M365 provider [(#10218)](https://github.com/prowler-cloud/prowler/pull/10218) - CheckMetadata Pydantic validators [(#8584)](https://github.com/prowler-cloud/prowler/pull/8583) +- `entra_conditional_access_policy_require_mfa_for_admin_portals` check for Azure provider and update CIS compliance [(#10330)](https://github.com/prowler-cloud/prowler/pull/10330) ### 🔄 Changed diff --git a/prowler/compliance/azure/cis_3.0_azure.json b/prowler/compliance/azure/cis_3.0_azure.json index 3e70009d72..90fa5bfea0 100644 --- a/prowler/compliance/azure/cis_3.0_azure.json +++ b/prowler/compliance/azure/cis_3.0_azure.json @@ -752,7 +752,7 @@ "Id": "2.2.8", "Description": "Ensure Multi-factor Authentication is Required to access Microsoft Admin Portals", "Checks": [ - "defender_ensure_defender_for_server_is_on" + "entra_conditional_access_policy_require_mfa_for_admin_portals" ], "Attributes": [ { diff --git a/prowler/compliance/azure/cis_4.0_azure.json b/prowler/compliance/azure/cis_4.0_azure.json index 0df135a546..3777d24663 100644 --- a/prowler/compliance/azure/cis_4.0_azure.json +++ b/prowler/compliance/azure/cis_4.0_azure.json @@ -1036,7 +1036,7 @@ "Id": "6.2.7", "Description": "Ensure that multifactor authentication is required to access Microsoft Admin Portals", "Checks": [ - "defender_ensure_defender_for_server_is_on" + "entra_conditional_access_policy_require_mfa_for_admin_portals" ], "Attributes": [ { diff --git a/prowler/compliance/azure/cis_5.0_azure.json b/prowler/compliance/azure/cis_5.0_azure.json index 4a7172de77..f786ebd3e2 100644 --- a/prowler/compliance/azure/cis_5.0_azure.json +++ b/prowler/compliance/azure/cis_5.0_azure.json @@ -472,7 +472,7 @@ "Id": "5.2.7", "Description": "Ensure that multifactor authentication is required to access Microsoft Admin Portals", "Checks": [ - "defender_ensure_defender_for_server_is_on" + "entra_conditional_access_policy_require_mfa_for_admin_portals" ], "Attributes": [ { diff --git a/prowler/compliance/azure/prowler_threatscore_azure.json b/prowler/compliance/azure/prowler_threatscore_azure.json index 1eabf7d582..aa801887b2 100644 --- a/prowler/compliance/azure/prowler_threatscore_azure.json +++ b/prowler/compliance/azure/prowler_threatscore_azure.json @@ -63,7 +63,7 @@ "Id": "1.1.4", "Description": "Ensure Multi-factor Authentication is Required to access Microsoft Admin Portals", "Checks": [ - "defender_ensure_defender_for_server_is_on" + "entra_conditional_access_policy_require_mfa_for_admin_portals" ], "Attributes": [ { diff --git a/prowler/providers/azure/config.py b/prowler/providers/azure/config.py index c9c9f4823a..e1aa257a97 100644 --- a/prowler/providers/azure/config.py +++ b/prowler/providers/azure/config.py @@ -1,7 +1,10 @@ from uuid import UUID -# Service management API +# Conditional Access target resource identifiers +# (Graph API includeApplications values) WINDOWS_AZURE_SERVICE_MANAGEMENT_API = "797f4846-ba00-4fd7-ba43-dac1f8f63013" +# Named app group — Graph API returns this literal string, not a GUID +MICROSOFT_ADMIN_PORTALS = "MicrosoftAdminPortals" # Authorization policy roles GUEST_USER_ACCESS_NO_RESTRICTICTED = UUID("a0b1b346-4d3e-4e8b-98f8-753987be4970") diff --git a/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/__init__.py b/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals.metadata.json b/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals.metadata.json new file mode 100644 index 0000000000..d12df2e5e6 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals.metadata.json @@ -0,0 +1,38 @@ +{ + "Provider": "azure", + "CheckID": "entra_conditional_access_policy_require_mfa_for_admin_portals", + "CheckTitle": "Conditional Access policy requires MFA for Microsoft Admin Portals", + "CheckType": [], + "ServiceName": "entra", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "NotDefined", + "ResourceGroup": "IAM", + "Description": "**Microsoft Entra Conditional Access** is evaluated for a policy that requires **multifactor authentication** when accessing **Microsoft Admin Portals** (Microsoft 365 Admin Center, Microsoft Entra Admin Center, Microsoft Exchange Admin Center, etc.). The check confirms an enabled policy targets **All users**, includes the Microsoft Admin Portals app, and enforces an **MFA** grant control.", + "Risk": "Without **MFA** on admin portals, attackers with stolen credentials can access **Microsoft 365 Admin Center**, **Entra Admin Center**, or **Exchange Admin Center** to modify tenant settings, escalate privileges, and exfiltrate data. This directly impacts **confidentiality**, **integrity**, and **availability** of all services managed through those portals.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://learn.microsoft.com/en-us/entra/identity/conditional-access/how-to-policy-mfa-admin-portals", + "https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-cloud-apps" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "1. Sign in to the Microsoft Entra admin center\n2. Go to Protection > Conditional Access > Policies\n3. Click + New policy and enter a name\n4. Under Users > Include, select All users\n5. Under Exclude, check Users and groups and select break-glass / non-interactive service accounts\n6. Under Target resources > Include, click Select apps, then select Microsoft Admin Portals\n7. Under Grant, select Grant access and check Require multifactor authentication\n8. Set Enable policy to Report-only, click Create\n9. After testing, change Enable policy from Report-only to On", + "Terraform": "```hcl\nresource \"azuread_conditional_access_policy\" \"\" {\n display_name = \"\"\n state = \"enabled\"\n\n conditions {\n users {\n included_users = [\"All\"]\n excluded_users = [\"\"]\n }\n applications {\n included_applications = [\"MicrosoftAdminPortals\"] # Critical: Microsoft Admin Portals\n }\n }\n\n grant_controls {\n operator = \"OR\"\n built_in_controls = [\"mfa\"] # Critical: requires MFA\n }\n}\n```" + }, + "Recommendation": { + "Text": "Create a **Conditional Access policy** that requires **MFA** for the **Microsoft Admin Portals** app targeting **All users**. Exclude only **break-glass** emergency accounts and non-interactive service principals. Test in **Report-only** mode before enforcing. Prefer **phishing-resistant** MFA methods (FIDO2, passkeys) and apply **least privilege** principles.", + "Url": "https://hub.prowler.com/check/entra_conditional_access_policy_require_mfa_for_admin_portals" + } + }, + "Categories": [ + "identity-access", + "e3" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Conditional Access policies require Microsoft Entra ID P1 or P2 licenses. Similarly, they may require additional overhead to maintain if users lose access to their MFA. Any users or groups which are granted an exception to this policy should be carefully tracked, be granted only minimal necessary privileges, and conditional access exceptions should be regularly reviewed or investigated." +} diff --git a/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals.py b/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals.py new file mode 100644 index 0000000000..30ba990ec6 --- /dev/null +++ b/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals.py @@ -0,0 +1,44 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.config import MICROSOFT_ADMIN_PORTALS +from prowler.providers.azure.services.entra.entra_client import entra_client + + +class entra_conditional_access_policy_require_mfa_for_admin_portals(Check): + def execute(self) -> Check_Report_Azure: + findings = [] + + for ( + tenant_name, + conditional_access_policies, + ) in entra_client.conditional_access_policy.items(): + for policy in conditional_access_policies.values(): + if ( + policy.state == "enabled" + and "All" in policy.users["include"] + and MICROSOFT_ADMIN_PORTALS in policy.target_resources["include"] + and any( + "mfa" in access_control.lower() + for access_control in policy.access_controls["grant"] + ) + ): + report = Check_Report_Azure( + metadata=self.metadata(), resource=policy + ) + report.subscription = f"Tenant: {tenant_name}" + report.status = "PASS" + report.status_extended = "Conditional Access Policy requires MFA for Microsoft Admin Portals." + break + else: + report = Check_Report_Azure( + metadata=self.metadata(), + resource=conditional_access_policies, + ) + report.subscription = f"Tenant: {tenant_name}" + report.resource_name = "Conditional Access Policy" + report.resource_id = "Conditional Access Policy" + report.status = "FAIL" + report.status_extended = "Conditional Access Policy does not require MFA for Microsoft Admin Portals." + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_management_api/entra_conditional_access_policy_require_mfa_for_management_api.metadata.json b/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_management_api/entra_conditional_access_policy_require_mfa_for_management_api.metadata.json index 5d0d2cacb7..6304f8676b 100644 --- a/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_management_api/entra_conditional_access_policy_require_mfa_for_management_api.metadata.json +++ b/prowler/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_management_api/entra_conditional_access_policy_require_mfa_for_management_api.metadata.json @@ -9,15 +9,19 @@ "Severity": "medium", "ResourceType": "NotDefined", "ResourceGroup": "IAM", - "Description": "This recommendation ensures that users accessing the Windows Azure Service Management API (i.e. Azure Powershell, Azure CLI, Azure Resource Manager API, etc.) are required to use multifactor authentication (MFA) credentials when accessing resources through the Windows Azure Service Management API.", - "Risk": "Administrative access to the Windows Azure Service Management API should be secured with a higher level of scrutiny to authenticating mechanisms. Enabling multifactor authentication is recommended to reduce the potential for abuse of Administrative actions, and to prevent intruders or compromised admin credentials from changing administrative settings.", + "Description": "**Microsoft Entra Conditional Access** is evaluated for a policy that requires **multifactor authentication** when accessing the **Windows Azure Service Management API** (Azure PowerShell, Azure CLI, Azure Resource Manager API, etc.). The check confirms an enabled policy targets **All users**, includes the Service Management API app, and enforces an **MFA** grant control.", + "Risk": "Without **MFA** on the Service Management API, attackers with stolen credentials can use **Azure CLI**, **PowerShell**, or the **Resource Manager API** to modify infrastructure, escalate privileges, and exfiltrate data. This directly impacts **confidentiality**, **integrity**, and **availability** of all Azure resources managed through the API.", "RelatedUrl": "", + "AdditionalURLs": [ + "https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-conditional-access-policy-azure-management", + "https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-cloud-apps" + ], "Remediation": { "Code": { "CLI": "", "NativeIaC": "", - "Other": "", - "Terraform": "" + "Other": "1. Sign in to the Microsoft Entra admin center\n2. Go to Protection > Conditional Access > Policies\n3. Click + New policy and enter a name\n4. Under Users > Include, select All users\n5. Under Exclude, check Users and groups and select break-glass / non-interactive service accounts\n6. Under Target resources > Include, click Select apps, then select Windows Azure Service Management API\n7. Under Grant, select Grant access and check Require multifactor authentication\n8. Set Enable policy to Report-only, click Create\n9. After testing, change Enable policy from Report-only to On", + "Terraform": "```hcl\nresource \"azuread_conditional_access_policy\" \"\" {\n display_name = \"\"\n state = \"enabled\"\n\n conditions {\n users {\n included_users = [\"All\"]\n excluded_users = [\"\"]\n }\n applications {\n included_applications = [\"797f4846-ba00-4fd7-ba43-dac1f8f63013\"] # Critical: Windows Azure Service Management API\n }\n }\n\n grant_controls {\n operator = \"OR\"\n built_in_controls = [\"mfa\"] # Critical: requires MFA\n }\n}\n```" }, "Recommendation": { "Text": "1. From the Azure Admin Portal dashboard, open Microsoft Entra ID. 2. Click Security in the Entra ID blade. 3. Click Conditional Access in the Security blade. 4. Click Policies in the Conditional Access blade. 5. Click + New policy. 6. Enter a name for the policy. 7. Click the blue text under Users. 8. Under Include, select All users. 9. Under Exclude, check Users and groups. 10. Select users or groups to be exempted from this policy (e.g. break-glass emergency accounts, and non-interactive service accounts) then click the Select button. 11. Click the blue text under Target Resources. 12. Under Include, click the Select apps radio button. 13. Click the blue text under Select. 14. Check the box next to Windows Azure Service Management APIs then click the Select button. 15. Click the blue text under Grant. 16. Under Grant access check the box for Require multifactor authentication then click the Select button. 17. Before creating, set Enable policy to Report-only. 18. Click Create. After testing the policy in report-only mode, update the Enable policy setting from Report-only to On.", diff --git a/tests/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals_test.py b/tests/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals_test.py new file mode 100644 index 0000000000..3909b80568 --- /dev/null +++ b/tests/providers/azure/services/entra/entra_conditional_access_policy_require_mfa_for_admin_portals/entra_conditional_access_policy_require_mfa_for_admin_portals_test.py @@ -0,0 +1,280 @@ +from unittest import mock +from uuid import uuid4 + +from tests.providers.azure.azure_fixtures import DOMAIN, set_mocked_azure_provider + + +class Test_entra_conditional_access_policy_require_mfa_for_admin_portals: + def test_entra_no_subscriptions(self): + entra_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_client", + new=entra_client, + ), + ): + from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals import ( + entra_conditional_access_policy_require_mfa_for_admin_portals, + ) + + entra_client.conditional_access_policy = {} + + check = entra_conditional_access_policy_require_mfa_for_admin_portals() + result = check.execute() + assert len(result) == 0 + + def test_entra_tenant_no_policies(self): + entra_client = mock.MagicMock + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_client", + new=entra_client, + ), + ): + from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals import ( + entra_conditional_access_policy_require_mfa_for_admin_portals, + ) + + entra_client.conditional_access_policy = {DOMAIN: {}} + + check = entra_conditional_access_policy_require_mfa_for_admin_portals() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: {DOMAIN}" + assert result[0].resource_name == "Conditional Access Policy" + assert result[0].resource_id == "Conditional Access Policy" + assert ( + result[0].status_extended + == "Conditional Access Policy does not require MFA for Microsoft Admin Portals." + ) + + def test_entra_tenant_policy_no_mfa(self): + entra_client = mock.MagicMock + policy_id = str(uuid4()) + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_client", + new=entra_client, + ), + ): + from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals import ( + entra_conditional_access_policy_require_mfa_for_admin_portals, + ) + from prowler.providers.azure.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + policy = ConditionalAccessPolicy( + id=policy_id, + name="Test Policy", + state="enabled", + users={"include": ["All"]}, + target_resources={"include": ["MicrosoftAdminPortals"]}, + access_controls={"grant": ["grant"]}, + ) + + entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}} + + check = entra_conditional_access_policy_require_mfa_for_admin_portals() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: {DOMAIN}" + assert result[0].resource_name == "Conditional Access Policy" + assert result[0].resource_id == "Conditional Access Policy" + assert ( + result[0].status_extended + == "Conditional Access Policy does not require MFA for Microsoft Admin Portals." + ) + + def test_entra_tenant_policy_mfa(self): + entra_client = mock.MagicMock + policy_id = str(uuid4()) + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_client", + new=entra_client, + ), + ): + from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals import ( + entra_conditional_access_policy_require_mfa_for_admin_portals, + ) + from prowler.providers.azure.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + policy = ConditionalAccessPolicy( + id=policy_id, + name="Test Policy", + state="enabled", + users={"include": ["All"]}, + target_resources={"include": ["MicrosoftAdminPortals"]}, + access_controls={"grant": ["grant", "MFA"]}, + ) + + entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}} + + check = entra_conditional_access_policy_require_mfa_for_admin_portals() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].subscription == f"Tenant: {DOMAIN}" + assert result[0].resource_name == "Test Policy" + assert result[0].resource_id == policy_id + assert ( + result[0].status_extended + == "Conditional Access Policy requires MFA for Microsoft Admin Portals." + ) + + def test_entra_tenant_policy_mfa_disabled(self): + entra_client = mock.MagicMock + policy_id = str(uuid4()) + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_client", + new=entra_client, + ), + ): + from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals import ( + entra_conditional_access_policy_require_mfa_for_admin_portals, + ) + from prowler.providers.azure.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + policy = ConditionalAccessPolicy( + id=policy_id, + name="Test Policy", + state="disabled", + users={"include": ["All"]}, + target_resources={"include": ["MicrosoftAdminPortals"]}, + access_controls={"grant": ["grant", "MFA"]}, + ) + + entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}} + + check = entra_conditional_access_policy_require_mfa_for_admin_portals() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: {DOMAIN}" + assert result[0].resource_name == "Conditional Access Policy" + assert result[0].resource_id == "Conditional Access Policy" + assert ( + result[0].status_extended + == "Conditional Access Policy does not require MFA for Microsoft Admin Portals." + ) + + def test_entra_tenant_policy_mfa_no_target(self): + entra_client = mock.MagicMock + policy_id = str(uuid4()) + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_client", + new=entra_client, + ), + ): + from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals import ( + entra_conditional_access_policy_require_mfa_for_admin_portals, + ) + from prowler.providers.azure.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + policy = ConditionalAccessPolicy( + id=policy_id, + name="Test Policy", + state="enabled", + users={"include": ["All"]}, + target_resources={"include": []}, + access_controls={"grant": ["grant", "MFA"]}, + ) + + entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}} + + check = entra_conditional_access_policy_require_mfa_for_admin_portals() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: {DOMAIN}" + assert result[0].resource_name == "Conditional Access Policy" + assert result[0].resource_id == "Conditional Access Policy" + assert ( + result[0].status_extended + == "Conditional Access Policy does not require MFA for Microsoft Admin Portals." + ) + + def test_entra_tenant_policy_mfa_no_users(self): + entra_client = mock.MagicMock + policy_id = str(uuid4()) + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_client", + new=entra_client, + ), + ): + from prowler.providers.azure.services.entra.entra_conditional_access_policy_require_mfa_for_admin_portals.entra_conditional_access_policy_require_mfa_for_admin_portals import ( + entra_conditional_access_policy_require_mfa_for_admin_portals, + ) + from prowler.providers.azure.services.entra.entra_service import ( + ConditionalAccessPolicy, + ) + + policy = ConditionalAccessPolicy( + id=policy_id, + name="Test Policy", + state="enabled", + users={"include": []}, + target_resources={"include": ["MicrosoftAdminPortals"]}, + access_controls={"grant": ["grant", "MFA"]}, + ) + + entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}} + + check = entra_conditional_access_policy_require_mfa_for_admin_portals() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].subscription == f"Tenant: {DOMAIN}" + assert result[0].resource_name == "Conditional Access Policy" + assert result[0].resource_id == "Conditional Access Policy" + assert ( + result[0].status_extended + == "Conditional Access Policy does not require MFA for Microsoft Admin Portals." + ) From ad02801c745e313dbeb068d9859192df8775d8c9 Mon Sep 17 00:00:00 2001 From: Josema Camacho Date: Mon, 16 Mar 2026 12:34:58 +0100 Subject: [PATCH 009/366] refactor(attack-paths): complete migration to private graph labels and properties (phase 2) (#10268) --- api/CHANGELOG.md | 1 + api/src/backend/api/attack_paths/database.py | 5 +-- .../backend/api/attack_paths/queries/aws.py | 14 ++++---- .../api/attack_paths/queries/schema.py | 4 +-- .../backend/api/attack_paths/views_helpers.py | 10 ++++-- .../backend/api/tests/test_attack_paths.py | 32 ++++++++++--------- .../backend/tasks/jobs/attack_paths/config.py | 28 ++++------------ .../tasks/jobs/attack_paths/findings.py | 4 --- .../tasks/jobs/attack_paths/indexes.py | 11 +++---- .../tasks/jobs/attack_paths/queries.py | 26 +++++++-------- .../backend/tasks/jobs/attack_paths/sync.py | 2 -- .../tasks/tests/test_attack_paths_scan.py | 26 ++++----------- 12 files changed, 66 insertions(+), 97 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index ab63e8283b..45e520dc16 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the **Prowler API** are documented in this file. ### 🔄 Changed - Attack Paths: Migrate network exposure queries from APOC to standard openCypher for Neo4j and Neptune compatibility [(#10266)](https://github.com/prowler-cloud/prowler/pull/10266) +- Attack Paths: Complete migration to private graph labels and properties, removing deprecated dual-write support [(#10268)](https://github.com/prowler-cloud/prowler/pull/10268) - `POST /api/v1/providers` returns `409 Conflict` if already exists [(#10293)](https://github.com/prowler-cloud/prowler/pull/10293) ### 🐞 Fixed diff --git a/api/src/backend/api/attack_paths/database.py b/api/src/backend/api/attack_paths/database.py index eb82912267..25172152e9 100644 --- a/api/src/backend/api/attack_paths/database.py +++ b/api/src/backend/api/attack_paths/database.py @@ -17,7 +17,8 @@ from api.attack_paths.retryable_session import RetryableSession from config.env import env from tasks.jobs.attack_paths.config import ( BATCH_SIZE, - DEPRECATED_PROVIDER_RESOURCE_LABEL, + PROVIDER_ID_PROPERTY, + PROVIDER_RESOURCE_LABEL, ) # Without this Celery goes crazy with Neo4j logging @@ -178,7 +179,7 @@ def drop_subgraph(database: str, provider_id: str) -> int: while deleted_count > 0: result = session.run( f""" - MATCH (n:{DEPRECATED_PROVIDER_RESOURCE_LABEL} {{provider_id: $provider_id}}) + MATCH (n:{PROVIDER_RESOURCE_LABEL} {{{PROVIDER_ID_PROPERTY}: $provider_id}}) WITH n LIMIT $batch_size DETACH DELETE n RETURN COUNT(n) AS deleted_nodes_count diff --git a/api/src/backend/api/attack_paths/queries/aws.py b/api/src/backend/api/attack_paths/queries/aws.py index 1a442f7bd6..3dac99a767 100644 --- a/api/src/backend/api/attack_paths/queries/aws.py +++ b/api/src/backend/api/attack_paths/queries/aws.py @@ -3,7 +3,7 @@ from api.attack_paths.queries.types import ( AttackPathsQueryDefinition, AttackPathsQueryParameterDefinition, ) -from tasks.jobs.attack_paths.config import PROWLER_FINDING_LABEL +from tasks.jobs.attack_paths.config import PROVIDER_ID_PROPERTY, PROWLER_FINDING_LABEL # Custom Attack Path Queries @@ -16,7 +16,7 @@ AWS_INTERNET_EXPOSED_EC2_SENSITIVE_S3_ACCESS = AttackPathsQueryDefinition( description="Detect EC2 instances with SSH exposed to the internet that can assume higher-privileged roles to read tagged sensitive S3 buckets despite bucket-level public access blocks.", provider="aws", cypher=f""" - OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}}) + OPTIONAL MATCH (internet:Internet {{{PROVIDER_ID_PROPERTY}: $provider_id}}) MATCH path_s3 = (aws:AWSAccount {{id: $provider_uid}})--(s3:S3Bucket)--(t:AWSTag) WHERE toLower(t.key) = toLower($tag_key) AND toLower(t.value) = toLower($tag_value) @@ -179,7 +179,7 @@ AWS_EC2_INSTANCES_INTERNET_EXPOSED = AttackPathsQueryDefinition( description="Find EC2 instances flagged as exposed to the internet within the selected account.", provider="aws", cypher=f""" - OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}}) + OPTIONAL MATCH (internet:Internet {{{PROVIDER_ID_PROPERTY}: $provider_id}}) MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(ec2:EC2Instance) WHERE ec2.exposed_internet = true @@ -201,7 +201,7 @@ AWS_SECURITY_GROUPS_OPEN_INTERNET_FACING = AttackPathsQueryDefinition( description="Find internet-facing resources associated with security groups that allow inbound access from '0.0.0.0/0'.", provider="aws", cypher=f""" - OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}}) + OPTIONAL MATCH (internet:Internet {{{PROVIDER_ID_PROPERTY}: $provider_id}}) // Match EC2 instances that are internet-exposed with open security groups (0.0.0.0/0) MATCH path_ec2 = (aws:AWSAccount {{id: $provider_uid}})--(ec2:EC2Instance)--(sg:EC2SecurityGroup)--(ipi:IpPermissionInbound)--(ir:IpRange) @@ -225,7 +225,7 @@ AWS_CLASSIC_ELB_INTERNET_EXPOSED = AttackPathsQueryDefinition( description="Find Classic Load Balancers exposed to the internet along with their listeners.", provider="aws", cypher=f""" - OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}}) + OPTIONAL MATCH (internet:Internet {{{PROVIDER_ID_PROPERTY}: $provider_id}}) MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(elb:LoadBalancer)--(listener:ELBListener) WHERE elb.exposed_internet = true @@ -247,7 +247,7 @@ AWS_ELBV2_INTERNET_EXPOSED = AttackPathsQueryDefinition( description="Find ELBv2 load balancers exposed to the internet along with their listeners.", provider="aws", cypher=f""" - OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}}) + OPTIONAL MATCH (internet:Internet {{{PROVIDER_ID_PROPERTY}: $provider_id}}) MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(elbv2:LoadBalancerV2)--(listener:ELBV2Listener) WHERE elbv2.exposed_internet = true @@ -269,7 +269,7 @@ AWS_PUBLIC_IP_RESOURCE_LOOKUP = AttackPathsQueryDefinition( description="Given a public IP address, find the related AWS resource and its adjacent node within the selected account.", provider="aws", cypher=f""" - OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}}) + OPTIONAL MATCH (internet:Internet {{{PROVIDER_ID_PROPERTY}: $provider_id}}) MATCH path = (aws:AWSAccount {{id: $provider_uid}})-[r]-(x)-[q]-(y) WHERE (x:EC2PrivateIp AND x.public_ip = $ip) diff --git a/api/src/backend/api/attack_paths/queries/schema.py b/api/src/backend/api/attack_paths/queries/schema.py index 1ed227458b..f557a83dd9 100644 --- a/api/src/backend/api/attack_paths/queries/schema.py +++ b/api/src/backend/api/attack_paths/queries/schema.py @@ -1,7 +1,7 @@ -from tasks.jobs.attack_paths.config import DEPRECATED_PROVIDER_RESOURCE_LABEL +from tasks.jobs.attack_paths.config import PROVIDER_ID_PROPERTY, PROVIDER_RESOURCE_LABEL CARTOGRAPHY_SCHEMA_METADATA = f""" - MATCH (n:{DEPRECATED_PROVIDER_RESOURCE_LABEL} {{provider_id: $provider_id}}) + MATCH (n:{PROVIDER_RESOURCE_LABEL} {{{PROVIDER_ID_PROPERTY}: $provider_id}}) WHERE n._module_name STARTS WITH 'cartography:' AND NOT n._module_name IN ['cartography:ontology', 'cartography:prowler'] AND n._module_version IS NOT NULL diff --git a/api/src/backend/api/attack_paths/views_helpers.py b/api/src/backend/api/attack_paths/views_helpers.py index b24392955e..38cc0ec9e2 100644 --- a/api/src/backend/api/attack_paths/views_helpers.py +++ b/api/src/backend/api/attack_paths/views_helpers.py @@ -13,7 +13,11 @@ from api.attack_paths.queries.schema import ( RAW_SCHEMA_URL, ) from config.custom_logging import BackendLogger -from tasks.jobs.attack_paths.config import INTERNAL_LABELS, INTERNAL_PROPERTIES +from tasks.jobs.attack_paths.config import ( + INTERNAL_LABELS, + INTERNAL_PROPERTIES, + PROVIDER_ID_PROPERTY, +) logger = logging.getLogger(BackendLogger.API) @@ -253,7 +257,7 @@ def _serialize_graph(graph, provider_id: str) -> dict[str, Any]: nodes = [] kept_node_ids = set() for node in graph.nodes: - if node._properties.get("provider_id") != provider_id: + if node._properties.get(PROVIDER_ID_PROPERTY) != provider_id: continue kept_node_ids.add(node.element_id) @@ -273,7 +277,7 @@ def _serialize_graph(graph, provider_id: str) -> dict[str, Any]: relationships = [] for relationship in graph.relationships: - if relationship._properties.get("provider_id") != provider_id: + if relationship._properties.get(PROVIDER_ID_PROPERTY) != provider_id: continue if ( diff --git a/api/src/backend/api/tests/test_attack_paths.py b/api/src/backend/api/tests/test_attack_paths.py index 3d164bf825..348f0dd124 100644 --- a/api/src/backend/api/tests/test_attack_paths.py +++ b/api/src/backend/api/tests/test_attack_paths.py @@ -9,6 +9,10 @@ from rest_framework.exceptions import APIException, PermissionDenied, Validation from api.attack_paths import database as graph_database from api.attack_paths import views_helpers +from tasks.jobs.attack_paths.config import ( + PROVIDER_ELEMENT_ID_PROPERTY, + PROVIDER_ID_PROPERTY, +) def _make_neo4j_error(message, code): @@ -108,7 +112,7 @@ def test_execute_query_serializes_graph( labels=["AWSAccount"], properties={ "name": "account", - "provider_id": provider_id, + PROVIDER_ID_PROPERTY: provider_id, "complex": { "items": [ attack_paths_graph_stub_classes.NativeValue("value"), @@ -118,14 +122,14 @@ def test_execute_query_serializes_graph( }, ) node_2 = attack_paths_graph_stub_classes.Node( - "node-2", ["RDSInstance"], {"provider_id": provider_id} + "node-2", ["RDSInstance"], {PROVIDER_ID_PROPERTY: provider_id} ) relationship = attack_paths_graph_stub_classes.Relationship( element_id="rel-1", rel_type="OWNS", start_node=node, end_node=node_2, - properties={"weight": 1, "provider_id": provider_id}, + properties={"weight": 1, PROVIDER_ID_PROPERTY: provider_id}, ) graph = SimpleNamespace(nodes=[node, node_2], relationships=[relationship]) @@ -213,20 +217,20 @@ def test_serialize_graph_filters_by_provider_id(attack_paths_graph_stub_classes) provider_id = "provider-keep" node_keep = attack_paths_graph_stub_classes.Node( - "n1", ["AWSAccount"], {"provider_id": provider_id} + "n1", ["AWSAccount"], {PROVIDER_ID_PROPERTY: provider_id} ) node_drop = attack_paths_graph_stub_classes.Node( - "n2", ["AWSAccount"], {"provider_id": "provider-other"} + "n2", ["AWSAccount"], {PROVIDER_ID_PROPERTY: "provider-other"} ) rel_keep = attack_paths_graph_stub_classes.Relationship( - "r1", "OWNS", node_keep, node_keep, {"provider_id": provider_id} + "r1", "OWNS", node_keep, node_keep, {PROVIDER_ID_PROPERTY: provider_id} ) rel_drop_by_provider = attack_paths_graph_stub_classes.Relationship( - "r2", "OWNS", node_keep, node_drop, {"provider_id": "provider-other"} + "r2", "OWNS", node_keep, node_drop, {PROVIDER_ID_PROPERTY: "provider-other"} ) rel_drop_orphaned = attack_paths_graph_stub_classes.Relationship( - "r3", "OWNS", node_keep, node_drop, {"provider_id": provider_id} + "r3", "OWNS", node_keep, node_drop, {PROVIDER_ID_PROPERTY: provider_id} ) graph = SimpleNamespace( @@ -350,10 +354,8 @@ def test_serialize_properties_filters_internal_fields(): "_module_name": "cartography:aws", "_module_version": "0.98.0", # Provider isolation - "_provider_id": "42", - "_provider_element_id": "42:abc123", - "provider_id": "42", - "provider_element_id": "42:abc123", + PROVIDER_ID_PROPERTY: "42", + PROVIDER_ELEMENT_ID_PROPERTY: "42:abc123", } result = views_helpers._serialize_properties(properties) @@ -440,13 +442,13 @@ def test_execute_custom_query_serializes_graph( ): provider_id = "test-provider-123" node_1 = attack_paths_graph_stub_classes.Node( - "node-1", ["AWSAccount"], {"provider_id": provider_id} + "node-1", ["AWSAccount"], {PROVIDER_ID_PROPERTY: provider_id} ) node_2 = attack_paths_graph_stub_classes.Node( - "node-2", ["RDSInstance"], {"provider_id": provider_id} + "node-2", ["RDSInstance"], {PROVIDER_ID_PROPERTY: provider_id} ) relationship = attack_paths_graph_stub_classes.Relationship( - "rel-1", "OWNS", node_1, node_2, {"provider_id": provider_id} + "rel-1", "OWNS", node_1, node_2, {PROVIDER_ID_PROPERTY: provider_id} ) graph_result = MagicMock() diff --git a/api/src/backend/tasks/jobs/attack_paths/config.py b/api/src/backend/tasks/jobs/attack_paths/config.py index 1667d314a7..2f259721ed 100644 --- a/api/src/backend/tasks/jobs/attack_paths/config.py +++ b/api/src/backend/tasks/jobs/attack_paths/config.py @@ -10,16 +10,12 @@ from tasks.jobs.attack_paths import aws BATCH_SIZE = env.int("ATTACK_PATHS_BATCH_SIZE", 1000) # Neo4j internal labels (Prowler-specific, not provider-specific) +# - `Internet`: Singleton node representing external internet access for exposed-resource queries # - `ProwlerFinding`: Label for finding nodes created by Prowler and linked to cloud resources # - `_ProviderResource`: Added to ALL synced nodes for provider isolation and drop/query ops -# - `Internet`: Singleton node representing external internet access for exposed-resource queries +INTERNET_NODE_LABEL = "Internet" PROWLER_FINDING_LABEL = "ProwlerFinding" PROVIDER_RESOURCE_LABEL = "_ProviderResource" -INTERNET_NODE_LABEL = "Internet" - -# Phase 1 dual-write: deprecated label kept for drop_subgraph and infrastructure queries -# Remove in Phase 2 once all nodes use the private label exclusively -DEPRECATED_PROVIDER_RESOURCE_LABEL = "ProviderResource" @dataclass(frozen=True) @@ -31,7 +27,6 @@ class ProviderConfig: uid_field: str # e.g., "arn" # Label for resources connected to the account node, enabling indexed finding lookups. resource_label: str # e.g., "_AWSResource" - deprecated_resource_label: str # e.g., "AWSResource" ingestion_function: Callable @@ -43,7 +38,6 @@ AWS_CONFIG = ProviderConfig( root_node_label="AWSAccount", uid_field="arn", resource_label="_AWSResource", - deprecated_resource_label="AWSResource", ingestion_function=aws.start_aws_ingestion, ) @@ -56,18 +50,16 @@ PROVIDER_CONFIGS: dict[str, ProviderConfig] = { INTERNAL_LABELS: list[str] = [ "Tenant", # From Cartography, but it looks like it's ours PROVIDER_RESOURCE_LABEL, - DEPRECATED_PROVIDER_RESOURCE_LABEL, - # Add all provider-specific resource labels *[config.resource_label for config in PROVIDER_CONFIGS.values()], - *[config.deprecated_resource_label for config in PROVIDER_CONFIGS.values()], ] # Provider isolation properties +PROVIDER_ID_PROPERTY = "_provider_id" +PROVIDER_ELEMENT_ID_PROPERTY = "_provider_element_id" + PROVIDER_ISOLATION_PROPERTIES: list[str] = [ - "_provider_id", - "_provider_element_id", - "provider_id", - "provider_element_id", + PROVIDER_ID_PROPERTY, + PROVIDER_ELEMENT_ID_PROPERTY, ] # Cartography bookkeeping metadata @@ -115,9 +107,3 @@ def get_provider_resource_label(provider_type: str) -> str: """Get the resource label for a provider type (e.g., `_AWSResource`).""" config = PROVIDER_CONFIGS.get(provider_type) return config.resource_label if config else "_UnknownProviderResource" - - -def get_deprecated_provider_resource_label(provider_type: str) -> str: - """Get the deprecated resource label for a provider type (e.g., `AWSResource`).""" - config = PROVIDER_CONFIGS.get(provider_type) - return config.deprecated_resource_label if config else "UnknownProviderResource" diff --git a/api/src/backend/tasks/jobs/attack_paths/findings.py b/api/src/backend/tasks/jobs/attack_paths/findings.py index 468f805cdd..b4534fb8de 100644 --- a/api/src/backend/tasks/jobs/attack_paths/findings.py +++ b/api/src/backend/tasks/jobs/attack_paths/findings.py @@ -25,7 +25,6 @@ from api.models import Provider, ResourceFindingMapping from prowler.config import config as ProwlerConfig from tasks.jobs.attack_paths.config import ( BATCH_SIZE, - get_deprecated_provider_resource_label, get_node_uid_field, get_provider_resource_label, get_root_node_label, @@ -153,9 +152,6 @@ def add_resource_label( { "__ROOT_LABEL__": get_root_node_label(provider_type), "__RESOURCE_LABEL__": get_provider_resource_label(provider_type), - "__DEPRECATED_RESOURCE_LABEL__": get_deprecated_provider_resource_label( - provider_type - ), }, ) diff --git a/api/src/backend/tasks/jobs/attack_paths/indexes.py b/api/src/backend/tasks/jobs/attack_paths/indexes.py index 69edfe6719..1332cf50b9 100644 --- a/api/src/backend/tasks/jobs/attack_paths/indexes.py +++ b/api/src/backend/tasks/jobs/attack_paths/indexes.py @@ -6,9 +6,10 @@ from cartography.client.core.tx import run_write_query from celery.utils.log import get_task_logger from tasks.jobs.attack_paths.config import ( - DEPRECATED_PROVIDER_RESOURCE_LABEL, INTERNET_NODE_LABEL, PROWLER_FINDING_LABEL, + PROVIDER_ELEMENT_ID_PROPERTY, + PROVIDER_ID_PROPERTY, PROVIDER_RESOURCE_LABEL, ) @@ -27,8 +28,6 @@ FINDINGS_INDEX_STATEMENTS = [ # Resource indexes for Prowler Finding lookups "CREATE INDEX aws_resource_arn IF NOT EXISTS FOR (n:_AWSResource) ON (n.arn);", "CREATE INDEX aws_resource_id IF NOT EXISTS FOR (n:_AWSResource) ON (n.id);", - "CREATE INDEX deprecated_aws_resource_arn IF NOT EXISTS FOR (n:AWSResource) ON (n.arn);", - "CREATE INDEX deprecated_aws_resource_id IF NOT EXISTS FOR (n:AWSResource) ON (n.id);", # Prowler Finding indexes f"CREATE INDEX prowler_finding_id IF NOT EXISTS FOR (n:{PROWLER_FINDING_LABEL}) ON (n.id);", f"CREATE INDEX prowler_finding_provider_uid IF NOT EXISTS FOR (n:{PROWLER_FINDING_LABEL}) ON (n.provider_uid);", @@ -40,10 +39,8 @@ FINDINGS_INDEX_STATEMENTS = [ # Indexes for provider resource sync operations SYNC_INDEX_STATEMENTS = [ - f"CREATE INDEX provider_element_id IF NOT EXISTS FOR (n:{PROVIDER_RESOURCE_LABEL}) ON (n._provider_element_id);", - f"CREATE INDEX provider_resource_provider_id IF NOT EXISTS FOR (n:{PROVIDER_RESOURCE_LABEL}) ON (n._provider_id);", - f"CREATE INDEX deprecated_provider_element_id IF NOT EXISTS FOR (n:{DEPRECATED_PROVIDER_RESOURCE_LABEL}) ON (n.provider_element_id);", - f"CREATE INDEX deprecated_provider_resource_provider_id IF NOT EXISTS FOR (n:{DEPRECATED_PROVIDER_RESOURCE_LABEL}) ON (n.provider_id);", + f"CREATE INDEX provider_resource_element_id IF NOT EXISTS FOR (n:{PROVIDER_RESOURCE_LABEL}) ON (n.{PROVIDER_ELEMENT_ID_PROPERTY});", + f"CREATE INDEX provider_resource_provider_id IF NOT EXISTS FOR (n:{PROVIDER_RESOURCE_LABEL}) ON (n.{PROVIDER_ID_PROPERTY});", ] diff --git a/api/src/backend/tasks/jobs/attack_paths/queries.py b/api/src/backend/tasks/jobs/attack_paths/queries.py index 4eada6684f..a641697789 100644 --- a/api/src/backend/tasks/jobs/attack_paths/queries.py +++ b/api/src/backend/tasks/jobs/attack_paths/queries.py @@ -2,6 +2,8 @@ from tasks.jobs.attack_paths.config import ( INTERNET_NODE_LABEL, PROWLER_FINDING_LABEL, + PROVIDER_ELEMENT_ID_PROPERTY, + PROVIDER_ID_PROPERTY, PROVIDER_RESOURCE_LABEL, ) @@ -26,7 +28,7 @@ ADD_RESOURCE_LABEL_TEMPLATE = """ MATCH (account:__ROOT_LABEL__ {id: $provider_uid})-->(r) WHERE NOT r:__ROOT_LABEL__ AND NOT r:__RESOURCE_LABEL__ WITH r LIMIT $batch_size - SET r:__RESOURCE_LABEL__:__DEPRECATED_RESOURCE_LABEL__ + SET r:__RESOURCE_LABEL__ RETURN COUNT(r) AS labeled_count """ @@ -149,22 +151,18 @@ RELATIONSHIPS_FETCH_QUERY = """ LIMIT $batch_size """ -NODE_SYNC_TEMPLATE = """ +NODE_SYNC_TEMPLATE = f""" UNWIND $rows AS row - MERGE (n:__NODE_LABELS__ {_provider_element_id: row.provider_element_id}) + MERGE (n:__NODE_LABELS__ {{{PROVIDER_ELEMENT_ID_PROPERTY}: row.provider_element_id}}) SET n += row.props - SET n._provider_id = $provider_id - SET n.provider_element_id = row.provider_element_id - SET n.provider_id = $provider_id -""" # The last two lines are deprecated properties + SET n.{PROVIDER_ID_PROPERTY} = $provider_id +""" RELATIONSHIP_SYNC_TEMPLATE = f""" UNWIND $rows AS row - MATCH (s:{PROVIDER_RESOURCE_LABEL} {{_provider_element_id: row.start_element_id}}) - MATCH (t:{PROVIDER_RESOURCE_LABEL} {{_provider_element_id: row.end_element_id}}) - MERGE (s)-[r:__REL_TYPE__ {{_provider_element_id: row.provider_element_id}}]->(t) + MATCH (s:{PROVIDER_RESOURCE_LABEL} {{{PROVIDER_ELEMENT_ID_PROPERTY}: row.start_element_id}}) + MATCH (t:{PROVIDER_RESOURCE_LABEL} {{{PROVIDER_ELEMENT_ID_PROPERTY}: row.end_element_id}}) + MERGE (s)-[r:__REL_TYPE__ {{{PROVIDER_ELEMENT_ID_PROPERTY}: row.provider_element_id}}]->(t) SET r += row.props - SET r._provider_id = $provider_id - SET r.provider_element_id = row.provider_element_id - SET r.provider_id = $provider_id -""" # The last two lines are deprecated properties + SET r.{PROVIDER_ID_PROPERTY} = $provider_id +""" diff --git a/api/src/backend/tasks/jobs/attack_paths/sync.py b/api/src/backend/tasks/jobs/attack_paths/sync.py index 2fc94ab540..08d9432827 100644 --- a/api/src/backend/tasks/jobs/attack_paths/sync.py +++ b/api/src/backend/tasks/jobs/attack_paths/sync.py @@ -13,7 +13,6 @@ from celery.utils.log import get_task_logger from api.attack_paths import database as graph_database from tasks.jobs.attack_paths.config import ( BATCH_SIZE, - DEPRECATED_PROVIDER_RESOURCE_LABEL, PROVIDER_ISOLATION_PROPERTIES, PROVIDER_RESOURCE_LABEL, ) @@ -113,7 +112,6 @@ def sync_nodes( for labels, batch in grouped.items(): label_set = set(labels) label_set.add(PROVIDER_RESOURCE_LABEL) - label_set.add(DEPRECATED_PROVIDER_RESOURCE_LABEL) node_labels = ":".join(f"`{label}`" for label in sorted(label_set)) query = render_cypher_template( diff --git a/api/src/backend/tasks/tests/test_attack_paths_scan.py b/api/src/backend/tasks/tests/test_attack_paths_scan.py index 8132b7dad3..42da12411f 100644 --- a/api/src/backend/tasks/tests/test_attack_paths_scan.py +++ b/api/src/backend/tasks/tests/test_attack_paths_scan.py @@ -6,9 +6,6 @@ import pytest from tasks.jobs.attack_paths import findings as findings_module from tasks.jobs.attack_paths import internet as internet_module from tasks.jobs.attack_paths import sync as sync_module -from tasks.jobs.attack_paths.config import ( - get_deprecated_provider_resource_label, -) from tasks.jobs.attack_paths.scan import run as attack_paths_run from api.models import ( @@ -648,7 +645,7 @@ class TestAttackPathsFindingsHelpers: ), patch( "tasks.jobs.attack_paths.findings.get_provider_resource_label", - return_value="AWSResource", + return_value="_AWSResource", ), ): findings_module.load_findings( @@ -1069,7 +1066,7 @@ class TestAttackPathsFindingsHelpers: ), patch( "tasks.jobs.attack_paths.findings.get_provider_resource_label", - return_value="AWSResource", + return_value="_AWSResource", ), ): findings_module.load_findings(mock_session, empty_gen(), provider, config) @@ -1077,19 +1074,8 @@ class TestAttackPathsFindingsHelpers: mock_session.run.assert_not_called() -class TestProviderConfigAccessors: - def test_get_deprecated_provider_resource_label_known_provider(self): - assert get_deprecated_provider_resource_label("aws") == "AWSResource" - - def test_get_deprecated_provider_resource_label_unknown_provider(self): - assert ( - get_deprecated_provider_resource_label("unknown") - == "UnknownProviderResource" - ) - - class TestAddResourceLabel: - def test_add_resource_label_applies_both_labels(self): + def test_add_resource_label_applies_private_label(self): mock_session = MagicMock() first_result = MagicMock() @@ -1104,11 +1090,11 @@ class TestAddResourceLabel: assert mock_session.run.call_count == 2 query = mock_session.run.call_args_list[0].args[0] assert "_AWSResource" in query - assert "AWSResource" in query + assert "AWSResource" not in query.replace("_AWSResource", "") class TestSyncNodes: - def test_sync_nodes_adds_both_labels(self): + def test_sync_nodes_adds_private_label(self): mock_source_session = MagicMock() mock_target_session = MagicMock() @@ -1137,7 +1123,7 @@ class TestSyncNodes: assert total == 1 query = mock_target_session.run.call_args.args[0] assert "_ProviderResource" in query - assert "ProviderResource" in query + assert "ProviderResource" not in query.replace("_ProviderResource", "") class TestInternetAnalysis: From b31145616064bd6727139777dca1cea9b977346a Mon Sep 17 00:00:00 2001 From: Josema Camacho Date: Mon, 16 Mar 2026 13:19:37 +0100 Subject: [PATCH 010/366] fix(security): Ignore cryptography vulnerability until we can upgrade it (#10345) --- .github/workflows/api-security.yml | 3 ++- .pre-commit-config.yaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/api-security.yml b/.github/workflows/api-security.yml index 99cc4fc132..490d01c2b7 100644 --- a/.github/workflows/api-security.yml +++ b/.github/workflows/api-security.yml @@ -64,8 +64,9 @@ jobs: - name: Safety if: steps.check-changes.outputs.any_changed == 'true' - run: poetry run safety check --ignore 79023,79027 + run: poetry run safety check --ignore 79023,79027,86217 # TODO: 79023 & 79027 knack ReDoS until `azure-cli-core` (via `cartography`) allows `knack` >=0.13.0 + # TODO: 86217 because `alibabacloud-tea-openapi == 0.4.3` don't let us upgrade `cryptography >= 46.0.0` - name: Vulture if: steps.check-changes.outputs.any_changed == 'true' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1858778f12..a45f436284 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -127,7 +127,8 @@ repos: description: "Safety is a tool that checks your installed dependencies for known security vulnerabilities" # TODO: Botocore needs urllib3 1.X so we need to ignore these vulnerabilities 77744,77745. Remove this once we upgrade to urllib3 2.X # TODO: 79023 & 79027 knack ReDoS until `azure-cli-core` (via `cartography`) allows `knack` >=0.13.0 - entry: bash -c 'safety check --ignore 70612,66963,74429,76352,76353,77744,77745,79023,79027' + # TODO: 86217 because `alibabacloud-tea-openapi == 0.4.3` don't let us upgrade `cryptography >= 46.0.0` + entry: bash -c 'safety check --ignore 70612,66963,74429,76352,76353,77744,77745,79023,79027,86217' language: system - id: vulture From 1cf6eaa0b7562615661e4ab109cb777e218da00f Mon Sep 17 00:00:00 2001 From: "shria :))" <96020379+shalkoda@users.noreply.github.com> Date: Mon, 16 Mar 2026 10:22:36 -0500 Subject: [PATCH 011/366] feat(github): add organization_repository_deletion_limited check (#10185) Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> --- prowler/CHANGELOG.md | 1 + prowler/compliance/github/cis_1.0_github.json | 4 +- prowler/providers/common/provider.py | 19 +- prowler/providers/github/github_provider.py | 16 +- .../__init__.py | 0 ..._repository_deletion_limited.metadata.json | 36 ++++ ...rganization_repository_deletion_limited.py | 31 +++ .../organization/organization_service.py | 6 + ...zation_repository_deletion_limited_test.py | 186 ++++++++++++++++++ 9 files changed, 294 insertions(+), 5 deletions(-) create mode 100644 prowler/providers/github/services/organization/organization_repository_deletion_limited/__init__.py create mode 100644 prowler/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited.metadata.json create mode 100644 prowler/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited.py create mode 100644 tests/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index df49d1fa25..3e7efa203a 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. - `entra_conditional_access_policy_device_code_flow_blocked` check for M365 provider [(#10218)](https://github.com/prowler-cloud/prowler/pull/10218) - CheckMetadata Pydantic validators [(#8584)](https://github.com/prowler-cloud/prowler/pull/8583) - `entra_conditional_access_policy_require_mfa_for_admin_portals` check for Azure provider and update CIS compliance [(#10330)](https://github.com/prowler-cloud/prowler/pull/10330) +- `organization_repository_deletion_limited` check for GitHub provider [(#10185)](https://github.com/prowler-cloud/prowler/pull/10185) ### 🔄 Changed diff --git a/prowler/compliance/github/cis_1.0_github.json b/prowler/compliance/github/cis_1.0_github.json index e675470169..e9dbccc7f6 100644 --- a/prowler/compliance/github/cis_1.0_github.json +++ b/prowler/compliance/github/cis_1.0_github.json @@ -499,7 +499,9 @@ { "Id": "1.2.3", "Description": "Ensure only a limited number of trusted users can delete repositories.", - "Checks": [], + "Checks": [ + "organization_repository_deletion_limited" + ], "Attributes": [ { "Section": "1 Source Code", diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index 0f028106bb..c47c268566 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -238,6 +238,21 @@ class Provider(ABC): fixer_config=fixer_config, ) elif "github" in provider_class_name.lower(): + orgs = [] + repos = [] + + if getattr(arguments, "organization", None): + orgs.extend(arguments.organization) + if getattr(arguments, "organizations", None): + orgs.extend(arguments.organizations) + if getattr(arguments, "repository", None): + repos.extend(arguments.repository) + if getattr(arguments, "repositories", None): + repos.extend(arguments.repositories) + + orgs = list(dict.fromkeys(orgs)) + repos = list(dict.fromkeys(repos)) + provider_class( personal_access_token=arguments.personal_access_token, oauth_app_token=arguments.oauth_app_token, @@ -245,8 +260,8 @@ class Provider(ABC): github_app_id=arguments.github_app_id, mutelist_path=arguments.mutelist_file, config_path=arguments.config_file, - repositories=arguments.repository, - organizations=arguments.organization, + repositories=repos, + organizations=orgs, ) elif "googleworkspace" in provider_class_name.lower(): provider_class( diff --git a/prowler/providers/github/github_provider.py b/prowler/providers/github/github_provider.py index 9088b79c6f..16d13f7434 100644 --- a/prowler/providers/github/github_provider.py +++ b/prowler/providers/github/github_provider.py @@ -139,8 +139,20 @@ class GithubProvider(Provider): logging.getLogger("github.GithubRetry").setLevel(logging.CRITICAL) # Set repositories and organizations for scoping - self._repositories = repositories or [] - self._organizations = organizations or [] + # Normalize single strings into lists (argparse sometimes passes str for singular flags) + if repositories is None: + self._repositories = [] + elif isinstance(repositories, str): + self._repositories = [repositories] + else: + self._repositories = list(repositories) + + if organizations is None: + self._organizations = [] + elif isinstance(organizations, str): + self._organizations = [organizations] + else: + self._organizations = list(organizations) self._session = GithubProvider.setup_session( personal_access_token, diff --git a/prowler/providers/github/services/organization/organization_repository_deletion_limited/__init__.py b/prowler/providers/github/services/organization/organization_repository_deletion_limited/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited.metadata.json b/prowler/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited.metadata.json new file mode 100644 index 0000000000..851848ce5a --- /dev/null +++ b/prowler/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited.metadata.json @@ -0,0 +1,36 @@ +{ + "Provider": "github", + "CheckID": "organization_repository_deletion_limited", + "CheckTitle": "Organization repository deletion and transfer is restricted to owners", + "CheckType": [], + "ServiceName": "organization", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "GitHubOrganization", + "ResourceGroup": "governance", + "Description": "Ensure repository deletion/transfer is restricted so only trusted organization users (owners) can delete or transfer repositories.", + "Risk": "If members can delete or transfer repositories, accidental or malicious deletions can cause irreversible data loss, service disruption, and increased blast radius from compromised accounts.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "1. Sign in to GitHub as an organization owner\n2. Go to Organization > Settings\n3. Under \"Access\", click \"Member privileges\"\n4. Disable \"Allow members to delete or transfer repositories\"\n5. Save changes", + "Terraform": "" + }, + "Recommendation": { + "Text": "Disable member repository deletion/transfer privileges so only organization owners can delete or transfer repositories.", + "Url": "https://hub.prowler.com/check/organization_repository_deletion_limited" + } + }, + "Categories": [ + "identity-access" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "AdditionalURLs": [ + "https://docs.github.com/en/organizations/managing-organization-settings/setting-permissions-for-deleting-or-transferring-repositories" + ] +} diff --git a/prowler/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited.py b/prowler/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited.py new file mode 100644 index 0000000000..5d94ad5af6 --- /dev/null +++ b/prowler/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited.py @@ -0,0 +1,31 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportGithub +from prowler.providers.github.services.organization.organization_client import ( + organization_client, +) + + +class organization_repository_deletion_limited(Check): + """Check if repository deletion/transfer is limited to trusted organization users.""" + + def execute(self) -> List[CheckReportGithub]: + findings = [] + for org in organization_client.organizations.values(): + members_can_delete = org.members_can_delete_repositories + + if members_can_delete is None: + continue + + report = CheckReportGithub(metadata=self.metadata(), resource=org) + + if members_can_delete is False: + report.status = "PASS" + report.status_extended = f"Organization {org.name} restricts repository deletion/transfer to trusted users." + else: + report.status = "FAIL" + report.status_extended = f"Organization {org.name} allows members to delete/transfer repositories." + + findings.append(report) + + return findings diff --git a/prowler/providers/github/services/organization/organization_service.py b/prowler/providers/github/services/organization/organization_service.py index 187da72f68..0aceca423e 100644 --- a/prowler/providers/github/services/organization/organization_service.py +++ b/prowler/providers/github/services/organization/organization_service.py @@ -76,6 +76,7 @@ class Organization(GithubService): id=user.id, name=user.login, mfa_required=None, # Users don't have MFA requirements like orgs + members_can_delete_repositories=None, is_verified=None, ) logger.info( @@ -189,6 +190,9 @@ class Organization(GithubService): repo_creation_settings["members_allowed_repository_creation_type"] = ( _extract_flag("members_allowed_repository_creation_type", str) ) + members_can_delete_repositories = _extract_flag( + "members_can_delete_repositories", bool + ) base_permission_raw = _extract_flag("default_repository_permission", str) base_permission = ( @@ -201,6 +205,7 @@ class Organization(GithubService): id=org.id, name=org.login, mfa_required=require_mfa, + members_can_delete_repositories=members_can_delete_repositories, members_can_create_repositories=repo_creation_settings[ "members_can_create_repositories" ], @@ -227,6 +232,7 @@ class Org(BaseModel): id: int name: str mfa_required: Optional[bool] = False + members_can_delete_repositories: Optional[bool] = None members_can_create_repositories: Optional[bool] = None members_can_create_public_repositories: Optional[bool] = None members_can_create_private_repositories: Optional[bool] = None diff --git a/tests/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited_test.py b/tests/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited_test.py new file mode 100644 index 0000000000..a9991ab2d1 --- /dev/null +++ b/tests/providers/github/services/organization/organization_repository_deletion_limited/organization_repository_deletion_limited_test.py @@ -0,0 +1,186 @@ +from unittest import mock + +from prowler.providers.github.services.organization.organization_service import Org +from tests.providers.github.github_fixtures import set_mocked_github_provider + + +class Test_organization_repository_deletion_limited: + def test_no_organizations(self): + organization_client = mock.MagicMock + organization_client.organizations = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.organization.organization_repository_deletion_limited.organization_repository_deletion_limited.organization_client", + new=organization_client, + ), + ): + from prowler.providers.github.services.organization.organization_repository_deletion_limited.organization_repository_deletion_limited import ( + organization_repository_deletion_limited, + ) + + check = organization_repository_deletion_limited() + result = check.execute() + assert len(result) == 0 + + def test_repository_deletion_disabled(self): + organization_client = mock.MagicMock + org_name = "test-organization" + organization_client.organizations = { + 1: Org( + id=1, + name=org_name, + mfa_required=None, + members_can_delete_repositories=False, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.organization.organization_repository_deletion_limited.organization_repository_deletion_limited.organization_client", + new=organization_client, + ), + ): + from prowler.providers.github.services.organization.organization_repository_deletion_limited.organization_repository_deletion_limited import ( + organization_repository_deletion_limited, + ) + + check = organization_repository_deletion_limited() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_name == org_name + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Organization {org_name} restricts repository deletion/transfer to trusted users." + ) + + def test_repository_deletion_enabled(self): + organization_client = mock.MagicMock + org_name = "test-organization" + organization_client.organizations = { + 1: Org( + id=1, + name=org_name, + mfa_required=None, + members_can_delete_repositories=True, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.organization.organization_repository_deletion_limited.organization_repository_deletion_limited.organization_client", + new=organization_client, + ), + ): + from prowler.providers.github.services.organization.organization_repository_deletion_limited.organization_repository_deletion_limited import ( + organization_repository_deletion_limited, + ) + + check = organization_repository_deletion_limited() + result = check.execute() + assert len(result) == 1 + assert result[0].resource_name == org_name + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Organization {org_name} allows members to delete/transfer repositories." + ) + + def test_repository_deletion_setting_not_available(self): + organization_client = mock.MagicMock + org_name = "test-organization" + organization_client.organizations = { + 1: Org( + id=1, + name=org_name, + mfa_required=None, + members_can_delete_repositories=None, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.organization.organization_repository_deletion_limited.organization_repository_deletion_limited.organization_client", + new=organization_client, + ), + ): + from prowler.providers.github.services.organization.organization_repository_deletion_limited.organization_repository_deletion_limited import ( + organization_repository_deletion_limited, + ) + + check = organization_repository_deletion_limited() + result = check.execute() + assert len(result) == 0 + + def test_multiple_organizations_mixed_settings(self): + organization_client = mock.MagicMock + org_name_1 = "test-organization-1" + org_name_2 = "test-organization-2" + org_name_3 = "test-organization-3" + organization_client.organizations = { + 1: Org( + id=1, + name=org_name_1, + mfa_required=None, + members_can_delete_repositories=False, + ), + 2: Org( + id=2, + name=org_name_2, + mfa_required=None, + members_can_delete_repositories=True, + ), + 3: Org( + id=3, + name=org_name_3, + mfa_required=None, + members_can_delete_repositories=None, + ), + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_github_provider(), + ), + mock.patch( + "prowler.providers.github.services.organization.organization_repository_deletion_limited.organization_repository_deletion_limited.organization_client", + new=organization_client, + ), + ): + from prowler.providers.github.services.organization.organization_repository_deletion_limited.organization_repository_deletion_limited import ( + organization_repository_deletion_limited, + ) + + check = organization_repository_deletion_limited() + result = check.execute() + assert len(result) == 2 + + # Find results by organization name + results_by_name = {r.resource_name: r for r in result} + + assert org_name_1 in results_by_name + assert results_by_name[org_name_1].status == "PASS" + + assert org_name_2 in results_by_name + assert results_by_name[org_name_2].status == "FAIL" + + # org_name_3 should not be in results because setting is None + assert org_name_3 not in results_by_name From 787a339cd93c768768bb7f74f655131b52f7ba2a Mon Sep 17 00:00:00 2001 From: Josema Camacho Date: Mon, 16 Mar 2026 16:31:15 +0100 Subject: [PATCH 012/366] feat(attack-paths): scans add tenant and provider related labels to nodes (#10308) --- api/CHANGELOG.md | 1 + api/poetry.lock | 2 +- .../backend/api/attack_paths/views_helpers.py | 7 ++++- .../backend/api/tests/test_attack_paths.py | 8 +++++ .../backend/tasks/jobs/attack_paths/config.py | 31 +++++++++++++++++++ .../backend/tasks/jobs/attack_paths/scan.py | 1 + .../backend/tasks/jobs/attack_paths/sync.py | 9 ++++++ .../tasks/tests/test_attack_paths_scan.py | 8 +++-- skills/prowler-attack-paths-query/SKILL.md | 6 ++-- 9 files changed, 66 insertions(+), 7 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 45e520dc16..0e09dd8ebb 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the **Prowler API** are documented in this file. - Attack Paths: Migrate network exposure queries from APOC to standard openCypher for Neo4j and Neptune compatibility [(#10266)](https://github.com/prowler-cloud/prowler/pull/10266) - Attack Paths: Complete migration to private graph labels and properties, removing deprecated dual-write support [(#10268)](https://github.com/prowler-cloud/prowler/pull/10268) - `POST /api/v1/providers` returns `409 Conflict` if already exists [(#10293)](https://github.com/prowler-cloud/prowler/pull/10293) +- Attack Paths: Added tenant and provider related labels to the nodes so they can be easily filtered on custom queries [(#10308)](https://github.com/prowler-cloud/prowler/pull/10308) ### 🐞 Fixed diff --git a/api/poetry.lock b/api/poetry.lock index dba0433896..6fbdb874bc 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -6730,7 +6730,7 @@ tzlocal = "5.3.1" type = "git" url = "https://github.com/prowler-cloud/prowler.git" reference = "master" -resolved_reference = "6962622fd21401886371add25463f77228cd9c1f" +resolved_reference = "b31145616064bd6727139777dca1cea9b977346a" [[package]] name = "psutil" diff --git a/api/src/backend/api/attack_paths/views_helpers.py b/api/src/backend/api/attack_paths/views_helpers.py index 38cc0ec9e2..8e3ee203b4 100644 --- a/api/src/backend/api/attack_paths/views_helpers.py +++ b/api/src/backend/api/attack_paths/views_helpers.py @@ -17,6 +17,7 @@ from tasks.jobs.attack_paths.config import ( INTERNAL_LABELS, INTERNAL_PROPERTIES, PROVIDER_ID_PROPERTY, + is_dynamic_isolation_label, ) logger = logging.getLogger(BackendLogger.API) @@ -305,7 +306,11 @@ def _serialize_graph(graph, provider_id: str) -> dict[str, Any]: def _filter_labels(labels: Iterable[str]) -> list[str]: - return [label for label in labels if label not in INTERNAL_LABELS] + return [ + label + for label in labels + if label not in INTERNAL_LABELS and not is_dynamic_isolation_label(label) + ] def _serialize_properties(properties: dict[str, Any]) -> dict[str, Any]: diff --git a/api/src/backend/api/tests/test_attack_paths.py b/api/src/backend/api/tests/test_attack_paths.py index 348f0dd124..442a9c5dd2 100644 --- a/api/src/backend/api/tests/test_attack_paths.py +++ b/api/src/backend/api/tests/test_attack_paths.py @@ -363,6 +363,14 @@ def test_serialize_properties_filters_internal_fields(): assert result == {"name": "prod"} +def test_filter_labels_strips_dynamic_isolation_labels(): + labels = ["AWSRole", "_Tenant_abc123", "_Provider_def456", "_ProviderResource"] + + result = views_helpers._filter_labels(labels) + + assert result == ["AWSRole"] + + def test_serialize_graph_as_text_node_without_properties(): graph = { "nodes": [{"id": "n1", "labels": ["AWSAccount"], "properties": {}}], diff --git a/api/src/backend/tasks/jobs/attack_paths/config.py b/api/src/backend/tasks/jobs/attack_paths/config.py index 2f259721ed..be9305d8a5 100644 --- a/api/src/backend/tasks/jobs/attack_paths/config.py +++ b/api/src/backend/tasks/jobs/attack_paths/config.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from typing import Callable +from uuid import UUID from config.env import env @@ -17,6 +18,12 @@ INTERNET_NODE_LABEL = "Internet" PROWLER_FINDING_LABEL = "ProwlerFinding" PROVIDER_RESOURCE_LABEL = "_ProviderResource" +# Dynamic isolation labels that contain entity UUIDs and are added to every synced node during sync +# Format: _Tenant_{uuid_no_hyphens}, _Provider_{uuid_no_hyphens} +TENANT_LABEL_PREFIX = "_Tenant_" +PROVIDER_LABEL_PREFIX = "_Provider_" +DYNAMIC_ISOLATION_PREFIXES = [TENANT_LABEL_PREFIX, PROVIDER_LABEL_PREFIX] + @dataclass(frozen=True) class ProviderConfig: @@ -107,3 +114,27 @@ def get_provider_resource_label(provider_type: str) -> str: """Get the resource label for a provider type (e.g., `_AWSResource`).""" config = PROVIDER_CONFIGS.get(provider_type) return config.resource_label if config else "_UnknownProviderResource" + + +# Dynamic Isolation Label Helpers +# -------------------------------- + + +def _normalize_uuid(value: str | UUID) -> str: + """Strip hyphens from a UUID string for use in Neo4j labels.""" + return str(value).replace("-", "") + + +def get_tenant_label(tenant_id: str | UUID) -> str: + """Get the Neo4j label for a tenant (e.g., `_Tenant_019c41ee7df37deca684d839f95619f8`).""" + return f"{TENANT_LABEL_PREFIX}{_normalize_uuid(tenant_id)}" + + +def get_provider_label(provider_id: str | UUID) -> str: + """Get the Neo4j label for a provider (e.g., `_Provider_019c41ee7df37deca684d839f95619f8`).""" + return f"{PROVIDER_LABEL_PREFIX}{_normalize_uuid(provider_id)}" + + +def is_dynamic_isolation_label(label: str) -> bool: + """Check if a label is a dynamic tenant/provider isolation label.""" + return any(label.startswith(prefix) for prefix in DYNAMIC_ISOLATION_PREFIXES) diff --git a/api/src/backend/tasks/jobs/attack_paths/scan.py b/api/src/backend/tasks/jobs/attack_paths/scan.py index f12736d807..6624680a5e 100644 --- a/api/src/backend/tasks/jobs/attack_paths/scan.py +++ b/api/src/backend/tasks/jobs/attack_paths/scan.py @@ -237,6 +237,7 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]: sync.sync_graph( source_database=tmp_database_name, target_database=tenant_database_name, + tenant_id=str(prowler_api_provider.tenant_id), provider_id=str(prowler_api_provider.id), ) db_utils.set_graph_data_ready(attack_paths_scan, True) diff --git a/api/src/backend/tasks/jobs/attack_paths/sync.py b/api/src/backend/tasks/jobs/attack_paths/sync.py index 08d9432827..c0b1799b9a 100644 --- a/api/src/backend/tasks/jobs/attack_paths/sync.py +++ b/api/src/backend/tasks/jobs/attack_paths/sync.py @@ -15,6 +15,8 @@ from tasks.jobs.attack_paths.config import ( BATCH_SIZE, PROVIDER_ISOLATION_PROPERTIES, PROVIDER_RESOURCE_LABEL, + get_provider_label, + get_tenant_label, ) from tasks.jobs.attack_paths.indexes import IndexType, create_indexes from tasks.jobs.attack_paths.queries import ( @@ -36,6 +38,7 @@ def create_sync_indexes(neo4j_session) -> None: def sync_graph( source_database: str, target_database: str, + tenant_id: str, provider_id: str, ) -> dict[str, int]: """ @@ -44,6 +47,7 @@ def sync_graph( Args: `source_database`: The temporary scan database `target_database`: The tenant database + `tenant_id`: The tenant ID for isolation `provider_id`: The provider ID for isolation Returns: @@ -52,6 +56,7 @@ def sync_graph( nodes_synced = sync_nodes( source_database, target_database, + tenant_id, provider_id, ) relationships_synced = sync_relationships( @@ -69,12 +74,14 @@ def sync_graph( def sync_nodes( source_database: str, target_database: str, + tenant_id: str, provider_id: str, ) -> int: """ Sync nodes from source to target database. Adds `_ProviderResource` label and `_provider_id` property to all nodes. + Also adds dynamic `_Tenant_{id}` and `_Provider_{id}` isolation labels. """ last_id = -1 total_synced = 0 @@ -112,6 +119,8 @@ def sync_nodes( for labels, batch in grouped.items(): label_set = set(labels) label_set.add(PROVIDER_RESOURCE_LABEL) + label_set.add(get_tenant_label(tenant_id)) + label_set.add(get_provider_label(provider_id)) node_labels = ":".join(f"`{label}`" for label in sorted(label_set)) query = render_cypher_template( diff --git a/api/src/backend/tasks/tests/test_attack_paths_scan.py b/api/src/backend/tasks/tests/test_attack_paths_scan.py index 42da12411f..cd4dfcffd9 100644 --- a/api/src/backend/tasks/tests/test_attack_paths_scan.py +++ b/api/src/backend/tasks/tests/test_attack_paths_scan.py @@ -151,6 +151,7 @@ class TestAttackPathsRun: mock_sync.assert_called_once_with( source_database="db-scan-id", target_database="tenant-db", + tenant_id=str(provider.tenant_id), provider_id=str(provider.id), ) mock_get_ingestion.assert_called_once_with(provider.provider) @@ -1118,12 +1119,15 @@ class TestSyncNodes: "tasks.jobs.attack_paths.sync.graph_database.get_session", side_effect=[source_ctx, target_ctx], ): - total = sync_module.sync_nodes("source-db", "target-db", "prov-1") + total = sync_module.sync_nodes( + "source-db", "target-db", "tenant-1", "prov-1" + ) assert total == 1 query = mock_target_session.run.call_args.args[0] assert "_ProviderResource" in query - assert "ProviderResource" not in query.replace("_ProviderResource", "") + assert "_Tenant_tenant1" in query + assert "_Provider_prov1" in query class TestInternetAnalysis: diff --git a/skills/prowler-attack-paths-query/SKILL.md b/skills/prowler-attack-paths-query/SKILL.md index 78f53b4c0d..dcbb2d406b 100644 --- a/skills/prowler-attack-paths-query/SKILL.md +++ b/skills/prowler-attack-paths-query/SKILL.md @@ -252,13 +252,13 @@ https://raw.githubusercontent.com/cartography-cncf/cartography/refs/tags/0.126.0 **IMPORTANT**: Always match the schema version to the dependency version in `pyproject.toml`. Using master/main may reference node labels or properties that don't exist in the deployed version. -**Additional Prowler Labels**: The Attack Paths sync task adds extra labels: +**Additional Prowler Labels**: The Attack Paths sync task adds labels that queries can reference: - `ProwlerFinding` - Prowler finding nodes with `status`, `provider_uid` properties -- `ProviderResource` - Generic resource marker -- `{Provider}Resource` - Provider-specific marker (e.g., `AWSResource`) - `Internet` - Internet sentinel node with `_provider_id` property (used in network exposure queries) +Other internal labels (`_ProviderResource`, `_AWSResource`, `_Tenant_*`, `_Provider_*`) exist for isolation but should never be used in queries. + These are defined in `api/src/backend/tasks/jobs/attack_paths/config.py`. ### 3. Consult the Schema for Available Data From febd2c8fdba88f645af370ec291f05c552ecd4c5 Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Tue, 17 Mar 2026 07:47:12 +0000 Subject: [PATCH 013/366] fix(ci): checkout upstream repo for tests (#10356) --- .github/actions/setup-python-poetry/action.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-python-poetry/action.yml b/.github/actions/setup-python-poetry/action.yml index 62d01091d8..505ba86da6 100644 --- a/.github/actions/setup-python-poetry/action.yml +++ b/.github/actions/setup-python-poetry/action.yml @@ -26,10 +26,18 @@ runs: if: github.event_name == 'pull_request' && github.base_ref == 'master' && github.repository == 'prowler-cloud/prowler' shell: bash working-directory: ${{ inputs.working-directory }} + env: + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} run: | BRANCH_NAME="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}" - echo "Using branch: $BRANCH_NAME" - sed -i "s|\(git+https://github.com/prowler-cloud/prowler[^@]*\)@master|\1@$BRANCH_NAME|g" pyproject.toml + UPSTREAM="prowler-cloud/prowler" + if [ "$HEAD_REPO" != "$UPSTREAM" ]; then + echo "Fork PR detected (${HEAD_REPO}), rewriting VCS URL to fork" + sed -i "s|git+https://github.com/prowler-cloud/prowler\([^@]*\)@master|git+https://github.com/${HEAD_REPO}\1@$BRANCH_NAME|g" pyproject.toml + else + echo "Same-repo PR, using branch: $BRANCH_NAME" + sed -i "s|\(git+https://github.com/prowler-cloud/prowler[^@]*\)@master|\1@$BRANCH_NAME|g" pyproject.toml + fi - name: Install poetry shell: bash From 6a4278ed4d1801250016f4f8c03cad8a8a71b12d Mon Sep 17 00:00:00 2001 From: Josema Camacho Date: Tue, 17 Mar 2026 09:00:56 +0100 Subject: [PATCH 014/366] fix(docs): setting a couple of API PRs in the next release instead of 5.20 (#10357) --- api/CHANGELOG.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 0e09dd8ebb..79e49d2b3b 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -2,14 +2,21 @@ All notable changes to the **Prowler API** are documented in this file. +## [1.22.0] (Prowler UNRELEASED) + +### 🔄 Changed + +- Attack Paths: Complete migration to private graph labels and properties, removing deprecated dual-write support [(#10268)](https://github.com/prowler-cloud/prowler/pull/10268) +- Attack Paths: Added tenant and provider related labels to the nodes so they can be easily filtered on custom queries [(#10308)](https://github.com/prowler-cloud/prowler/pull/10308) + +--- + ## [1.21.0] (Prowler v5.20.0) ### 🔄 Changed - Attack Paths: Migrate network exposure queries from APOC to standard openCypher for Neo4j and Neptune compatibility [(#10266)](https://github.com/prowler-cloud/prowler/pull/10266) -- Attack Paths: Complete migration to private graph labels and properties, removing deprecated dual-write support [(#10268)](https://github.com/prowler-cloud/prowler/pull/10268) - `POST /api/v1/providers` returns `409 Conflict` if already exists [(#10293)](https://github.com/prowler-cloud/prowler/pull/10293) -- Attack Paths: Added tenant and provider related labels to the nodes so they can be easily filtered on custom queries [(#10308)](https://github.com/prowler-cloud/prowler/pull/10308) ### 🐞 Fixed From 712da2cf98f8489f9d6a73621efa7cff871c5b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Tue, 17 Mar 2026 09:45:29 +0100 Subject: [PATCH 015/366] feat(ui): Add CloudTrail Events tab to detail cards (#10320) Co-authored-by: alejandrobailo --- ui/CHANGELOG.md | 1 + ui/actions/resources/index.ts | 1 + ui/actions/resources/resources.test.ts | 171 +++++++ ui/actions/resources/resources.ts | 58 +++ .../findings/table/finding-detail.test.tsx | 4 + .../findings/table/finding-detail.tsx | 20 +- .../table/resource-detail-content.tsx | 15 + .../events-timeline/events-timeline.test.tsx | 293 +++++++++++ .../events-timeline/events-timeline.tsx | 458 ++++++++++++++++++ ui/types/resources.ts | 21 + 10 files changed, 1040 insertions(+), 2 deletions(-) create mode 100644 ui/actions/resources/resources.test.ts create mode 100644 ui/components/shared/events-timeline/events-timeline.test.tsx create mode 100644 ui/components/shared/events-timeline/events-timeline.tsx diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 7f2d29e44e..9842b76870 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the **Prowler UI** are documented in this file. ### 🚀 Added - Organization and organizational unit row actions (Edit Name, Update Credentials, Test Connections, Delete) in providers table dropdown [(#10317)](https://github.com/prowler-cloud/prowler/pull/10317) +- Events tab in Findings and Resource detail cards showing an AWS CloudTrail timeline with expandable event rows, actor info, request/response JSON payloads, and error details [(#10320)](https://github.com/prowler-cloud/prowler/pull/10320) --- diff --git a/ui/actions/resources/index.ts b/ui/actions/resources/index.ts index e4e24e4d9a..afaf91a381 100644 --- a/ui/actions/resources/index.ts +++ b/ui/actions/resources/index.ts @@ -3,5 +3,6 @@ export { getLatestResources, getMetadataInfo, getResourceById, + getResourceEvents, getResources, } from "./resources"; diff --git a/ui/actions/resources/resources.test.ts b/ui/actions/resources/resources.test.ts new file mode 100644 index 0000000000..b3fc4b257b --- /dev/null +++ b/ui/actions/resources/resources.test.ts @@ -0,0 +1,171 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const { fetchMock, getAuthHeadersMock, handleApiResponseMock } = vi.hoisted( + () => ({ + fetchMock: vi.fn(), + getAuthHeadersMock: vi.fn(), + handleApiResponseMock: vi.fn(), + }), +); + +vi.mock("@/lib", () => ({ + apiBaseUrl: "https://api.example.com/api/v1", + getAuthHeaders: getAuthHeadersMock, +})); + +vi.mock("@/lib/server-actions-helper", () => ({ + handleApiResponse: handleApiResponseMock, +})); + +vi.mock("@/lib/provider-filters", () => ({ + appendSanitizedProviderTypeFilters: vi.fn(), +})); + +vi.mock("next/navigation", () => ({ + redirect: vi.fn(), +})); + +import { getResourceEvents } from "./resources"; + +describe("getResourceEvents", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.stubGlobal("fetch", fetchMock); + getAuthHeadersMock.mockResolvedValue({ Authorization: "Bearer token" }); + }); + + it("calls the correct API endpoint with default parameters", async () => { + // Given + const mockResponse = new Response("", { status: 200 }); + fetchMock.mockResolvedValue(mockResponse); + handleApiResponseMock.mockResolvedValue({ data: [] }); + + // When + await getResourceEvents("resource-123"); + + // Then + expect(fetchMock).toHaveBeenCalledTimes(1); + const calledUrl = new URL(fetchMock.mock.calls[0][0]); + expect(calledUrl.pathname).toBe("/api/v1/resources/resource-123/events"); + expect(calledUrl.searchParams.get("include_read_events")).toBe("false"); + expect(calledUrl.searchParams.get("lookback_days")).toBe("90"); + expect(calledUrl.searchParams.get("page[size]")).toBe("50"); + }); + + it("passes custom parameters to the API", async () => { + // Given + const mockResponse = new Response("", { status: 200 }); + fetchMock.mockResolvedValue(mockResponse); + handleApiResponseMock.mockResolvedValue({ data: [] }); + + // When + await getResourceEvents("resource-456", { + includeReadEvents: true, + lookbackDays: 30, + pageSize: 25, + }); + + // Then + const calledUrl = new URL(fetchMock.mock.calls[0][0]); + expect(calledUrl.searchParams.get("include_read_events")).toBe("true"); + expect(calledUrl.searchParams.get("lookback_days")).toBe("30"); + expect(calledUrl.searchParams.get("page[size]")).toBe("25"); + }); + + it("returns parsed response on success", async () => { + // Given + const mockData = { + data: [ + { + type: "resource-events", + id: "event-1", + attributes: { event_name: "CreateStack" }, + }, + ], + }; + const mockResponse = new Response("", { status: 200 }); + fetchMock.mockResolvedValue(mockResponse); + handleApiResponseMock.mockResolvedValue(mockData); + + // When + const result = await getResourceEvents("resource-123"); + + // Then + expect(result).toEqual(mockData); + expect(handleApiResponseMock).toHaveBeenCalledWith(mockResponse); + }); + + it("returns error object for non-ok responses without calling handleApiResponse", async () => { + // Given + const errorBody = JSON.stringify({ + errors: [ + { + detail: + "Provider credentials are invalid or expired. Please reconnect the provider.", + }, + ], + }); + const mockResponse = new Response(errorBody, { + status: 502, + statusText: "Bad Gateway", + }); + fetchMock.mockResolvedValue(mockResponse); + + // When + const result = await getResourceEvents("resource-123"); + + // Then + expect(result).toEqual({ + error: + "Provider credentials are invalid or expired. Please reconnect the provider.", + status: 502, + }); + expect(handleApiResponseMock).not.toHaveBeenCalled(); + }); + + it("returns error with statusText when response body is not JSON", async () => { + // Given + const mockResponse = new Response("Service Unavailable", { + status: 503, + statusText: "Service Unavailable", + }); + fetchMock.mockResolvedValue(mockResponse); + + // When + const result = await getResourceEvents("resource-123"); + + // Then + expect(result).toEqual({ + error: "Service Unavailable", + status: 503, + }); + }); + + it("returns generic error when fetch throws", async () => { + // Given + fetchMock.mockRejectedValue(new Error("Network failure")); + + // When + const result = await getResourceEvents("resource-123"); + + // Then + expect(result).toEqual({ error: "An unexpected error occurred." }); + }); + + it.each([ + "../../../etc/passwd", + "resource/../../secret", + "id with spaces", + "id;rm -rf /", + "", + "resource%00id", + "", + ])("rejects malicious or invalid resourceId: %s", async (maliciousId) => { + // When + const result = await getResourceEvents(maliciousId); + + // Then + expect(result).toEqual({ error: "Invalid resource ID format." }); + expect(fetchMock).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/actions/resources/resources.ts b/ui/actions/resources/resources.ts index d65505704f..36f9761959 100644 --- a/ui/actions/resources/resources.ts +++ b/ui/actions/resources/resources.ts @@ -160,6 +160,64 @@ export const getLatestMetadataInfo = async ({ } }; +const SAFE_RESOURCE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/; + +export const getResourceEvents = async ( + resourceId: string, + { + includeReadEvents = false, + lookbackDays = 90, + pageSize = 50, + }: { + includeReadEvents?: boolean; + lookbackDays?: number; + pageSize?: number; + } = {}, +) => { + if (!SAFE_RESOURCE_ID_PATTERN.test(resourceId)) { + return { error: "Invalid resource ID format." }; + } + + const headers = await getAuthHeaders({ contentType: false }); + + const url = new URL(`${apiBaseUrl}/resources/${resourceId}/events`); + url.searchParams.append("include_read_events", String(includeReadEvents)); + url.searchParams.append("lookback_days", String(lookbackDays)); + url.searchParams.append("page[size]", String(pageSize)); + + try { + const response = await fetch(url.toString(), { headers }); + + if (!response.ok) { + const rawText = await response.text().catch(() => ""); + const defaultError = "An error occurred while fetching events."; + try { + const errorData = rawText ? JSON.parse(rawText) : null; + return { + error: + errorData?.errors?.[0]?.detail || + errorData?.error || + errorData?.message || + rawText || + response.statusText || + defaultError, + status: response.status, + }; + } catch { + return { + error: rawText || response.statusText || defaultError, + status: response.status, + }; + } + } + + return handleApiResponse(response); + } catch (error) { + console.error("Error fetching resource events:", error); + return { error: "An unexpected error occurred." }; + } +}; + export const getResourceById = async ( id: string, { diff --git a/ui/components/findings/table/finding-detail.test.tsx b/ui/components/findings/table/finding-detail.test.tsx index 1d01919326..ec2e84e2fb 100644 --- a/ui/components/findings/table/finding-detail.test.tsx +++ b/ui/components/findings/table/finding-detail.test.tsx @@ -132,6 +132,10 @@ vi.mock("./delta-indicator", () => ({ DeltaIndicator: () => null, })); +vi.mock("@/components/shared/events-timeline/events-timeline", () => ({ + EventsTimeline: () =>
, +})); + vi.mock("react-markdown", () => ({ default: ({ children }: { children: string }) => {children}, })); diff --git a/ui/components/findings/table/finding-detail.tsx b/ui/components/findings/table/finding-detail.tsx index 195dadc042..d8a3918e14 100644 --- a/ui/components/findings/table/finding-detail.tsx +++ b/ui/components/findings/table/finding-detail.tsx @@ -2,7 +2,7 @@ import { ExternalLink, Link, VolumeX, X } from "lucide-react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; -import { type ReactNode, useState } from "react"; +import { type ReactNode, useEffect, useState } from "react"; import ReactMarkdown from "react-markdown"; import { @@ -23,6 +23,7 @@ import { TooltipContent, TooltipTrigger, } from "@/components/shadcn"; +import { EventsTimeline } from "@/components/shared/events-timeline/events-timeline"; import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; import { CustomLink } from "@/components/ui/custom/custom-link"; import { EntityInfo } from "@/components/ui/entities"; @@ -92,6 +93,12 @@ export const FindingDetail = ({ const searchParams = useSearchParams(); const [isMuteModalOpen, setIsMuteModalOpen] = useState(false); + const [activeTab, setActiveTab] = useState("general"); + + useEffect(() => { + setActiveTab("general"); + }, [findingDetails.id]); + const copyFindingUrl = () => { const params = new URLSearchParams(searchParams.toString()); params.set("id", findingDetails.id); @@ -172,12 +179,13 @@ export const FindingDetail = ({
{/* Tabs */} - +
General Resources Scans + Events {!attributes.muted && ( @@ -470,6 +478,14 @@ export const FindingDetail = ({

)} + + {/* Events Tab */} + + +
); diff --git a/ui/components/resources/table/resource-detail-content.tsx b/ui/components/resources/table/resource-detail-content.tsx index 1e4c1f1f15..edbe9494f8 100644 --- a/ui/components/resources/table/resource-detail-content.tsx +++ b/ui/components/resources/table/resource-detail-content.tsx @@ -18,6 +18,7 @@ import { TooltipContent, TooltipTrigger, } from "@/components/shadcn"; +import { EventsTimeline } from "@/components/shared/events-timeline/events-timeline"; import { BreadcrumbNavigation, CustomBreadcrumbItem } from "@/components/ui"; import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; import { @@ -134,6 +135,11 @@ export const ResourceDetailContent = ({ const attributes = resource.attributes; const providerData = resource.relationships.provider.data.attributes; + // Reset to overview tab when switching resources + useEffect(() => { + setActiveTab("overview"); + }, [resourceId]); + // Cleanup abort controller on unmount useEffect(() => { return () => { @@ -409,6 +415,7 @@ export const ResourceDetailContent = ({ Findings {totalFindings > 0 && `(${totalFindings})`} + Events {/* Overview Tab */} @@ -535,6 +542,14 @@ export const ResourceDetailContent = ({ )} + + {/* Events Tab */} + + +
); diff --git a/ui/components/shared/events-timeline/events-timeline.test.tsx b/ui/components/shared/events-timeline/events-timeline.test.tsx new file mode 100644 index 0000000000..3daf445384 --- /dev/null +++ b/ui/components/shared/events-timeline/events-timeline.test.tsx @@ -0,0 +1,293 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { EventsTimeline } from "./events-timeline"; + +const { getResourceEventsMock } = vi.hoisted(() => ({ + getResourceEventsMock: vi.fn(), +})); + +vi.mock("@/actions/resources", () => ({ + getResourceEvents: getResourceEventsMock, +})); + +const mockEvent = { + type: "resource-events" as const, + id: "event-1", + attributes: { + event_time: "2026-01-26T16:05:07Z", + event_name: "CreateStack", + event_source: "cloudformation.amazonaws.com", + actor: "admin-role", + actor_uid: "arn:aws:sts::123456:assumed-role/admin-role", + actor_type: "AssumedRole", + source_ip_address: "192.168.1.1", + user_agent: "aws-cli/2.0", + request_data: { stackName: "my-stack" }, + response_data: { stackId: "arn:aws:cloudformation:..." }, + error_code: null, + error_message: null, + }, +}; + +const mockErrorEvent = { + ...mockEvent, + id: "event-2", + attributes: { + ...mockEvent.attributes, + event_name: "DeleteStack", + error_code: "AccessDenied", + error_message: "User is not authorized", + }, +}; + +describe("EventsTimeline", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("shows non-AWS message for non-AWS providers", () => { + // When + render(); + + // Then + expect( + screen.getByText("Events timeline is only available for AWS resources."), + ).toBeInTheDocument(); + expect(getResourceEventsMock).not.toHaveBeenCalled(); + }); + + it("shows loading state while fetching events", async () => { + // Given + getResourceEventsMock.mockReturnValue(new Promise(() => {})); // never resolves + + // When + render(); + + // Then + await waitFor(() => { + expect( + screen.getByText("Fetching CloudTrail events..."), + ).toBeInTheDocument(); + }); + }); + + it("renders events after successful fetch", async () => { + // Given + getResourceEventsMock.mockResolvedValue({ + data: [mockEvent], + }); + + // When + render(); + + // Then + await waitFor(() => { + expect(screen.getByText("CreateStack")).toBeInTheDocument(); + }); + expect(screen.getByText("1 event")).toBeInTheDocument(); + expect(screen.getByText("admin-role")).toBeInTheDocument(); + }); + + it("shows empty state when no events are returned", async () => { + // Given + getResourceEventsMock.mockResolvedValue({ data: [] }); + + // When + render(); + + // Then + await waitFor(() => { + expect( + screen.getByText("No events found in the last 90 days."), + ).toBeInTheDocument(); + }); + }); + + it("shows error message when API returns an error", async () => { + // Given + getResourceEventsMock.mockResolvedValue({ + error: "Provider credentials are invalid or expired.", + status: 502, + }); + + // When + render(); + + // Then + await waitFor(() => { + expect( + screen.getByText( + "Provider credentials are invalid or expired. Please reconnect your AWS provider.", + ), + ).toBeInTheDocument(); + }); + expect(screen.getByText("Try again")).toBeInTheDocument(); + }); + + it("shows 503 error message for AWS unavailability", async () => { + // Given + getResourceEventsMock.mockResolvedValue({ + error: "Service Unavailable", + status: 503, + }); + + // When + render(); + + // Then + await waitFor(() => { + expect( + screen.getByText( + "AWS CloudTrail is temporarily unavailable. Please try again later.", + ), + ).toBeInTheDocument(); + }); + }); + + it("shows raw error message for other error statuses", async () => { + // Given + getResourceEventsMock.mockResolvedValue({ + error: "Invalid lookback_days parameter.", + status: 400, + }); + + // When + render(); + + // Then + await waitFor(() => { + expect( + screen.getByText("Invalid lookback_days parameter."), + ).toBeInTheDocument(); + }); + }); + + it("expands event to show detail card on click", async () => { + // Given + const user = userEvent.setup(); + getResourceEventsMock.mockResolvedValue({ data: [mockEvent] }); + + render(); + + await waitFor(() => { + expect(screen.getByText("CreateStack")).toBeInTheDocument(); + }); + + // When - click the event row to expand + await user.click(screen.getByText("CreateStack")); + + // Then - detail card should show expanded info + expect(screen.getByText("192.168.1.1")).toBeInTheDocument(); + expect(screen.getByText("AssumedRole")).toBeInTheDocument(); + expect(screen.getByText("Request")).toBeInTheDocument(); + expect(screen.getByText("Response")).toBeInTheDocument(); + }); + + it("collapses event when clicked again", async () => { + // Given + const user = userEvent.setup(); + getResourceEventsMock.mockResolvedValue({ data: [mockEvent] }); + + render(); + + await waitFor(() => { + expect(screen.getByText("CreateStack")).toBeInTheDocument(); + }); + + // When - expand then collapse (use getAllByText since expanded card also shows event name) + await user.click(screen.getByText("CreateStack")); + expect(screen.getByText("Request")).toBeInTheDocument(); + + await user.click(screen.getAllByText("CreateStack")[0]); + + // Then + expect(screen.queryByText("Request")).not.toBeInTheDocument(); + }); + + it("shows error banner for events with error codes", async () => { + // Given + const user = userEvent.setup(); + getResourceEventsMock.mockResolvedValue({ + data: [mockErrorEvent], + }); + + render(); + + await waitFor(() => { + expect(screen.getByText("DeleteStack")).toBeInTheDocument(); + }); + + // When + await user.click(screen.getByText("DeleteStack")); + + // Then + expect(screen.getByText("AccessDenied")).toBeInTheDocument(); + expect(screen.getByText("User is not authorized")).toBeInTheDocument(); + }); + + it("refetches events when include read events checkbox is toggled", async () => { + // Given + const user = userEvent.setup(); + getResourceEventsMock.mockResolvedValue({ data: [mockEvent] }); + + render(); + + await waitFor(() => { + expect(screen.getByText("CreateStack")).toBeInTheDocument(); + }); + + expect(getResourceEventsMock).toHaveBeenCalledWith("resource-1", { + includeReadEvents: false, + }); + + // When + await user.click(screen.getByRole("checkbox")); + + // Then + await waitFor(() => { + expect(getResourceEventsMock).toHaveBeenCalledWith("resource-1", { + includeReadEvents: true, + }); + }); + }); + + it("retries fetch when retry button is clicked", async () => { + // Given + const user = userEvent.setup(); + getResourceEventsMock + .mockResolvedValueOnce({ error: "Something went wrong", status: 500 }) + .mockResolvedValueOnce({ data: [mockEvent] }); + + render(); + + await waitFor(() => { + expect(screen.getByText("Try again")).toBeInTheDocument(); + }); + + // When + await user.click(screen.getByText("Try again")); + + // Then + await waitFor(() => { + expect(screen.getByText("CreateStack")).toBeInTheDocument(); + }); + expect(getResourceEventsMock).toHaveBeenCalledTimes(2); + }); + + it("handles null response gracefully", async () => { + // Given + getResourceEventsMock.mockResolvedValue(null); + + // When + render(); + + // Then + await waitFor(() => { + expect( + screen.getByText("Failed to fetch events. Please try again."), + ).toBeInTheDocument(); + }); + }); +}); diff --git a/ui/components/shared/events-timeline/events-timeline.tsx b/ui/components/shared/events-timeline/events-timeline.tsx new file mode 100644 index 0000000000..415b699050 --- /dev/null +++ b/ui/components/shared/events-timeline/events-timeline.tsx @@ -0,0 +1,458 @@ +"use client"; + +import { + AlertTriangle, + ChevronRight, + Clock, + Download, + Loader2, + Server, + Shield, +} from "lucide-react"; +import { useEffect, useState, useTransition } from "react"; + +import { getResourceEvents } from "@/actions/resources"; +import { + Alert, + AlertDescription, + Badge, + Button, + Card, + Checkbox, + InfoField, +} from "@/components/shadcn"; +import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; +import { cn } from "@/lib/utils"; +import { ResourceEventProps } from "@/types"; + +interface EventsTimelineProps { + resourceId?: string; + isAwsProvider: boolean; +} + +export const EventsTimeline = ({ + resourceId, + isAwsProvider, +}: EventsTimelineProps) => { + const [events, setEvents] = useState([]); + const [error, setError] = useState(null); + const [errorStatus, setErrorStatus] = useState(null); + const [expandedRows, setExpandedRows] = useState>(new Set()); + const [includeReadEvents, setIncludeReadEvents] = useState(false); + const [hasFetched, setHasFetched] = useState(false); + const [retryCount, setRetryCount] = useState(0); + const [isPending, startTransition] = useTransition(); + + useEffect(() => { + if (!isAwsProvider || !resourceId) return; + + let cancelled = false; + + setError(null); + setErrorStatus(null); + setHasFetched(false); + + startTransition(async () => { + try { + const response = await getResourceEvents(resourceId, { + includeReadEvents, + }); + + if (cancelled) return; + + if (!response) { + setError("Failed to fetch events. Please try again."); + return; + } + + if (response.error) { + setError(response.error); + setErrorStatus(response.status || null); + return; + } + + setEvents(response.data || []); + setExpandedRows(new Set()); + } catch (err) { + if (cancelled) return; + console.error("Error fetching events:", err); + setError("An unexpected error occurred."); + } finally { + if (!cancelled) setHasFetched(true); + } + }); + + return () => { + cancelled = true; + }; + }, [resourceId, includeReadEvents, isAwsProvider, retryCount]); + + const toggleRow = (eventId: string) => { + setExpandedRows((prev) => { + const next = new Set(prev); + if (next.has(eventId)) { + next.delete(eventId); + } else { + next.add(eventId); + } + return next; + }); + }; + + const downloadEventJson = (event: ResourceEventProps) => { + const json = JSON.stringify(event.attributes, null, 2); + const blob = new Blob([json], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `event-${event.attributes.event_name}-${event.attributes.event_time}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + setTimeout(() => URL.revokeObjectURL(url), 100); + }; + + if (!isAwsProvider) { + return ( +
+
+ +
+

+ Events timeline is only available for AWS resources. +

+
+ ); + } + + if (isPending && !hasFetched) { + return ( +
+ +

+ Fetching CloudTrail events... +

+
+ ); + } + + if (error) { + return ( +
+ + + +

+ {errorStatus === 502 + ? "Provider credentials are invalid or expired. Please reconnect your AWS provider." + : errorStatus === 503 + ? "AWS CloudTrail is temporarily unavailable. Please try again later." + : error} +

+ +
+
+
+ ); + } + + return ( +
+ {/* Controls bar */} +
+ +
+ {isPending && } + + {events.length} event{events.length !== 1 && "s"} + +
+
+ + {/* Timeline */} + {events.length === 0 && hasFetched ? ( +
+
+ +
+

+ No events found in the last 90 days. +

+

+ CloudTrail events may take up to 15 minutes to appear after an + action is performed. +

+
+ ) : ( +
+ {/* Timeline vertical line */} +
+ +
+ {events.map((event, index) => { + const isExpanded = expandedRows.has(event.id); + const attrs = event.attributes; + const hasError = !!attrs.error_code; + const isLast = index === events.length - 1; + + return ( +
+ {/* Timeline node + row */} + + + {/* Expanded detail card */} + {isExpanded && ( +
+ + {/* Header bar */} +
+
+ + + {attrs.event_name} + + + via {attrs.event_source} + +
+ +
+ + {/* Detail rows */} +
+
+ + {new Date(attrs.event_time).toLocaleString()} + +
+
+ +
+ + {attrs.actor} + + {attrs.actor_type} +
+
+
+
+ + + {attrs.source_ip_address} + + +
+
+ + {/* Error banner */} + {hasError && ( + + + + + {attrs.error_code} + + {attrs.error_message && ( + <> + + {" "} + |{" "} + + + {attrs.error_message} + + + )} + + + )} + + {/* JSON payloads */} + {(attrs.request_data || attrs.response_data) && ( +
+ {attrs.request_data && ( + + )} + {attrs.response_data && ( + + )} +
+ )} +
+
+ )} +
+ ); + })} +
+
+ )} +
+ ); +}; + +// --- Sub-components --- + +const JsonBlock = ({ + label, + data, +}: { + label: string; + data: Record; +}) => { + const [collapsed, setCollapsed] = useState(true); + const json = JSON.stringify(data, null, 2); + const lineCount = json.split("\n").length; + const isLong = lineCount > 8; + + return ( +
+
+ + {label} + + +
+
+
+          {json}
+        
+ {isLong && collapsed && ( +
+ +
+ )} +
+
+ ); +}; + +// --- Helpers --- + +const dateFmt = new Intl.DateTimeFormat(undefined, { + month: "short", + day: "numeric", +}); +const timeFmt = new Intl.DateTimeFormat(undefined, { + hour: "2-digit", + minute: "2-digit", +}); + +const formatShortDate = (iso: string) => { + const d = new Date(iso); + return `${dateFmt.format(d)} ${timeFmt.format(d)}`; +}; diff --git a/ui/types/resources.ts b/ui/types/resources.ts index 5413809145..da00e70cb2 100644 --- a/ui/types/resources.ts +++ b/ui/types/resources.ts @@ -145,3 +145,24 @@ export interface ResourceApiResponse { included: ResourceItemProps[]; meta: Meta; } + +export interface ResourceEventAttributes { + event_time: string; + event_name: string; + event_source: string; + actor: string; + actor_uid: string; + actor_type: string; + source_ip_address: string; + user_agent: string; + request_data: Record | null; + response_data: Record | null; + error_code: string | null; + error_message: string | null; +} + +export interface ResourceEventProps { + type: "resource-events"; + id: string; + attributes: ResourceEventAttributes; +} From 887a20f06e83d35cacc82956a73d9d471d3b0971 Mon Sep 17 00:00:00 2001 From: Zakir Jiwani Date: Tue, 17 Mar 2026 04:55:06 -0400 Subject: [PATCH 016/366] feat: CORS_ALLOWED_ORIGINS configurable via environment variable (#10355) Co-authored-by: Pepe Fagoaga --- api/CHANGELOG.md | 4 ++++ api/src/backend/config/django/production.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 79e49d2b3b..023abaeb40 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to the **Prowler API** are documented in this file. ## [1.22.0] (Prowler UNRELEASED) +### 🚀 Added + +- `CORS_ALLOWED_ORIGINS` configurable via environment variable [(#10355)](https://github.com/prowler-cloud/prowler/pull/10355) + ### 🔄 Changed - Attack Paths: Complete migration to private graph labels and properties, removing deprecated dual-write support [(#10268)](https://github.com/prowler-cloud/prowler/pull/10268) diff --git a/api/src/backend/config/django/production.py b/api/src/backend/config/django/production.py index b2769237fc..91bd50d0d1 100644 --- a/api/src/backend/config/django/production.py +++ b/api/src/backend/config/django/production.py @@ -3,6 +3,10 @@ from config.env import env DEBUG = env.bool("DJANGO_DEBUG", default=False) ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["localhost", "127.0.0.1"]) +CORS_ALLOWED_ORIGINS = env.list( + "DJANGO_CORS_ALLOWED_ORIGINS", + default=["http://localhost", "http://127.0.0.1"], +) # Database # TODO Use Django database routers https://docs.djangoproject.com/en/5.0/topics/db/multi-db/#automatic-database-routing From 451071d6949efcaf49d845eec5159591ee952954 Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:53:37 +0100 Subject: [PATCH 017/366] feat(image): add image provider to UI (#10167) Co-authored-by: alejandrobailo Co-authored-by: Claude Opus 4.6 Co-authored-by: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> --- prowler/CHANGELOG.md | 1 + prowler/providers/image/image_provider.py | 96 +++++++++--- .../image/lib/arguments/arguments.py | 4 +- tests/providers/image/image_provider_test.py | 142 +++++++++++++++++- ui/CHANGELOG.md | 1 + .../_components/accounts-selector.tsx | 2 + .../_components/provider-type-selector.tsx | 13 +- .../filters/custom-provider-inputs.tsx | 10 ++ .../findings/table/provider-icon-cell.tsx | 2 + ui/components/graphs/sankey-chart.tsx | 7 +- .../providers-badge/image-provider-badge.tsx | 36 +++++ ui/components/icons/providers-badge/index.ts | 5 +- .../providers/radio-group-provider.tsx | 6 + .../workflow/forms/base-credentials-form.tsx | 7 + .../workflow/forms/connect-account-form.tsx | 5 + .../image-credentials-form.tsx | 84 +++++++++++ .../workflow/forms/via-credentials/index.ts | 1 + .../ui/entities/get-provider-logo.tsx | 5 + ui/hooks/use-credentials-form.ts | 9 ++ ui/lib/external-urls.ts | 5 + .../build-crendentials.ts | 30 ++++ .../provider-credential-fields.ts | 12 ++ ui/types/components.ts | 10 ++ ui/types/formSchemas.ts | 117 +++++++++++---- ui/types/providers.ts | 2 + 25 files changed, 551 insertions(+), 61 deletions(-) create mode 100644 ui/components/icons/providers-badge/image-provider-badge.tsx create mode 100644 ui/components/providers/workflow/forms/via-credentials/image-credentials-form.tsx diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 3e7efa203a..1cc4cbc410 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### 🚀 Added +- `misconfig` scanner as default for Image provider scans [(#10167)](https://github.com/prowler-cloud/prowler/pull/10167) - `entra_conditional_access_policy_device_code_flow_blocked` check for M365 provider [(#10218)](https://github.com/prowler-cloud/prowler/pull/10218) - CheckMetadata Pydantic validators [(#8584)](https://github.com/prowler-cloud/prowler/pull/8583) - `entra_conditional_access_policy_require_mfa_for_admin_portals` check for Azure provider and update CIS compliance [(#10330)](https://github.com/prowler-cloud/prowler/pull/10330) diff --git a/prowler/providers/image/image_provider.py b/prowler/providers/image/image_provider.py index 9817656dd2..4beb5eca4e 100644 --- a/prowler/providers/image/image_provider.py +++ b/prowler/providers/image/image_provider.py @@ -5,6 +5,7 @@ import os import re import subprocess import sys +import tempfile from typing import Generator from alive_progress import alive_bar @@ -88,7 +89,9 @@ class ImageProvider(Provider): self.images = images if images is not None else [] self.image_list_file = image_list_file - self.scanners = scanners if scanners is not None else ["vuln", "secret"] + self.scanners = ( + scanners if scanners is not None else ["vuln", "secret", "misconfig"] + ) self.image_config_scanners = ( image_config_scanners if image_config_scanners is not None else [] ) @@ -100,6 +103,10 @@ class ImageProvider(Provider): self._session = None self._identity = "prowler" self._listing_only = False + self._trivy_cache_dir_obj = tempfile.TemporaryDirectory( + prefix="prowler-trivy-cache-" + ) + self._trivy_cache_dir = self._trivy_cache_dir_obj.name # Registry authentication (follows IaC pattern: explicit params, env vars internal) self.registry_username = registry_username or os.environ.get( @@ -329,9 +336,15 @@ class ImageProvider(Provider): def _is_registry_url(image_uid: str) -> bool: """Determine whether an image UID is a registry URL (namespace only). - A registry URL like ``docker.io/andoniaf`` has a registry host but - the remaining part contains no ``/`` (no repo) and no ``:`` (no tag). + Bare hostnames like "714274078102.dkr.ecr.eu-west-1.amazonaws.com" + or "myregistry.com:5000" are registry URLs (dots in host, no slash). + Image references like "alpine:3.18" or "nginx" are not. """ + if "/" not in image_uid: + host_part = image_uid.split(":")[0] + if "." in host_part: + return True + registry_host = ImageProvider._extract_registry(image_uid) if not registry_host: return False @@ -340,6 +353,8 @@ class ImageProvider(Provider): def cleanup(self) -> None: """Clean up any resources after scanning.""" + if hasattr(self, "_trivy_cache_dir_obj"): + self._trivy_cache_dir_obj.cleanup() def _process_finding( self, @@ -540,6 +555,8 @@ class ImageProvider(Provider): trivy_command = [ "trivy", "image", + "--cache-dir", + self._trivy_cache_dir, "--format", "json", "--scanners", @@ -928,6 +945,9 @@ class ImageProvider(Provider): Uses registry HTTP APIs directly instead of Trivy to avoid false failures caused by Trivy DB download issues. + For bare registry hostnames (e.g. ECR URLs passed by the API as provider_uid), + uses the OCI catalog endpoint instead of trivy image. + Args: image: Container image or registry URL to test raise_on_exception: Whether to raise exceptions @@ -946,32 +966,34 @@ class ImageProvider(Provider): if not image: return Connection(is_connected=False, error="Image name is required") + # Registry URL (bare hostname) → test via OCI catalog if ImageProvider._is_registry_url(image): - # Registry enumeration mode — test by listing repositories - adapter = create_registry_adapter( + return ImageProvider._test_registry_connection( registry_url=image, - username=registry_username, - password=registry_password, - token=registry_token, + registry_username=registry_username, + registry_password=registry_password, + registry_token=registry_token, ) - adapter.list_repositories() - return Connection(is_connected=True) - # Image reference mode — verify the specific tag exists + # Image reference → verify tag exists via registry API registry_host = ImageProvider._extract_registry(image) - repo_and_tag = image[len(registry_host) + 1 :] if registry_host else image - if ":" in repo_and_tag: - repository, tag = repo_and_tag.rsplit(":", 1) - else: - repository = repo_and_tag - tag = "latest" - - is_dockerhub = not registry_host or registry_host in ( + is_dockerhub = registry_host is None or registry_host in ( "docker.io", "registry-1.docker.io", ) - # Docker Hub official images use "library/" prefix + # Parse repository and tag from the image reference + ref = image.rsplit("@", 1)[0] if "@" in image else image + last_segment = ref.split("/")[-1] + if ":" in last_segment: + tag = last_segment.split(":")[-1] + base = ref[: -(len(tag) + 1)] + else: + tag = "latest" + base = ref + + repository = base[len(registry_host) + 1 :] if registry_host else base + if is_dockerhub and "/" not in repository: repository = f"library/{repository}" @@ -1013,3 +1035,37 @@ class ImageProvider(Provider): is_connected=False, error=f"Unexpected error: {str(error)}", ) + + @staticmethod + def _test_registry_connection( + registry_url: str, + registry_username: str | None = None, + registry_password: str | None = None, + registry_token: str | None = None, + ) -> "Connection": + """Test connection to a registry URL by listing repositories via OCI catalog.""" + try: + adapter = create_registry_adapter( + registry_url=registry_url, + username=registry_username, + password=registry_password, + token=registry_token, + ) + adapter.list_repositories() + return Connection(is_connected=True) + except Exception as error: + error_str = str(error).lower() + if "401" in error_str or "unauthorized" in error_str: + return Connection( + is_connected=False, + error="Authentication failed. Check registry credentials.", + ) + elif "404" in error_str or "not found" in error_str: + return Connection( + is_connected=False, + error="Registry catalog not found.", + ) + return Connection( + is_connected=False, + error=f"Failed to connect to registry: {str(error)[:200]}", + ) diff --git a/prowler/providers/image/lib/arguments/arguments.py b/prowler/providers/image/lib/arguments/arguments.py index 3dfe9cf92b..529061befc 100644 --- a/prowler/providers/image/lib/arguments/arguments.py +++ b/prowler/providers/image/lib/arguments/arguments.py @@ -50,9 +50,9 @@ def init_parser(self): "--scanner", dest="scanners", nargs="+", - default=["vuln", "secret"], + default=["vuln", "secret", "misconfig"], choices=SCANNERS_CHOICES, - help="Trivy scanners to use. Default: vuln, secret. Available: vuln, secret, misconfig, license", + help="Trivy scanners to use. Default: vuln, secret, misconfig. Available: vuln, secret, misconfig, license", ) scan_config_group.add_argument( diff --git a/tests/providers/image/image_provider_test.py b/tests/providers/image/image_provider_test.py index b25e9edfc2..ab9358b002 100644 --- a/tests/providers/image/image_provider_test.py +++ b/tests/providers/image/image_provider_test.py @@ -53,7 +53,7 @@ class TestImageProvider: assert provider._type == "image" assert provider.type == "image" assert provider.images == ["alpine:3.18"] - assert provider.scanners == ["vuln", "secret"] + assert provider.scanners == ["vuln", "secret", "misconfig"] assert provider.image_config_scanners == [] assert provider.trivy_severity == [] assert provider.ignore_unfixed is False @@ -698,8 +698,118 @@ class TestExtractRegistry: class TestIsRegistryUrl: - def test_registry_url_with_namespace(self): - assert ImageProvider._is_registry_url("docker.io/andoniaf") is True + def test_bare_ecr_hostname(self): + assert ImageProvider._is_registry_url( + "714274078102.dkr.ecr.eu-west-1.amazonaws.com" + ) + + def test_bare_hostname_with_port(self): + assert ImageProvider._is_registry_url("myregistry.com:5000") + + def test_bare_ghcr(self): + assert ImageProvider._is_registry_url("ghcr.io") + + def test_registry_with_namespace_only(self): + """Registry URL with a single path segment (no tag) is a registry URL.""" + assert ImageProvider._is_registry_url("ghcr.io/myorg") + + def test_image_reference_not_registry(self): + """Full image reference with repo and tag is not a registry URL.""" + assert not ImageProvider._is_registry_url("ghcr.io/myorg/repo:tag") + + def test_simple_image_name(self): + assert not ImageProvider._is_registry_url("alpine:3.18") + + def test_bare_image_no_tag(self): + assert not ImageProvider._is_registry_url("nginx") + + def test_dockerhub_namespace(self): + assert not ImageProvider._is_registry_url("library/alpine") + + +class TestTestRegistryConnection: + @patch("prowler.providers.image.image_provider.create_registry_adapter") + def test_registry_connection_success(self, mock_factory): + """Test that a bare hostname triggers registry catalog test.""" + mock_adapter = MagicMock() + mock_adapter.list_repositories.return_value = ["repo1"] + mock_factory.return_value = mock_adapter + + result = ImageProvider.test_connection( + image="714274078102.dkr.ecr.eu-west-1.amazonaws.com", + registry_username="user", + registry_password="pass", + ) + + assert result.is_connected is True + mock_factory.assert_called_once_with( + registry_url="714274078102.dkr.ecr.eu-west-1.amazonaws.com", + username="user", + password="pass", + token=None, + ) + mock_adapter.list_repositories.assert_called_once() + + @patch("prowler.providers.image.image_provider.create_registry_adapter") + def test_registry_connection_auth_failure(self, mock_factory): + """Test that 401 from registry adapter returns auth failure.""" + mock_adapter = MagicMock() + mock_adapter.list_repositories.side_effect = Exception("401 unauthorized") + mock_factory.return_value = mock_adapter + + result = ImageProvider.test_connection( + image="714274078102.dkr.ecr.eu-west-1.amazonaws.com", + ) + + assert result.is_connected is False + assert "Authentication failed" in result.error + + @patch("prowler.providers.image.image_provider.create_registry_adapter") + def test_registry_connection_generic_error(self, mock_factory): + """Test that a generic error from registry adapter returns error message.""" + mock_adapter = MagicMock() + mock_adapter.list_repositories.side_effect = Exception("connection refused") + mock_factory.return_value = mock_adapter + + result = ImageProvider.test_connection( + image="myregistry.example.com", + ) + + assert result.is_connected is False + assert "Failed to connect to registry" in result.error + + @patch("prowler.providers.image.image_provider.create_registry_adapter") + def test_image_reference_uses_registry_adapter(self, mock_factory): + """Test that a full image reference uses registry adapter to verify tag.""" + mock_adapter = MagicMock() + mock_adapter.list_tags.return_value = ["3.18", "latest"] + mock_factory.return_value = mock_adapter + + result = ImageProvider.test_connection(image="alpine:3.18") + + assert result.is_connected is True + mock_adapter.list_tags.assert_called_once() + + +class TestTrivyAuthIntegration: + @patch("subprocess.run") + def test_run_scan_passes_trivy_env_with_credentials(self, mock_subprocess): + """Test that run_scan() passes TRIVY_USERNAME/PASSWORD via env when credentials are set.""" + mock_subprocess.return_value = MagicMock( + returncode=0, stdout=get_sample_trivy_json_output(), stderr="" + ) + provider = _make_provider( + images=["ghcr.io/user/image:tag"], + registry_username="myuser", + registry_password="mypass", + ) + + list(provider.run_scan()) + + call_kwargs = mock_subprocess.call_args + env = call_kwargs.kwargs.get("env") or call_kwargs[1].get("env") + assert env["TRIVY_USERNAME"] == "myuser" + assert env["TRIVY_PASSWORD"] == "mypass" def test_registry_url_ghcr(self): assert ImageProvider._is_registry_url("ghcr.io/org") is True @@ -734,6 +844,16 @@ class TestCleanup: provider.cleanup() provider.cleanup() + def test_cleanup_removes_trivy_cache_dir(self): + """Test that cleanup removes the temporary Trivy cache directory.""" + provider = _make_provider() + cache_dir = provider._trivy_cache_dir + assert os.path.isdir(cache_dir) + + provider.cleanup() + + assert not os.path.isdir(cache_dir) + class TestImageProviderInputValidation: def test_invalid_timeout_format_raises_error(self): @@ -804,6 +924,22 @@ class TestImageProviderInputValidation: with pytest.raises(ImageInvalidConfigScannerError): _make_provider(image_config_scanners=["misconfig", "vuln"]) + @patch("subprocess.run") + def test_trivy_command_includes_cache_dir(self, mock_subprocess): + """Test that Trivy command includes --cache-dir for cache isolation.""" + provider = _make_provider() + mock_subprocess.return_value = MagicMock( + returncode=0, stdout=get_empty_trivy_output(), stderr="" + ) + + for _ in provider._scan_single_image("alpine:3.18"): + pass + + call_args = mock_subprocess.call_args[0][0] + assert "--cache-dir" in call_args + idx = call_args.index("--cache-dir") + assert call_args[idx + 1] == provider._trivy_cache_dir + @patch("subprocess.run") def test_trivy_command_includes_image_config_scanners(self, mock_subprocess): """Test that Trivy command includes --image-config-scanners when set.""" diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 9842b76870..d124555be7 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the **Prowler UI** are documented in this file. ### 🚀 Added +- Image (Container Registry) provider support in UI: badge icon, credentials form, and provider-type filtering [(#10167)](https://github.com/prowler-cloud/prowler/pull/10167) - Organization and organizational unit row actions (Edit Name, Update Credentials, Test Connections, Delete) in providers table dropdown [(#10317)](https://github.com/prowler-cloud/prowler/pull/10317) - Events tab in Findings and Resource detail cards showing an AWS CloudTrail timeline with expandable event rows, actor info, request/response JSON payloads, and error details [(#10320)](https://github.com/prowler-cloud/prowler/pull/10320) diff --git a/ui/app/(prowler)/_overview/_components/accounts-selector.tsx b/ui/app/(prowler)/_overview/_components/accounts-selector.tsx index 0e3845787e..0be0e1dd20 100644 --- a/ui/app/(prowler)/_overview/_components/accounts-selector.tsx +++ b/ui/app/(prowler)/_overview/_components/accounts-selector.tsx @@ -11,6 +11,7 @@ import { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -35,6 +36,7 @@ const PROVIDER_ICON: Record = { m365: , github: , iac: , + image: , oraclecloud: , mongodbatlas: , alibabacloud: , diff --git a/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx b/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx index 881bc54969..bf7adf87cd 100644 --- a/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx +++ b/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx @@ -1,7 +1,7 @@ "use client"; import { useSearchParams } from "next/navigation"; -import { lazy, Suspense } from "react"; +import { type ComponentType, lazy, Suspense } from "react"; import { MultiSelect, @@ -48,6 +48,11 @@ const IacProviderBadge = lazy(() => default: m.IacProviderBadge, })), ); +const ImageProviderBadge = lazy(() => + import("@/components/icons/providers-badge").then((m) => ({ + default: m.ImageProviderBadge, + })), +); const OracleCloudProviderBadge = lazy(() => import("@/components/icons/providers-badge").then((m) => ({ default: m.OracleCloudProviderBadge, @@ -82,7 +87,7 @@ const IconPlaceholder = ({ width, height }: IconProps) => ( const PROVIDER_DATA: Record< ProviderType, - { label: string; icon: React.ComponentType } + { label: string; icon: ComponentType } > = { aws: { label: "Amazon Web Services", @@ -112,6 +117,10 @@ const PROVIDER_DATA: Record< label: "Infrastructure as Code", icon: IacProviderBadge, }, + image: { + label: "Container Registry", + icon: ImageProviderBadge, + }, oraclecloud: { label: "Oracle Cloud Infrastructure", icon: OracleCloudProviderBadge, diff --git a/ui/components/filters/custom-provider-inputs.tsx b/ui/components/filters/custom-provider-inputs.tsx index 46937401dc..3e435b3fd7 100644 --- a/ui/components/filters/custom-provider-inputs.tsx +++ b/ui/components/filters/custom-provider-inputs.tsx @@ -6,6 +6,7 @@ import { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -85,6 +86,15 @@ export const CustomProviderInputIac = () => { ); }; +export const CustomProviderInputImage = () => { + return ( +
+ +

Container Registry

+
+ ); +}; + export const CustomProviderInputOracleCloud = () => { return (
diff --git a/ui/components/findings/table/provider-icon-cell.tsx b/ui/components/findings/table/provider-icon-cell.tsx index 613d236985..f368b776cd 100644 --- a/ui/components/findings/table/provider-icon-cell.tsx +++ b/ui/components/findings/table/provider-icon-cell.tsx @@ -6,6 +6,7 @@ import { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -22,6 +23,7 @@ export const PROVIDER_ICONS = { m365: M365ProviderBadge, github: GitHubProviderBadge, iac: IacProviderBadge, + image: ImageProviderBadge, oraclecloud: OracleCloudProviderBadge, mongodbatlas: MongoDBAtlasProviderBadge, alibabacloud: AlibabaCloudProviderBadge, diff --git a/ui/components/graphs/sankey-chart.tsx b/ui/components/graphs/sankey-chart.tsx index fe7708e7a1..fc7a913444 100644 --- a/ui/components/graphs/sankey-chart.tsx +++ b/ui/components/graphs/sankey-chart.tsx @@ -5,7 +5,7 @@ import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; import { Rectangle, ResponsiveContainer, Sankey, Tooltip } from "recharts"; -import { PROVIDER_ICONS } from "@/components/icons/providers-badge"; +import { PROVIDER_BADGE_BY_NAME } from "@/components/icons/providers-badge"; import { initializeChartColors } from "@/lib/charts/colors"; import { PROVIDER_DISPLAY_NAMES } from "@/types/providers"; import { SEVERITY_FILTER_MAP } from "@/types/severities"; @@ -209,7 +209,7 @@ const CustomNode = ({ } }; - const IconComponent = PROVIDER_ICONS[nodeName]; + const IconComponent = PROVIDER_BADGE_BY_NAME[nodeName]; const hasIcon = IconComponent !== undefined; const iconSize = 24; const iconGap = 8; @@ -620,7 +620,8 @@ export function SankeyChart({

{zeroDataProviders.map((provider) => { - const IconComponent = PROVIDER_ICONS[provider.displayName]; + const IconComponent = + PROVIDER_BADGE_BY_NAME[provider.displayName]; return (
= ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/ui/components/icons/providers-badge/index.ts b/ui/components/icons/providers-badge/index.ts index 9b8b5e7a82..5627352ab0 100644 --- a/ui/components/icons/providers-badge/index.ts +++ b/ui/components/icons/providers-badge/index.ts @@ -9,6 +9,7 @@ import { CloudflareProviderBadge } from "./cloudflare-provider-badge"; import { GCPProviderBadge } from "./gcp-provider-badge"; import { GitHubProviderBadge } from "./github-provider-badge"; import { IacProviderBadge } from "./iac-provider-badge"; +import { ImageProviderBadge } from "./image-provider-badge"; import { KS8ProviderBadge } from "./ks8-provider-badge"; import { M365ProviderBadge } from "./m365-provider-badge"; import { MongoDBAtlasProviderBadge } from "./mongodbatlas-provider-badge"; @@ -23,6 +24,7 @@ export { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -31,7 +33,7 @@ export { }; // Map provider display names to their icon components -export const PROVIDER_ICONS: Record> = { +export const PROVIDER_BADGE_BY_NAME: Record> = { AWS: AWSProviderBadge, Azure: AzureProviderBadge, "Google Cloud": GCPProviderBadge, @@ -39,6 +41,7 @@ export const PROVIDER_ICONS: Record> = { "Microsoft 365": M365ProviderBadge, GitHub: GitHubProviderBadge, "Infrastructure as Code": IacProviderBadge, + "Container Registry": ImageProviderBadge, "Oracle Cloud Infrastructure": OracleCloudProviderBadge, "MongoDB Atlas": MongoDBAtlasProviderBadge, "Alibaba Cloud": AlibabaCloudProviderBadge, diff --git a/ui/components/providers/radio-group-provider.tsx b/ui/components/providers/radio-group-provider.tsx index b071062e94..a179772e20 100644 --- a/ui/components/providers/radio-group-provider.tsx +++ b/ui/components/providers/radio-group-provider.tsx @@ -16,6 +16,7 @@ import { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -65,6 +66,11 @@ const PROVIDERS = [ label: "Infrastructure as Code", badge: IacProviderBadge, }, + { + value: "image", + label: "Container Registry", + badge: ImageProviderBadge, + }, { value: "oraclecloud", label: "Oracle Cloud Infrastructure", diff --git a/ui/components/providers/workflow/forms/base-credentials-form.tsx b/ui/components/providers/workflow/forms/base-credentials-form.tsx index 959749b8b7..aa61d07479 100644 --- a/ui/components/providers/workflow/forms/base-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/base-credentials-form.tsx @@ -23,6 +23,7 @@ import { GCPDefaultCredentials, GCPServiceAccountKey, IacCredentials, + ImageCredentials, KubernetesCredentials, M365CertificateCredentials, M365ClientSecretCredentials, @@ -52,6 +53,7 @@ import { import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form"; import { GitHubCredentialsForm } from "./via-credentials/github-credentials-form"; import { IacCredentialsForm } from "./via-credentials/iac-credentials-form"; +import { ImageCredentialsForm } from "./via-credentials/image-credentials-form"; import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-form"; import { MongoDBAtlasCredentialsForm } from "./via-credentials/mongodbatlas-credentials-form"; import { OpenStackCredentialsForm } from "./via-credentials/openstack-credentials-form"; @@ -211,6 +213,11 @@ export const BaseCredentialsForm = ({ control={form.control as unknown as Control} /> )} + {providerType === "image" && ( + } + /> + )} {providerType === "oraclecloud" && ( } diff --git a/ui/components/providers/workflow/forms/connect-account-form.tsx b/ui/components/providers/workflow/forms/connect-account-form.tsx index 2b62a79459..0c0687081e 100644 --- a/ui/components/providers/workflow/forms/connect-account-form.tsx +++ b/ui/components/providers/workflow/forms/connect-account-form.tsx @@ -81,6 +81,11 @@ const getProviderFieldDetails = (providerType?: ProviderType) => { label: "Repository URL", placeholder: "e.g. https://github.com/user/repo", }; + case "image": + return { + label: "Registry URL", + placeholder: "e.g. 123456789012.dkr.ecr.us-east-1.amazonaws.com", + }; case "oraclecloud": return { label: "Tenancy OCID", diff --git a/ui/components/providers/workflow/forms/via-credentials/image-credentials-form.tsx b/ui/components/providers/workflow/forms/via-credentials/image-credentials-form.tsx new file mode 100644 index 0000000000..b071b1c013 --- /dev/null +++ b/ui/components/providers/workflow/forms/via-credentials/image-credentials-form.tsx @@ -0,0 +1,84 @@ +import { Control } from "react-hook-form"; + +import { WizardInputField } from "@/components/providers/workflow/forms/fields"; +import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields"; +import { ImageCredentials } from "@/types"; + +export const ImageCredentialsForm = ({ + control, +}: { + control: Control; +}) => { + return ( + <> +
+
+ Connect via Registry Credentials +
+
+ Provide registry credentials to authenticate with your container + registry (all fields are optional). +
+
+ + + + +
+
+ Scan Scope +
+
+ Limit which repositories and tags are scanned using regex patterns. +
+
+ + + + ); +}; diff --git a/ui/components/providers/workflow/forms/via-credentials/index.ts b/ui/components/providers/workflow/forms/via-credentials/index.ts index 2b44d85485..4f5b55846a 100644 --- a/ui/components/providers/workflow/forms/via-credentials/index.ts +++ b/ui/components/providers/workflow/forms/via-credentials/index.ts @@ -1,6 +1,7 @@ export * from "./azure-credentials-form"; export * from "./github-credentials-form"; export * from "./iac-credentials-form"; +export * from "./image-credentials-form"; export * from "./k8s-credentials-form"; export * from "./mongodbatlas-credentials-form"; export * from "./openstack-credentials-form"; diff --git a/ui/components/ui/entities/get-provider-logo.tsx b/ui/components/ui/entities/get-provider-logo.tsx index 7ac437a4af..1fb8a5ae41 100644 --- a/ui/components/ui/entities/get-provider-logo.tsx +++ b/ui/components/ui/entities/get-provider-logo.tsx @@ -6,6 +6,7 @@ import { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -30,6 +31,8 @@ export const getProviderLogo = (provider: ProviderType) => { return ; case "iac": return ; + case "image": + return ; case "oraclecloud": return ; case "mongodbatlas": @@ -61,6 +64,8 @@ export const getProviderName = (provider: ProviderType): string => { return "GitHub"; case "iac": return "Infrastructure as Code"; + case "image": + return "Container Registry"; case "oraclecloud": return "Oracle Cloud Infrastructure"; case "mongodbatlas": diff --git a/ui/hooks/use-credentials-form.ts b/ui/hooks/use-credentials-form.ts index 10008c70d5..020dc7a6b2 100644 --- a/ui/hooks/use-credentials-form.ts +++ b/ui/hooks/use-credentials-form.ts @@ -226,6 +226,15 @@ export const useCredentialsForm = ({ [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT]: "", [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD]: "", }; + case "image": + return { + ...baseDefaults, + [ProviderCredentialFields.REGISTRY_USERNAME]: "", + [ProviderCredentialFields.REGISTRY_PASSWORD]: "", + [ProviderCredentialFields.REGISTRY_TOKEN]: "", + [ProviderCredentialFields.IMAGE_FILTER]: "", + [ProviderCredentialFields.TAG_FILTER]: "", + }; default: return baseDefaults; } diff --git a/ui/lib/external-urls.ts b/ui/lib/external-urls.ts index 654bf27b43..023b90df1e 100644 --- a/ui/lib/external-urls.ts +++ b/ui/lib/external-urls.ts @@ -57,6 +57,11 @@ export const getProviderHelpText = (provider: string) => { text: "Need help scanning your Infrastructure as Code repository?", link: "https://goto.prowler.com/provider-iac", }; + case "image": + return { + text: "Need help scanning your container registry?", + link: "https://goto.prowler.com/provider-image", + }; case "oraclecloud": return { text: "Need help connecting your Oracle Cloud account?", diff --git a/ui/lib/provider-credentials/build-crendentials.ts b/ui/lib/provider-credentials/build-crendentials.ts index fb0e29a18b..9238a9d9a6 100644 --- a/ui/lib/provider-credentials/build-crendentials.ts +++ b/ui/lib/provider-credentials/build-crendentials.ts @@ -278,6 +278,32 @@ export const buildIacSecret = (formData: FormData) => { return filterEmptyValues(secret); }; +export const buildImageSecret = (formData: FormData) => { + const secret = { + [ProviderCredentialFields.REGISTRY_USERNAME]: getFormValue( + formData, + ProviderCredentialFields.REGISTRY_USERNAME, + ), + [ProviderCredentialFields.REGISTRY_PASSWORD]: getFormValue( + formData, + ProviderCredentialFields.REGISTRY_PASSWORD, + ), + [ProviderCredentialFields.REGISTRY_TOKEN]: getFormValue( + formData, + ProviderCredentialFields.REGISTRY_TOKEN, + ), + [ProviderCredentialFields.IMAGE_FILTER]: getFormValue( + formData, + ProviderCredentialFields.IMAGE_FILTER, + ), + [ProviderCredentialFields.TAG_FILTER]: getFormValue( + formData, + ProviderCredentialFields.TAG_FILTER, + ), + }; + return filterEmptyValues(secret); +}; + /** * Utility function to safely encode a string to base64 * Handles UTF-8 characters properly without using deprecated APIs @@ -426,6 +452,10 @@ export const buildSecretConfig = ( secretType: "static", secret: buildIacSecret(formData), }), + image: () => ({ + secretType: "static", + secret: buildImageSecret(formData), + }), oraclecloud: () => ({ secretType: "static", secret: buildOracleCloudSecret(formData, providerUid), diff --git a/ui/lib/provider-credentials/provider-credential-fields.ts b/ui/lib/provider-credentials/provider-credential-fields.ts index 6ed6aedfdd..c5773233e1 100644 --- a/ui/lib/provider-credentials/provider-credential-fields.ts +++ b/ui/lib/provider-credentials/provider-credential-fields.ts @@ -53,6 +53,13 @@ export const ProviderCredentialFields = { REPOSITORY_URL: "repository_url", ACCESS_TOKEN: "access_token", + // Image (Container Registry) fields + REGISTRY_USERNAME: "registry_username", + REGISTRY_PASSWORD: "registry_password", + REGISTRY_TOKEN: "registry_token", + IMAGE_FILTER: "image_filter", + TAG_FILTER: "tag_filter", + // OCI fields OCI_USER: "user", OCI_FINGERPRINT: "fingerprint", @@ -106,6 +113,11 @@ export const ErrorPointers = { GITHUB_APP_KEY: "/data/attributes/secret/github_app_key_content", REPOSITORY_URL: "/data/attributes/secret/repository_url", ACCESS_TOKEN: "/data/attributes/secret/access_token", + REGISTRY_USERNAME: "/data/attributes/secret/registry_username", + REGISTRY_PASSWORD: "/data/attributes/secret/registry_password", + REGISTRY_TOKEN: "/data/attributes/secret/registry_token", + IMAGE_FILTER: "/data/attributes/secret/image_filter", + TAG_FILTER: "/data/attributes/secret/tag_filter", CERTIFICATE_CONTENT: "/data/attributes/secret/certificate_content", OCI_USER: "/data/attributes/secret/user", OCI_FINGERPRINT: "/data/attributes/secret/fingerprint", diff --git a/ui/types/components.ts b/ui/types/components.ts index cf74cc7970..96fbce5083 100644 --- a/ui/types/components.ts +++ b/ui/types/components.ts @@ -304,6 +304,15 @@ export type IacCredentials = { [ProviderCredentialFields.PROVIDER_ID]: string; }; +export type ImageCredentials = { + [ProviderCredentialFields.REGISTRY_USERNAME]?: string; + [ProviderCredentialFields.REGISTRY_PASSWORD]?: string; + [ProviderCredentialFields.REGISTRY_TOKEN]?: string; + [ProviderCredentialFields.IMAGE_FILTER]?: string; + [ProviderCredentialFields.TAG_FILTER]?: string; + [ProviderCredentialFields.PROVIDER_ID]: string; +}; + export type OCICredentials = { [ProviderCredentialFields.OCI_USER]: string; [ProviderCredentialFields.OCI_FINGERPRINT]: string; @@ -363,6 +372,7 @@ export type CredentialsFormSchema = | GCPServiceAccountKey | KubernetesCredentials | IacCredentials + | ImageCredentials | M365Credentials | OCICredentials | MongoDBAtlasCredentials diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts index 9c4cc58f41..c7044c782c 100644 --- a/ui/types/formSchemas.ts +++ b/ui/types/formSchemas.ts @@ -115,6 +115,11 @@ export const addProviderFormSchema = z [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(), providerUid: z.string().trim().min(1, "Provider ID is required"), }), + z.object({ + providerType: z.literal("image"), + [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(), + providerUid: z.string().trim().min(1, "Provider ID is required"), + }), z.object({ providerType: z.literal("oraclecloud"), [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(), @@ -269,44 +274,63 @@ export const addCredentialsFormSchema = ( .string() .min(1, "Access Key Secret is required"), } - : providerType === "cloudflare" + : providerType === "image" ? { - [ProviderCredentialFields.CLOUDFLARE_API_TOKEN]: - z.string().optional(), - [ProviderCredentialFields.CLOUDFLARE_API_KEY]: z + [ProviderCredentialFields.REGISTRY_USERNAME]: z + .string() + .optional(), + [ProviderCredentialFields.REGISTRY_PASSWORD]: z + .string() + .optional(), + [ProviderCredentialFields.REGISTRY_TOKEN]: z + .string() + .optional(), + [ProviderCredentialFields.IMAGE_FILTER]: z + .string() + .optional(), + [ProviderCredentialFields.TAG_FILTER]: z .string() .optional(), - [ProviderCredentialFields.CLOUDFLARE_API_EMAIL]: - z - .string() - .superRefine((val, ctx) => { - if (val && val.trim() !== "") { - const emailRegex = - /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(val)) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: - "Please enter a valid email address", - }); - } - } - }) - .optional(), } - : providerType === "openstack" + : providerType === "cloudflare" ? { - [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT]: + [ProviderCredentialFields.CLOUDFLARE_API_TOKEN]: + z.string().optional(), + [ProviderCredentialFields.CLOUDFLARE_API_KEY]: + z.string().optional(), + [ProviderCredentialFields.CLOUDFLARE_API_EMAIL]: z .string() - .min( - 1, - "Clouds YAML content is required", - ), - [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD]: - z.string().min(1, "Cloud name is required"), + .superRefine((val, ctx) => { + if (val && val.trim() !== "") { + const emailRegex = + /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(val)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + "Please enter a valid email address", + }); + } + } + }) + .optional(), } - : {}), + : providerType === "openstack" + ? { + [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT]: + z + .string() + .min( + 1, + "Clouds YAML content is required", + ), + [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD]: + z + .string() + .min(1, "Cloud name is required"), + } + : {}), }) .superRefine((data: Record, ctx) => { if (providerType === "m365") { @@ -369,6 +393,39 @@ export const addCredentialsFormSchema = ( } } + if (providerType === "image") { + const token = data[ProviderCredentialFields.REGISTRY_TOKEN]; + const username = data[ProviderCredentialFields.REGISTRY_USERNAME]; + const password = data[ProviderCredentialFields.REGISTRY_PASSWORD]; + + // When a token is provided, username/password validation is skipped (matches API behavior) + if (!token || token.trim() === "") { + if ( + username && + username.trim() !== "" && + (!password || password.trim() === "") + ) { + ctx.addIssue({ + code: "custom", + message: + "Registry Password is required when providing a username", + path: [ProviderCredentialFields.REGISTRY_PASSWORD], + }); + } + if ( + password && + password.trim() !== "" && + (!username || username.trim() === "") + ) { + ctx.addIssue({ + code: "custom", + message: + "Registry Username is required when providing a password", + path: [ProviderCredentialFields.REGISTRY_USERNAME], + }); + } + } + } if (providerType === "cloudflare") { // For Cloudflare, validation depends on the 'via' parameter if (via === "api_token") { diff --git a/ui/types/providers.ts b/ui/types/providers.ts index 6e6a2c79c6..066bea0414 100644 --- a/ui/types/providers.ts +++ b/ui/types/providers.ts @@ -7,6 +7,7 @@ export const PROVIDER_TYPES = [ "mongodbatlas", "github", "iac", + "image", "oraclecloud", "alibabacloud", "cloudflare", @@ -24,6 +25,7 @@ export const PROVIDER_DISPLAY_NAMES: Record = { mongodbatlas: "MongoDB Atlas", github: "GitHub", iac: "Infrastructure as Code", + image: "Container Registry", oraclecloud: "Oracle Cloud Infrastructure", alibabacloud: "Alibaba Cloud", cloudflare: "Cloudflare", From df680ef2771d848a6358bb6e13125cf2aeec164e Mon Sep 17 00:00:00 2001 From: Pawan Gambhir <122386120+AlienwareSec@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:32:48 +0530 Subject: [PATCH 018/366] fix(route53): resolve false positive in dangling IP check (#9952) Co-authored-by: Daniel Barranquero --- prowler/CHANGELOG.md | 4 + .../route53_dangling_ip_subdomain_takeover.py | 22 ++--- .../aws/services/route53/route53_service.py | 35 ++++++++ ...e53_dangling_ip_subdomain_takeover_test.py | 82 ++++++++++++++++++- 4 files changed, 130 insertions(+), 13 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 1cc4cbc410..758cefe560 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -24,6 +24,10 @@ All notable changes to the **Prowler SDK** are documented in this file. - Bump `multipart` to 1.3.1 to fix [GHSA-p2m9-wcp5-6qw3](https://github.com/defnull/multipart/security/advisories/GHSA-p2m9-wcp5-6qw3) [(#10331)](https://github.com/prowler-cloud/prowler/pull/10331) +### 🐞 Fixed + +- Route53 dangling IP check false positive when using `--region` flag [(#9952)](https://github.com/prowler-cloud/prowler/pull/9952) + --- ## [5.20.0] (Prowler v5.20.0) diff --git a/prowler/providers/aws/services/route53/route53_dangling_ip_subdomain_takeover/route53_dangling_ip_subdomain_takeover.py b/prowler/providers/aws/services/route53/route53_dangling_ip_subdomain_takeover/route53_dangling_ip_subdomain_takeover.py index dca63d47ef..444f7dd2a2 100644 --- a/prowler/providers/aws/services/route53/route53_dangling_ip_subdomain_takeover/route53_dangling_ip_subdomain_takeover.py +++ b/prowler/providers/aws/services/route53/route53_dangling_ip_subdomain_takeover/route53_dangling_ip_subdomain_takeover.py @@ -12,19 +12,21 @@ class route53_dangling_ip_subdomain_takeover(Check): def execute(self) -> Check_Report_AWS: findings = [] + # When --region is used, Route53 service gathers EIPs from all regions + # to avoid false positives. Otherwise, use ec2_client data directly. + if route53_client.all_account_elastic_ips: + public_ips = list(route53_client.all_account_elastic_ips) + else: + public_ips = [eip.public_ip for eip in ec2_client.elastic_ips] + + # Add Network Interface public IPs from audited regions + for ni in ec2_client.network_interfaces.values(): + if ni.association and ni.association.get("PublicIp"): + public_ips.append(ni.association.get("PublicIp")) + for record_set in route53_client.record_sets: # Check only A records and avoid aliases (only need to check IPs not AWS Resources) if record_set.type == "A" and not record_set.is_alias: - # Gather Elastic IPs and Network Interfaces Public IPs inside the AWS Account - public_ips = [] - public_ips.extend([eip.public_ip for eip in ec2_client.elastic_ips]) - # Add public IPs from Network Interfaces - for network_interface in ec2_client.network_interfaces.values(): - if ( - network_interface.association - and network_interface.association.get("PublicIp") - ): - public_ips.append(network_interface.association.get("PublicIp")) for record in record_set.records: # Check if record is an IP Address if validate_ip_address(record): diff --git a/prowler/providers/aws/services/route53/route53_service.py b/prowler/providers/aws/services/route53/route53_service.py index 2e0eeb4499..bb579ca6ee 100644 --- a/prowler/providers/aws/services/route53/route53_service.py +++ b/prowler/providers/aws/services/route53/route53_service.py @@ -13,10 +13,20 @@ class Route53(AWSService): super().__init__(__class__.__name__, provider, global_service=True) self.hosted_zones = {} self.record_sets = [] + self.all_account_elastic_ips = [] self._list_hosted_zones() self._list_query_logging_configs() self._list_tags_for_resource() self._list_resource_record_sets() + # Gather Elastic IPs from all regions only when the --region flag is used, + # since EC2 service will only have EIPs from the specified region(s) but + # Route53 is global and can reference EIPs from any region. + if ( + "route53_dangling_ip_subdomain_takeover" + in provider.audit_metadata.expected_checks + and provider._identity.audited_regions + ): + self._get_all_region_elastic_ips() def _list_hosted_zones(self): logger.info("Route53 - Listing Hosting Zones...") @@ -77,6 +87,31 @@ class Route53(AWSService): f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def _get_all_region_elastic_ips(self): + """Gather Elastic IPs from all enabled regions since Route53 is a global service. + + When running Prowler with --region, ec2_client.elastic_ips is scoped + to the specified region(s). Route53 records can reference EIPs from any + region, so we need to query all enabled regions to avoid false positives. + """ + logger.info("Route53 - Gathering Elastic IPs from all regions...") + all_regions = self.provider._enabled_regions or set( + self.provider._identity.audited_regions + ) + + for region in all_regions: + try: + regional_ec2_client = self.session.client("ec2", region_name=region) + for addr in regional_ec2_client.describe_addresses().get( + "Addresses", [] + ): + if "PublicIp" in addr: + self.all_account_elastic_ips.append(addr["PublicIp"]) + except Exception as error: + logger.warning( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + def _list_query_logging_configs(self): logger.info("Route53 - Listing Query Logging Configs...") try: diff --git a/tests/providers/aws/services/route53/route53_dangling_ip_subdomain_takeover/route53_dangling_ip_subdomain_takeover_test.py b/tests/providers/aws/services/route53/route53_dangling_ip_subdomain_takeover/route53_dangling_ip_subdomain_takeover_test.py index 8d05e64a6e..63cc9193d9 100644 --- a/tests/providers/aws/services/route53/route53_dangling_ip_subdomain_takeover/route53_dangling_ip_subdomain_takeover_test.py +++ b/tests/providers/aws/services/route53/route53_dangling_ip_subdomain_takeover/route53_dangling_ip_subdomain_takeover_test.py @@ -3,7 +3,11 @@ from unittest import mock from boto3 import client, resource from moto import mock_aws -from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider +from tests.providers.aws.utils import ( + AWS_REGION_US_EAST_1, + AWS_REGION_US_WEST_2, + set_mocked_aws_provider, +) HOSTED_ZONE_NAME = "testdns.aws.com." @@ -309,7 +313,10 @@ class Test_route53_dangling_ip_subdomain_takeover: from prowler.providers.aws.services.ec2.ec2_service import EC2 from prowler.providers.aws.services.route53.route53_service import Route53 - aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], + expected_checks=["route53_dangling_ip_subdomain_takeover"], + ) with mock.patch( "prowler.providers.common.provider.Provider.get_global_provider", @@ -387,7 +394,10 @@ class Test_route53_dangling_ip_subdomain_takeover: from prowler.providers.aws.services.ec2.ec2_service import EC2 from prowler.providers.aws.services.route53.route53_service import Route53 - aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], + expected_checks=["route53_dangling_ip_subdomain_takeover"], + ) with mock.patch( "prowler.providers.common.provider.Provider.get_global_provider", @@ -426,3 +436,69 @@ class Test_route53_dangling_ip_subdomain_takeover: result[0].resource_arn == f"arn:{aws_provider.identity.partition}:route53:::hostedzone/{zone_id.replace('/hostedzone/', '')}" ) + + @mock_aws + def test_hosted_zone_eip_cross_region(self): + """EIP in us-west-2 referenced by Route53 A record should PASS even when auditing us-east-1 only.""" + conn = client("route53", region_name=AWS_REGION_US_EAST_1) + ec2_west = client("ec2", region_name=AWS_REGION_US_WEST_2) + + address = "17.5.7.3" + ec2_west.allocate_address(Domain="vpc", Address=address) + + zone_id = conn.create_hosted_zone( + Name=HOSTED_ZONE_NAME, CallerReference=str(hash("foo")) + )["HostedZone"]["Id"] + + record_set_name = "foo.bar.testdns.aws.com." + record_ip = address + conn.change_resource_record_sets( + HostedZoneId=zone_id, + ChangeBatch={ + "Changes": [ + { + "Action": "CREATE", + "ResourceRecordSet": { + "Name": record_set_name, + "Type": "A", + "ResourceRecords": [{"Value": record_ip}], + }, + } + ] + }, + ) + from prowler.providers.aws.services.ec2.ec2_service import EC2 + from prowler.providers.aws.services.route53.route53_service import Route53 + + # Audit only us-east-1 but enable both regions so Route53 finds the cross-region EIP + aws_provider = set_mocked_aws_provider( + audited_regions=[AWS_REGION_US_EAST_1], + enabled_regions={AWS_REGION_US_EAST_1, AWS_REGION_US_WEST_2}, + expected_checks=["route53_dangling_ip_subdomain_takeover"], + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.route53.route53_dangling_ip_subdomain_takeover.route53_dangling_ip_subdomain_takeover.route53_client", + new=Route53(aws_provider), + ): + with mock.patch( + "prowler.providers.aws.services.route53.route53_dangling_ip_subdomain_takeover.route53_dangling_ip_subdomain_takeover.ec2_client", + new=EC2(aws_provider), + ): + from prowler.providers.aws.services.route53.route53_dangling_ip_subdomain_takeover.route53_dangling_ip_subdomain_takeover import ( + route53_dangling_ip_subdomain_takeover, + ) + + check = route53_dangling_ip_subdomain_takeover() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Route53 record {record_ip} (name: {record_set_name}) in Hosted Zone {HOSTED_ZONE_NAME} is not a dangling IP." + ) From 88ce188103a713f6171c79f2b070276034bf0728 Mon Sep 17 00:00:00 2001 From: Sandiyo Christan <55909152+sandiyochristan@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:54:24 +0530 Subject: [PATCH 019/366] fix(api): [security] use psycopg2.sql to safely compose DDL in PostgresEnumMigration (#10166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Adrián Peña Co-authored-by: Pepe Fagoaga --- api/CHANGELOG.md | 4 ++ api/src/backend/api/db_utils.py | 15 ++++-- api/src/backend/api/tests/test_db_utils.py | 60 ++++++++++++++++++++++ api/src/backend/api/tests/test_views.py | 8 ++- 4 files changed, 82 insertions(+), 5 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 023abaeb40..8c5a47ad6b 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -13,6 +13,10 @@ All notable changes to the **Prowler API** are documented in this file. - Attack Paths: Complete migration to private graph labels and properties, removing deprecated dual-write support [(#10268)](https://github.com/prowler-cloud/prowler/pull/10268) - Attack Paths: Added tenant and provider related labels to the nodes so they can be easily filtered on custom queries [(#10308)](https://github.com/prowler-cloud/prowler/pull/10308) +### 🔐 Security + +- Use `psycopg2.sql` to safely compose DDL in `PostgresEnumMigration`, preventing SQL injection via f-string interpolation [(#10166)](https://github.com/prowler-cloud/prowler/pull/10166) + --- ## [1.21.0] (Prowler v5.20.0) diff --git a/api/src/backend/api/db_utils.py b/api/src/backend/api/db_utils.py index 7a71084ccd..e3b11d7084 100644 --- a/api/src/backend/api/db_utils.py +++ b/api/src/backend/api/db_utils.py @@ -18,6 +18,7 @@ from django.db import ( ) from django_celery_beat.models import PeriodicTask from psycopg2 import connect as psycopg2_connect +from psycopg2 import sql as psycopg2_sql from psycopg2.extensions import AsIs, new_type, register_adapter, register_type from rest_framework_json_api.serializers import ValidationError @@ -280,15 +281,23 @@ class PostgresEnumMigration: self.enum_values = enum_values def create_enum_type(self, apps, schema_editor): # noqa: F841 - string_enum_values = ", ".join([f"'{value}'" for value in self.enum_values]) with schema_editor.connection.cursor() as cursor: cursor.execute( - f"CREATE TYPE {self.enum_name} AS ENUM ({string_enum_values});" + psycopg2_sql.SQL("CREATE TYPE {} AS ENUM ({})").format( + psycopg2_sql.Identifier(self.enum_name), + psycopg2_sql.SQL(", ").join( + psycopg2_sql.Literal(v) for v in self.enum_values + ), + ) ) def drop_enum_type(self, apps, schema_editor): # noqa: F841 with schema_editor.connection.cursor() as cursor: - cursor.execute(f"DROP TYPE {self.enum_name};") + cursor.execute( + psycopg2_sql.SQL("DROP TYPE {}").format( + psycopg2_sql.Identifier(self.enum_name) + ) + ) class PostgresEnumField(models.Field): diff --git a/api/src/backend/api/tests/test_db_utils.py b/api/src/backend/api/tests/test_db_utils.py index f52bb349aa..18935b9a3e 100644 --- a/api/src/backend/api/tests/test_db_utils.py +++ b/api/src/backend/api/tests/test_db_utils.py @@ -6,10 +6,12 @@ import pytest from django.conf import settings from django.db import DEFAULT_DB_ALIAS, OperationalError from freezegun import freeze_time +from psycopg2 import sql as psycopg2_sql from rest_framework_json_api.serializers import ValidationError from api.db_utils import ( POSTGRES_TENANT_VAR, + PostgresEnumMigration, _should_create_index_on_partition, batch_delete, create_objects_in_batches, @@ -910,3 +912,61 @@ class TestRlsTransaction: cursor.execute("SELECT 1") result = cursor.fetchone() assert result[0] == 1 + + +class TestPostgresEnumMigration: + """ + Verify that PostgresEnumMigration builds DDL statements via psycopg2.sql + so that enum type names and values are always properly quoted — preventing + SQL injection through f-string interpolation. + """ + + def _make_mock_schema_editor(self): + mock_cursor = MagicMock() + mock_conn = MagicMock() + mock_conn.cursor.return_value.__enter__ = MagicMock(return_value=mock_cursor) + mock_conn.cursor.return_value.__exit__ = MagicMock(return_value=False) + mock_schema_editor = MagicMock() + mock_schema_editor.connection = mock_conn + return mock_schema_editor, mock_cursor + + def test_create_enum_type_generates_correct_sql(self): + """create_enum_type builds a proper CREATE TYPE … AS ENUM via psycopg2.sql.""" + migration = PostgresEnumMigration("my_enum", ("val_a", "val_b")) + schema_editor, mock_cursor = self._make_mock_schema_editor() + + migration.create_enum_type(apps=None, schema_editor=schema_editor) + + mock_cursor.execute.assert_called_once() + query_arg = mock_cursor.execute.call_args[0][0] + assert isinstance( + query_arg, psycopg2_sql.Composable + ), "create_enum_type must pass a psycopg2.sql.Composable, not a raw string." + # Verify the composed SQL structure: CREATE TYPE AS ENUM () + parts = query_arg.seq + assert parts[0] == psycopg2_sql.SQL("CREATE TYPE ") + assert isinstance(parts[1], psycopg2_sql.Identifier) + assert parts[1].strings == ("my_enum",) + assert parts[2] == psycopg2_sql.SQL(" AS ENUM (") + # The enum values are a Composed of Literal items joined by ", " + enum_literals = [p for p in parts[3].seq if isinstance(p, psycopg2_sql.Literal)] + assert [lit._wrapped for lit in enum_literals] == ["val_a", "val_b"] + assert parts[4] == psycopg2_sql.SQL(")") + + def test_drop_enum_type_generates_correct_sql(self): + """drop_enum_type builds a proper DROP TYPE via psycopg2.sql.""" + migration = PostgresEnumMigration("my_enum", ("val_a",)) + schema_editor, mock_cursor = self._make_mock_schema_editor() + + migration.drop_enum_type(apps=None, schema_editor=schema_editor) + + mock_cursor.execute.assert_called_once() + query_arg = mock_cursor.execute.call_args[0][0] + assert isinstance( + query_arg, psycopg2_sql.Composable + ), "drop_enum_type must pass a psycopg2.sql.Composable, not a raw string." + # Verify the composed SQL structure: DROP TYPE + parts = query_arg.seq + assert parts[0] == psycopg2_sql.SQL("DROP TYPE ") + assert isinstance(parts[1], psycopg2_sql.Identifier) + assert parts[1].strings == ("my_enum",) diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index 5aa7ae143d..d857b2bc07 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -7858,8 +7858,12 @@ class TestUserRoleRelationshipViewSet: assert response.status_code == status.HTTP_204_NO_CONTENT relationships = UserRoleRelationship.objects.filter(user=create_test_user.id) assert relationships.count() == 4 - for relationship in relationships[2:]: # Skip admin role - assert relationship.role.id in [r.id for r in roles_fixture[:2]] + # Use set membership instead of positional slicing — QuerySet ordering is + # non-deterministic without an explicit order_by, which makes slice-based + # checks intermittently fail. + added_role_ids = {r.id for r in roles_fixture[:2]} + relationship_role_ids = {rel.role.id for rel in relationships} + assert added_role_ids.issubset(relationship_role_ids) def test_create_relationship_already_exists( self, authenticated_client, roles_fixture, create_test_user From 4f93a89d1b21b2823a677c278ba6afb9ce8238e7 Mon Sep 17 00:00:00 2001 From: lydiavilchez <114735608+lydiavilchez@users.noreply.github.com> Date: Tue, 17 Mar 2026 13:28:28 +0100 Subject: [PATCH 020/366] feat(ui): add Google Workspace provider integration (#10333) Co-authored-by: alejandrobailo --- ui/CHANGELOG.md | 3 +- .../_components/accounts-selector.tsx | 2 + .../_components/provider-type-selector.tsx | 9 ++ .../findings/table/provider-icon-cell.tsx | 2 + .../googleworkspace-provider-badge.tsx | 44 ++++++++++ ui/components/icons/providers-badge/index.ts | 3 + .../providers/radio-group-provider.tsx | 6 ++ .../workflow/forms/base-credentials-form.tsx | 9 ++ .../workflow/forms/connect-account-form.tsx | 5 ++ .../googleworkspace-credentials-form.tsx | 58 +++++++++++++ .../ui/entities/get-provider-logo.tsx | 5 ++ ui/hooks/use-credentials-form.ts | 8 ++ ui/lib/error-mappings.ts | 6 ++ ui/lib/external-urls.ts | 5 ++ .../build-crendentials.ts | 19 ++++ .../provider-credential-fields.ts | 9 ++ ui/tests/providers/providers-page.ts | 78 +++++++++++++++++ ui/tests/providers/providers.md | 64 ++++++++++++++ ui/tests/providers/providers.spec.ts | 87 +++++++++++++++++++ ui/types/components.ts | 10 ++- ui/types/formSchemas.ts | 66 +++++++++++--- ui/types/providers.ts | 2 + 22 files changed, 488 insertions(+), 12 deletions(-) create mode 100644 ui/components/icons/providers-badge/googleworkspace-provider-badge.tsx create mode 100644 ui/components/providers/workflow/forms/via-credentials/googleworkspace-credentials-form.tsx diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index d124555be7..dc451861c5 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -2,10 +2,11 @@ All notable changes to the **Prowler UI** are documented in this file. -## [1.21.0] (Prowler UNRELEASED) +## [1.21.0] (Prowler v5.21.0 UNRELEASED) ### 🚀 Added +- Google Workspace provider support [(#10333)](https://github.com/prowler-cloud/prowler/pull/10333) - Image (Container Registry) provider support in UI: badge icon, credentials form, and provider-type filtering [(#10167)](https://github.com/prowler-cloud/prowler/pull/10167) - Organization and organizational unit row actions (Edit Name, Update Credentials, Test Connections, Delete) in providers table dropdown [(#10317)](https://github.com/prowler-cloud/prowler/pull/10317) - Events tab in Findings and Resource detail cards showing an AWS CloudTrail timeline with expandable event rows, actor info, request/response JSON payloads, and error details [(#10320)](https://github.com/prowler-cloud/prowler/pull/10320) diff --git a/ui/app/(prowler)/_overview/_components/accounts-selector.tsx b/ui/app/(prowler)/_overview/_components/accounts-selector.tsx index 0be0e1dd20..68ffec2706 100644 --- a/ui/app/(prowler)/_overview/_components/accounts-selector.tsx +++ b/ui/app/(prowler)/_overview/_components/accounts-selector.tsx @@ -10,6 +10,7 @@ import { CloudflareProviderBadge, GCPProviderBadge, GitHubProviderBadge, + GoogleWorkspaceProviderBadge, IacProviderBadge, ImageProviderBadge, KS8ProviderBadge, @@ -35,6 +36,7 @@ const PROVIDER_ICON: Record = { kubernetes: , m365: , github: , + googleworkspace: , iac: , image: , oraclecloud: , diff --git a/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx b/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx index bf7adf87cd..6c62a34667 100644 --- a/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx +++ b/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx @@ -78,6 +78,11 @@ const OpenStackProviderBadge = lazy(() => default: m.OpenStackProviderBadge, })), ); +const GoogleWorkspaceProviderBadge = lazy(() => + import("@/components/icons/providers-badge").then((m) => ({ + default: m.GoogleWorkspaceProviderBadge, + })), +); type IconProps = { width: number; height: number }; @@ -113,6 +118,10 @@ const PROVIDER_DATA: Record< label: "GitHub", icon: GitHubProviderBadge, }, + googleworkspace: { + label: "Google Workspace", + icon: GoogleWorkspaceProviderBadge, + }, iac: { label: "Infrastructure as Code", icon: IacProviderBadge, diff --git a/ui/components/findings/table/provider-icon-cell.tsx b/ui/components/findings/table/provider-icon-cell.tsx index f368b776cd..d56d82b5c9 100644 --- a/ui/components/findings/table/provider-icon-cell.tsx +++ b/ui/components/findings/table/provider-icon-cell.tsx @@ -5,6 +5,7 @@ import { CloudflareProviderBadge, GCPProviderBadge, GitHubProviderBadge, + GoogleWorkspaceProviderBadge, IacProviderBadge, ImageProviderBadge, KS8ProviderBadge, @@ -22,6 +23,7 @@ export const PROVIDER_ICONS = { kubernetes: KS8ProviderBadge, m365: M365ProviderBadge, github: GitHubProviderBadge, + googleworkspace: GoogleWorkspaceProviderBadge, iac: IacProviderBadge, image: ImageProviderBadge, oraclecloud: OracleCloudProviderBadge, diff --git a/ui/components/icons/providers-badge/googleworkspace-provider-badge.tsx b/ui/components/icons/providers-badge/googleworkspace-provider-badge.tsx new file mode 100644 index 0000000000..dc853282ce --- /dev/null +++ b/ui/components/icons/providers-badge/googleworkspace-provider-badge.tsx @@ -0,0 +1,44 @@ +import { type FC } from "react"; + +import { IconSvgProps } from "@/types"; + +export const GoogleWorkspaceProviderBadge: FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/ui/components/icons/providers-badge/index.ts b/ui/components/icons/providers-badge/index.ts index 5627352ab0..be035c9e7d 100644 --- a/ui/components/icons/providers-badge/index.ts +++ b/ui/components/icons/providers-badge/index.ts @@ -8,6 +8,7 @@ import { AzureProviderBadge } from "./azure-provider-badge"; import { CloudflareProviderBadge } from "./cloudflare-provider-badge"; import { GCPProviderBadge } from "./gcp-provider-badge"; import { GitHubProviderBadge } from "./github-provider-badge"; +import { GoogleWorkspaceProviderBadge } from "./googleworkspace-provider-badge"; import { IacProviderBadge } from "./iac-provider-badge"; import { ImageProviderBadge } from "./image-provider-badge"; import { KS8ProviderBadge } from "./ks8-provider-badge"; @@ -23,6 +24,7 @@ export { CloudflareProviderBadge, GCPProviderBadge, GitHubProviderBadge, + GoogleWorkspaceProviderBadge, IacProviderBadge, ImageProviderBadge, KS8ProviderBadge, @@ -40,6 +42,7 @@ export const PROVIDER_BADGE_BY_NAME: Record> = { Kubernetes: KS8ProviderBadge, "Microsoft 365": M365ProviderBadge, GitHub: GitHubProviderBadge, + "Google Workspace": GoogleWorkspaceProviderBadge, "Infrastructure as Code": IacProviderBadge, "Container Registry": ImageProviderBadge, "Oracle Cloud Infrastructure": OracleCloudProviderBadge, diff --git a/ui/components/providers/radio-group-provider.tsx b/ui/components/providers/radio-group-provider.tsx index a179772e20..02466189a1 100644 --- a/ui/components/providers/radio-group-provider.tsx +++ b/ui/components/providers/radio-group-provider.tsx @@ -15,6 +15,7 @@ import { CloudflareProviderBadge, GCPProviderBadge, GitHubProviderBadge, + GoogleWorkspaceProviderBadge, IacProviderBadge, ImageProviderBadge, KS8ProviderBadge, @@ -61,6 +62,11 @@ const PROVIDERS = [ label: "GitHub", badge: GitHubProviderBadge, }, + { + value: "googleworkspace", + label: "Google Workspace", + badge: GoogleWorkspaceProviderBadge, + }, { value: "iac", label: "Infrastructure as Code", diff --git a/ui/components/providers/workflow/forms/base-credentials-form.tsx b/ui/components/providers/workflow/forms/base-credentials-form.tsx index aa61d07479..02a4628e5d 100644 --- a/ui/components/providers/workflow/forms/base-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/base-credentials-form.tsx @@ -22,6 +22,7 @@ import { CloudflareTokenCredentials, GCPDefaultCredentials, GCPServiceAccountKey, + GoogleWorkspaceCredentials, IacCredentials, ImageCredentials, KubernetesCredentials, @@ -52,6 +53,7 @@ import { } from "./select-credentials-type/m365"; import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form"; import { GitHubCredentialsForm } from "./via-credentials/github-credentials-form"; +import { GoogleWorkspaceCredentialsForm } from "./via-credentials/googleworkspace-credentials-form"; import { IacCredentialsForm } from "./via-credentials/iac-credentials-form"; import { ImageCredentialsForm } from "./via-credentials/image-credentials-form"; import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-form"; @@ -263,6 +265,13 @@ export const BaseCredentialsForm = ({ control={form.control as unknown as Control} /> )} + {providerType === "googleworkspace" && ( + + } + /> + )} {!hideActions && (
diff --git a/ui/components/providers/workflow/forms/connect-account-form.tsx b/ui/components/providers/workflow/forms/connect-account-form.tsx index 0c0687081e..0b08aec1c3 100644 --- a/ui/components/providers/workflow/forms/connect-account-form.tsx +++ b/ui/components/providers/workflow/forms/connect-account-form.tsx @@ -111,6 +111,11 @@ const getProviderFieldDetails = (providerType?: ProviderType) => { label: "Project ID", placeholder: "e.g. a1b2c3d4-e5f6-7890-abcd-ef1234567890", }; + case "googleworkspace": + return { + label: "Customer ID", + placeholder: "e.g. C01234abc", + }; default: return { label: "Provider UID", diff --git a/ui/components/providers/workflow/forms/via-credentials/googleworkspace-credentials-form.tsx b/ui/components/providers/workflow/forms/via-credentials/googleworkspace-credentials-form.tsx new file mode 100644 index 0000000000..de692aa1fe --- /dev/null +++ b/ui/components/providers/workflow/forms/via-credentials/googleworkspace-credentials-form.tsx @@ -0,0 +1,58 @@ +import { Control, Controller } from "react-hook-form"; + +import { + WizardInputField, + WizardTextareaField, +} from "@/components/providers/workflow/forms/fields"; +import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields"; +import { GoogleWorkspaceCredentials } from "@/types"; + +export const GoogleWorkspaceCredentialsForm = ({ + control, +}: { + control: Control; +}) => { + return ( + <> +
+
+ Connect via Service Account +
+
+ Provide your Service Account JSON and the admin email to impersonate. +
+
+ {/* Hidden input for customer_id - auto-populated from provider UID */} + } + /> + + +
+ Credentials never leave your browser unencrypted and are stored as + secrets in the backend. You can revoke the Service Account from the + Google Cloud Console anytime if needed. +
+ + ); +}; diff --git a/ui/components/ui/entities/get-provider-logo.tsx b/ui/components/ui/entities/get-provider-logo.tsx index 1fb8a5ae41..6e5381aa66 100644 --- a/ui/components/ui/entities/get-provider-logo.tsx +++ b/ui/components/ui/entities/get-provider-logo.tsx @@ -5,6 +5,7 @@ import { CloudflareProviderBadge, GCPProviderBadge, GitHubProviderBadge, + GoogleWorkspaceProviderBadge, IacProviderBadge, ImageProviderBadge, KS8ProviderBadge, @@ -29,6 +30,8 @@ export const getProviderLogo = (provider: ProviderType) => { return ; case "github": return ; + case "googleworkspace": + return ; case "iac": return ; case "image": @@ -62,6 +65,8 @@ export const getProviderName = (provider: ProviderType): string => { return "Microsoft 365"; case "github": return "GitHub"; + case "googleworkspace": + return "Google Workspace"; case "iac": return "Infrastructure as Code"; case "image": diff --git a/ui/hooks/use-credentials-form.ts b/ui/hooks/use-credentials-form.ts index 020dc7a6b2..68a34183f7 100644 --- a/ui/hooks/use-credentials-form.ts +++ b/ui/hooks/use-credentials-form.ts @@ -226,6 +226,14 @@ export const useCredentialsForm = ({ [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT]: "", [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD]: "", }; + case "googleworkspace": + return { + ...baseDefaults, + [ProviderCredentialFields.GOOGLEWORKSPACE_CUSTOMER_ID]: + providerUid || "", + [ProviderCredentialFields.GOOGLEWORKSPACE_CREDENTIALS_CONTENT]: "", + [ProviderCredentialFields.GOOGLEWORKSPACE_DELEGATED_USER]: "", + }; case "image": return { ...baseDefaults, diff --git a/ui/lib/error-mappings.ts b/ui/lib/error-mappings.ts index 7024801e30..ff15c790c6 100644 --- a/ui/lib/error-mappings.ts +++ b/ui/lib/error-mappings.ts @@ -40,4 +40,10 @@ export const PROVIDER_CREDENTIALS_ERROR_MAPPING: Record = { ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT, [ErrorPointers.OPENSTACK_CLOUDS_YAML_CLOUD]: ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD, + [ErrorPointers.GOOGLEWORKSPACE_CUSTOMER_ID]: + ProviderCredentialFields.GOOGLEWORKSPACE_CUSTOMER_ID, + [ErrorPointers.GOOGLEWORKSPACE_CREDENTIALS_CONTENT]: + ProviderCredentialFields.GOOGLEWORKSPACE_CREDENTIALS_CONTENT, + [ErrorPointers.GOOGLEWORKSPACE_DELEGATED_USER]: + ProviderCredentialFields.GOOGLEWORKSPACE_DELEGATED_USER, }; diff --git a/ui/lib/external-urls.ts b/ui/lib/external-urls.ts index 023b90df1e..7b4cdf4c7e 100644 --- a/ui/lib/external-urls.ts +++ b/ui/lib/external-urls.ts @@ -87,6 +87,11 @@ export const getProviderHelpText = (provider: string) => { text: "Need help connecting your OpenStack cloud?", link: "https://goto.prowler.com/provider-openstack", }; + case "googleworkspace": + return { + text: "Need help connecting your Google Workspace account?", + link: "https://goto.prowler.com/provider-googleworkspace", + }; default: return { text: "How to setup a provider?", diff --git a/ui/lib/provider-credentials/build-crendentials.ts b/ui/lib/provider-credentials/build-crendentials.ts index 9238a9d9a6..27f0babba1 100644 --- a/ui/lib/provider-credentials/build-crendentials.ts +++ b/ui/lib/provider-credentials/build-crendentials.ts @@ -264,6 +264,21 @@ export const buildOpenStackSecret = (formData: FormData) => { return filterEmptyValues(secret); }; +export const buildGoogleWorkspaceSecret = (formData: FormData) => { + const secret = { + [ProviderCredentialFields.GOOGLEWORKSPACE_CREDENTIALS_CONTENT]: + getFormValue( + formData, + ProviderCredentialFields.GOOGLEWORKSPACE_CREDENTIALS_CONTENT, + ), + [ProviderCredentialFields.GOOGLEWORKSPACE_DELEGATED_USER]: getFormValue( + formData, + ProviderCredentialFields.GOOGLEWORKSPACE_DELEGATED_USER, + ), + }; + return filterEmptyValues(secret); +}; + export const buildIacSecret = (formData: FormData) => { const secret = { [ProviderCredentialFields.REPOSITORY_URL]: getFormValue( @@ -480,6 +495,10 @@ export const buildSecretConfig = ( secretType: "static", secret: buildOpenStackSecret(formData), }), + googleworkspace: () => ({ + secretType: "static", + secret: buildGoogleWorkspaceSecret(formData), + }), }; const builder = secretBuilders[providerType]; diff --git a/ui/lib/provider-credentials/provider-credential-fields.ts b/ui/lib/provider-credentials/provider-credential-fields.ts index c5773233e1..f4f2c60c3a 100644 --- a/ui/lib/provider-credentials/provider-credential-fields.ts +++ b/ui/lib/provider-credentials/provider-credential-fields.ts @@ -83,6 +83,11 @@ export const ProviderCredentialFields = { // OpenStack fields OPENSTACK_CLOUDS_YAML_CONTENT: "clouds_yaml_content", OPENSTACK_CLOUDS_YAML_CLOUD: "clouds_yaml_cloud", + + // Google Workspace fields + GOOGLEWORKSPACE_CUSTOMER_ID: "customer_id", + GOOGLEWORKSPACE_CREDENTIALS_CONTENT: "credentials_content", + GOOGLEWORKSPACE_DELEGATED_USER: "delegated_user", } as const; // Type for credential field values @@ -137,6 +142,10 @@ export const ErrorPointers = { CLOUDFLARE_API_EMAIL: "/data/attributes/secret/api_email", OPENSTACK_CLOUDS_YAML_CONTENT: "/data/attributes/secret/clouds_yaml_content", OPENSTACK_CLOUDS_YAML_CLOUD: "/data/attributes/secret/clouds_yaml_cloud", + GOOGLEWORKSPACE_CUSTOMER_ID: "/data/attributes/secret/customer_id", + GOOGLEWORKSPACE_CREDENTIALS_CONTENT: + "/data/attributes/secret/credentials_content", + GOOGLEWORKSPACE_DELEGATED_USER: "/data/attributes/secret/delegated_user", } as const; export type ErrorPointer = (typeof ErrorPointers)[keyof typeof ErrorPointers]; diff --git a/ui/tests/providers/providers-page.ts b/ui/tests/providers/providers-page.ts index 55a0ed21ab..b8a5d28e91 100644 --- a/ui/tests/providers/providers-page.ts +++ b/ui/tests/providers/providers-page.ts @@ -59,6 +59,28 @@ export interface AlibabaCloudProviderData { alias?: string; } +// Google Workspace provider data +export interface GoogleWorkspaceProviderData { + customerId: string; + alias?: string; +} + +// Google Workspace credential options +export const GOOGLEWORKSPACE_CREDENTIAL_OPTIONS = { + GOOGLEWORKSPACE_SERVICE_ACCOUNT: "service_account", +} as const; + +// Google Workspace credential type +type GoogleWorkspaceCredentialType = + (typeof GOOGLEWORKSPACE_CREDENTIAL_OPTIONS)[keyof typeof GOOGLEWORKSPACE_CREDENTIAL_OPTIONS]; + +// Google Workspace provider credential +export interface GoogleWorkspaceProviderCredential { + type: GoogleWorkspaceCredentialType; + serviceAccountJson: string; + delegatedUser: string; +} + // AWS credential options export const AWS_CREDENTIAL_OPTIONS = { AWS_ROLE_ARN: "role", @@ -223,6 +245,12 @@ export class ProvidersPage extends BasePage { readonly githubProviderRadio: Locator; readonly ociProviderRadio: Locator; readonly alibabacloudProviderRadio: Locator; + readonly googleworkspaceProviderRadio: Locator; + + // Google Workspace provider form elements + readonly googleworkspaceCustomerIdInput: Locator; + readonly googleworkspaceServiceAccountJsonInput: Locator; + readonly googleworkspaceDelegatedUserInput: Locator; // AWS provider form elements readonly accountIdInput: Locator; @@ -449,6 +477,20 @@ export class ProvidersPage extends BasePage { name: /Connect assuming RAM Role/i, }); + // Google Workspace + this.googleworkspaceProviderRadio = page.getByRole("option", { + name: /Google Workspace/i, + }); + this.googleworkspaceCustomerIdInput = page.getByRole("textbox", { + name: "Customer ID", + }); + this.googleworkspaceServiceAccountJsonInput = page.getByRole("textbox", { + name: /Service Account JSON/i, + }); + this.googleworkspaceDelegatedUserInput = page.getByRole("textbox", { + name: /Delegated User Email/i, + }); + // Alias input this.aliasInput = page.getByRole("textbox", { name: "Provider alias (optional)", @@ -1208,6 +1250,41 @@ export class ProvidersPage extends BasePage { await expect(this.alibabacloudAccessKeySecretInput).toBeVisible(); } + async selectGoogleWorkspaceProvider(): Promise { + await this.selectProviderRadio(this.googleworkspaceProviderRadio); + } + + async fillGoogleWorkspaceProviderDetails( + data: GoogleWorkspaceProviderData, + ): Promise { + await this.googleworkspaceCustomerIdInput.fill(data.customerId); + + if (data.alias) { + await this.aliasInput.fill(data.alias); + } + } + + async fillGoogleWorkspaceCredentials( + credentials: GoogleWorkspaceProviderCredential, + ): Promise { + if (credentials.serviceAccountJson) { + await this.googleworkspaceServiceAccountJsonInput.fill( + credentials.serviceAccountJson, + ); + } + if (credentials.delegatedUser) { + await this.googleworkspaceDelegatedUserInput.fill( + credentials.delegatedUser, + ); + } + } + + async verifyGoogleWorkspaceCredentialsPageLoaded(): Promise { + await this.verifyPageHasProwlerTitle(); + await expect(this.googleworkspaceServiceAccountJsonInput).toBeVisible(); + await expect(this.googleworkspaceDelegatedUserInput).toBeVisible(); + } + async verifyPageLoaded(): Promise { // Verify the providers page is loaded @@ -1228,6 +1305,7 @@ export class ProvidersPage extends BasePage { await expect(this.kubernetesProviderRadio).toBeVisible(); await expect(this.githubProviderRadio).toBeVisible(); await expect(this.alibabacloudProviderRadio).toBeVisible(); + await expect(this.googleworkspaceProviderRadio).toBeVisible(); } async verifyCredentialsPageLoaded(): Promise { diff --git a/ui/tests/providers/providers.md b/ui/tests/providers/providers.md index 2f2bc410ca..1e0873d5a1 100644 --- a/ui/tests/providers/providers.md +++ b/ui/tests/providers/providers.md @@ -948,3 +948,67 @@ - Organization ID must follow AWS format (e.g., o-abc123def4) - Role ARN must belong to the StackSet deployment for Organizations flow - Provider cleanup is executed before test run to avoid unique constraint conflicts + +--- + +## Test Case: `PROVIDER-E2E-017` - Add Google Workspace Provider with Service Account Credentials + +**Priority:** `critical` + +**Tags:** + +- type → @e2e, @serial +- feature → @providers +- provider → @googleworkspace + +**Description/Objective:** Validates the complete flow of adding a new Google Workspace provider using Service Account authentication with Customer ID, Service Account JSON, and delegated user email. + +**Preconditions:** + +- Admin user authentication required (admin.auth.setup setup) +- Environment variables configured: E2E_GOOGLEWORKSPACE_CUSTOMER_ID, E2E_GOOGLEWORKSPACE_SERVICE_ACCOUNT_JSON, E2E_GOOGLEWORKSPACE_DELEGATED_USER +- Remove any existing provider with the same Customer ID before starting the test +- This test must be run serially and never in parallel with other tests, as it requires the Customer ID not to be already registered beforehand. + +### Flow Steps: + +1. Navigate to providers page +2. Click "Add Provider" button +3. Select Google Workspace provider type +4. Fill provider details (customer ID and alias) +5. Verify Google Workspace credentials page is loaded +6. Fill Google Workspace credentials (customer ID, service account JSON, delegated user email) +7. Launch initial scan +8. Verify redirect to Scans page +9. Verify scheduled scan status in Scans table (provider exists and scan name is "scheduled scan") + +### Expected Result: + +- Google Workspace provider successfully added with Service Account credentials +- Initial scan launched successfully +- User redirected to Scans page +- Scheduled scan appears in Scans table with correct provider and scan name + +### Key verification points: + +- Provider page loads correctly +- Connect account page displays Google Workspace option +- Provider details form accepts customer ID (format: C[0-9a-zA-Z]+) and alias +- Credentials page loads with customer ID, service account JSON textarea, and delegated user email fields +- Customer ID help text is visible with instructions on finding the Customer ID +- Service account JSON field accepts multi-line formatted JSON +- Delegated user email field validates email format +- Launch scan page appears +- Successful redirect to Scans page after scan launch +- Provider exists in Scans table (verified by customer ID) +- Scan name field contains "scheduled scan" + +### Notes: + +- Test uses environment variables for Google Workspace credentials +- Service Account JSON is provided as multi-line JSON string (not base64 encoded) +- Customer ID must start with 'C' followed by alphanumeric characters (e.g., C01234abc) +- Delegated user email must be a super admin in the Google Workspace domain +- Provider cleanup performed before each test to ensure clean state +- Requires valid Google Workspace account with Service Account having domain-wide delegation enabled +- Service Account must have appropriate Google Workspace API scopes for security scanning diff --git a/ui/tests/providers/providers.spec.ts b/ui/tests/providers/providers.spec.ts index fc9f802830..7d2a5dd2a0 100644 --- a/ui/tests/providers/providers.spec.ts +++ b/ui/tests/providers/providers.spec.ts @@ -27,6 +27,9 @@ import { AlibabaCloudProviderData, AlibabaCloudProviderCredential, ALIBABACLOUD_CREDENTIAL_OPTIONS, + GoogleWorkspaceProviderData, + GoogleWorkspaceProviderCredential, + GOOGLEWORKSPACE_CREDENTIAL_OPTIONS, } from "./providers-page"; import { ScansPage } from "../scans/scans-page"; import fs from "fs"; @@ -1348,6 +1351,90 @@ test.describe("Add Provider", () => { }, ); }); + + test.describe.serial("Add Google Workspace Provider", () => { + let providersPage: ProvidersPage; + let scansPage: ScansPage; + + const customerId = process.env.E2E_GOOGLEWORKSPACE_CUSTOMER_ID ?? ""; + const serviceAccountJson = + process.env.E2E_GOOGLEWORKSPACE_SERVICE_ACCOUNT_JSON ?? ""; + const delegatedUser = process.env.E2E_GOOGLEWORKSPACE_DELEGATED_USER ?? ""; + + test.beforeEach(async ({ page }) => { + test.skip( + !customerId || !serviceAccountJson || !delegatedUser, + "Google Workspace E2E env vars are not set", + ); + providersPage = new ProvidersPage(page); + await deleteProviderIfExists(providersPage, customerId!); + }); + + test.use({ storageState: "playwright/.auth/admin_user.json" }); + + test( + "should add a new Google Workspace provider with service account credentials", + { + tag: [ + "@critical", + "@e2e", + "@providers", + "@googleworkspace", + "@serial", + "@PROVIDER-E2E-017", + ], + }, + async ({ page }) => { + const googleWorkspaceProviderData: GoogleWorkspaceProviderData = { + customerId: customerId, + alias: "Test E2E Google Workspace - Service Account", + }; + + const googleWorkspaceCredentials: GoogleWorkspaceProviderCredential = { + type: GOOGLEWORKSPACE_CREDENTIAL_OPTIONS.GOOGLEWORKSPACE_SERVICE_ACCOUNT, + serviceAccountJson: serviceAccountJson, + delegatedUser: delegatedUser, + }; + + // Navigate to providers page + await providersPage.goto(); + await providersPage.verifyPageLoaded(); + + // Start adding new provider + await providersPage.clickAddProvider(); + await providersPage.verifyConnectAccountPageLoaded(); + + // Select Google Workspace provider + await providersPage.selectGoogleWorkspaceProvider(); + + // Fill provider details (customer ID and alias) + await providersPage.fillGoogleWorkspaceProviderDetails( + googleWorkspaceProviderData, + ); + await providersPage.clickNext(); + + // Verify credentials page is loaded + await providersPage.verifyGoogleWorkspaceCredentialsPageLoaded(); + + // Fill credentials + await providersPage.fillGoogleWorkspaceCredentials( + googleWorkspaceCredentials, + ); + await providersPage.clickNext(); + + // Launch scan + await providersPage.verifyLaunchScanPageLoaded(); + await providersPage.clickNext(); + + // Wait for redirect to scan page + scansPage = new ScansPage(page); + await scansPage.verifyPageLoaded(); + + // Verify scan status is "Scheduled scan" + await scansPage.verifyScheduledScanStatus(customerId); + }, + ); + }); }); test.describe("Update Provider Credentials", () => { diff --git a/ui/types/components.ts b/ui/types/components.ts index 96fbce5083..787f58c51c 100644 --- a/ui/types/components.ts +++ b/ui/types/components.ts @@ -364,6 +364,13 @@ export type OpenStackCredentials = { [ProviderCredentialFields.PROVIDER_ID]: string; }; +export type GoogleWorkspaceCredentials = { + [ProviderCredentialFields.GOOGLEWORKSPACE_CUSTOMER_ID]: string; + [ProviderCredentialFields.GOOGLEWORKSPACE_CREDENTIALS_CONTENT]: string; + [ProviderCredentialFields.GOOGLEWORKSPACE_DELEGATED_USER]: string; + [ProviderCredentialFields.PROVIDER_ID]: string; +}; + export type CredentialsFormSchema = | AWSCredentials | AWSCredentialsRole @@ -379,7 +386,8 @@ export type CredentialsFormSchema = | AlibabaCloudCredentials | AlibabaCloudCredentialsRole | CloudflareCredentials - | OpenStackCredentials; + | OpenStackCredentials + | GoogleWorkspaceCredentials; export interface SearchParamsProps { [key: string]: string | string[] | undefined; diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts index c7044c782c..7598757266 100644 --- a/ui/types/formSchemas.ts +++ b/ui/types/formSchemas.ts @@ -145,6 +145,17 @@ export const addProviderFormSchema = z [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(), providerUid: z.string(), }), + z.object({ + providerType: z.literal("googleworkspace"), + [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(), + providerUid: z + .string() + .trim() + .regex( + /^C[0-9a-zA-Z]+$/, + "Customer ID must start with 'C' followed by alphanumeric characters (e.g., C01234abc)", + ), + }), ]), ); @@ -316,21 +327,56 @@ export const addCredentialsFormSchema = ( }) .optional(), } - : providerType === "openstack" + : providerType === "googleworkspace" ? { - [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT]: + [ProviderCredentialFields.GOOGLEWORKSPACE_CUSTOMER_ID]: z .string() - .min( - 1, - "Clouds YAML content is required", + .trim() + .regex( + /^C[0-9a-zA-Z]+$/, + "Customer ID must start with 'C' followed by alphanumeric characters (e.g., C01234abc)", ), - [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD]: - z - .string() - .min(1, "Cloud name is required"), + [ProviderCredentialFields.GOOGLEWORKSPACE_CREDENTIALS_CONTENT]: + z.string().refine( + (val) => { + try { + const parsed = JSON.parse(val); + return ( + typeof parsed === "object" && + parsed !== null && + !Array.isArray(parsed) + ); + } catch { + return false; + } + }, + { + message: + "Invalid JSON format. Please provide a valid Service Account JSON.", + }, + ), + [ProviderCredentialFields.GOOGLEWORKSPACE_DELEGATED_USER]: + z.email({ + error: + "Please enter a valid email address", + }), } - : {}), + : providerType === "openstack" + ? { + [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT]: + z + .string() + .min( + 1, + "Clouds YAML content is required", + ), + [ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD]: + z + .string() + .min(1, "Cloud name is required"), + } + : {}), }) .superRefine((data: Record, ctx) => { if (providerType === "m365") { diff --git a/ui/types/providers.ts b/ui/types/providers.ts index 066bea0414..61a18722a2 100644 --- a/ui/types/providers.ts +++ b/ui/types/providers.ts @@ -6,6 +6,7 @@ export const PROVIDER_TYPES = [ "m365", "mongodbatlas", "github", + "googleworkspace", "iac", "image", "oraclecloud", @@ -24,6 +25,7 @@ export const PROVIDER_DISPLAY_NAMES: Record = { m365: "Microsoft 365", mongodbatlas: "MongoDB Atlas", github: "GitHub", + googleworkspace: "Google Workspace", iac: "Infrastructure as Code", image: "Container Registry", oraclecloud: "Oracle Cloud Infrastructure", From b7571abaebfe3e992883cff621c0d01cd9a26267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Tue, 17 Mar 2026 15:42:39 +0100 Subject: [PATCH 021/366] fix(dashboard): add RBI compliance dashboard support for Azure (#10360) --- .../rbi_cyber_security_framework_azure.py | 20 +++++++++++++++++++ prowler/CHANGELOG.md | 1 + 2 files changed, 21 insertions(+) create mode 100644 dashboard/compliance/rbi_cyber_security_framework_azure.py diff --git a/dashboard/compliance/rbi_cyber_security_framework_azure.py b/dashboard/compliance/rbi_cyber_security_framework_azure.py new file mode 100644 index 0000000000..cecafbce53 --- /dev/null +++ b/dashboard/compliance/rbi_cyber_security_framework_azure.py @@ -0,0 +1,20 @@ +import warnings + +from dashboard.common_methods import get_section_containers_rbi + +warnings.filterwarnings("ignore") + + +def get_table(data): + aux = data[ + [ + "REQUIREMENTS_ID", + "REQUIREMENTS_DESCRIPTION", + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ] + return get_section_containers_rbi(aux, "REQUIREMENTS_ID") diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 758cefe560..0a23b7b42a 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. ### 🐞 Fixed - Route53 dangling IP check false positive when using `--region` flag [(#9952)](https://github.com/prowler-cloud/prowler/pull/9952) +- RBI compliance framework support on Prowler Dashboard for the Azure provider [(#10360)](https://github.com/prowler-cloud/prowler/pull/10360) --- From cb03573599582308d3a3e6ca38ad7a789eca0dd0 Mon Sep 17 00:00:00 2001 From: tejas_0007 <102517877+tejas0077@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:55:30 -0400 Subject: [PATCH 022/366] feat(compliance): Add RBI Cyber Security Framework for GCP (#10339) Co-authored-by: Tejas Saubhage Co-authored-by: pedrooot --- .../rbi_cyber_security_framework_gcp.py | 20 ++ prowler/CHANGELOG.md | 3 +- .../gcp/rbi_cyber_security_framework_gcp.json | 178 ++++++++++++++++++ 3 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 dashboard/compliance/rbi_cyber_security_framework_gcp.py create mode 100644 prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json diff --git a/dashboard/compliance/rbi_cyber_security_framework_gcp.py b/dashboard/compliance/rbi_cyber_security_framework_gcp.py new file mode 100644 index 0000000000..cecafbce53 --- /dev/null +++ b/dashboard/compliance/rbi_cyber_security_framework_gcp.py @@ -0,0 +1,20 @@ +import warnings + +from dashboard.common_methods import get_section_containers_rbi + +warnings.filterwarnings("ignore") + + +def get_table(data): + aux = data[ + [ + "REQUIREMENTS_ID", + "REQUIREMENTS_DESCRIPTION", + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ] + return get_section_containers_rbi(aux, "REQUIREMENTS_ID") diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 0a23b7b42a..977e9c1a9d 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -8,8 +8,9 @@ All notable changes to the **Prowler SDK** are documented in this file. - `misconfig` scanner as default for Image provider scans [(#10167)](https://github.com/prowler-cloud/prowler/pull/10167) - `entra_conditional_access_policy_device_code_flow_blocked` check for M365 provider [(#10218)](https://github.com/prowler-cloud/prowler/pull/10218) +- RBI compliance for the Azure provider [(#10339)](https://github.com/prowler-cloud/prowler/pull/10339) +-`entra_conditional_access_policy_require_mfa_for_admin_portals` check for Azure provider and update CIS compliance [(#10330)](https://github.com/prowler-cloud/prowler/pull/10330) - CheckMetadata Pydantic validators [(#8584)](https://github.com/prowler-cloud/prowler/pull/8583) -- `entra_conditional_access_policy_require_mfa_for_admin_portals` check for Azure provider and update CIS compliance [(#10330)](https://github.com/prowler-cloud/prowler/pull/10330) - `organization_repository_deletion_limited` check for GitHub provider [(#10185)](https://github.com/prowler-cloud/prowler/pull/10185) ### 🔄 Changed diff --git a/prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json b/prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json new file mode 100644 index 0000000000..e86aee6e63 --- /dev/null +++ b/prowler/compliance/gcp/rbi_cyber_security_framework_gcp.json @@ -0,0 +1,178 @@ +{ + "Framework": "RBI-Cyber-Security-Framework", + "Name": "Reserve Bank of India (RBI) Cyber Security Framework", + "Version": "", + "Provider": "GCP", + "Description": "The Reserve Bank had prescribed a set of baseline cyber security controls for primary (Urban) cooperative banks (UCBs) in October 2018. On further examination, it has been decided to prescribe a comprehensive cyber security framework for the UCBs, as a graded approach, based on their digital depth and interconnectedness with the payment systems landscape, digital products offered by them and assessment of cyber security risk. The framework would mandate implementation of progressively stronger security measures based on the nature, variety and scale of digital product offerings of banks.", + "Requirements": [ + { + "Id": "annex_i_1_1", + "Name": "Annex I (1.1)", + "Description": "UCBs should maintain an up-to-date business IT Asset Inventory Register containing the following fields, as a minimum: a) Details of the IT Asset (viz., hardware/software/network devices, key personnel, services, etc.), b. Details of systems where customer data are stored, c. Associated business applications, if any, d. Criticality of the IT asset (For example, High/Medium/Low).", + "Attributes": [ + { + "ItemId": "annex_i_1_1", + "Service": "gcp" + } + ], + "Checks": [ + "iam_cloud_asset_inventory_enabled", + "iam_organization_essential_contacts_configured" + ] + }, + { + "Id": "annex_i_1_3", + "Name": "Annex I (1.3)", + "Description": "Appropriately manage and provide protection within and outside UCB/network, keeping in mind how the data/information is stored, transmitted, processed, accessed and put to use within/outside the UCB's network, and level of risk they are exposed to depending on the sensitivity of the data/information.", + "Attributes": [ + { + "ItemId": "annex_i_1_3", + "Service": "gcp" + } + ], + "Checks": [ + "cloudstorage_bucket_public_access", + "cloudstorage_bucket_uniform_bucket_level_access", + "cloudstorage_bucket_logging_enabled", + "cloudstorage_bucket_versioning_enabled", + "cloudsql_instance_public_access", + "cloudsql_instance_public_ip", + "cloudsql_instance_ssl_connections", + "compute_instance_public_ip", + "compute_instance_encryption_with_csek_enabled", + "compute_image_not_publicly_shared", + "kms_key_rotation_enabled", + "kms_key_not_publicly_accessible", + "bigquery_dataset_public_access", + "bigquery_dataset_cmk_encryption" + ] + }, + { + "Id": "annex_i_5_1", + "Name": "Annex I (5.1)", + "Description": "The firewall configurations should be set to the highest security level and evaluation of critical device (such as firewall, network switches, security devices, etc.) configurations should be done periodically.", + "Attributes": [ + { + "ItemId": "annex_i_5_1", + "Service": "compute" + } + ], + "Checks": [ + "compute_firewall_rdp_access_from_the_internet_allowed", + "compute_firewall_ssh_access_from_the_internet_allowed", + "compute_network_not_legacy", + "compute_network_default_in_use", + "compute_subnet_flow_logs_enabled", + "compute_network_dns_logging_enabled", + "dns_dnssec_disabled" + ] + }, + { + "Id": "annex_i_6", + "Name": "Annex I (6)", + "Description": "Put in place systems and processes to identify, track, manage and monitor the status of patches to servers, operating system and application software running at the systems used by the UCB officials (end-users). Implement and update antivirus protection for all servers and applicable end points preferably through a centralised system.", + "Attributes": [ + { + "ItemId": "annex_i_6", + "Service": "gcp" + } + ], + "Checks": [ + "compute_instance_shielded_vm_enabled", + "compute_project_os_login_enabled", + "artifacts_container_analysis_enabled", + "gcr_container_scanning_enabled" + ] + }, + { + "Id": "annex_i_7_1", + "Name": "Annex I (7.1)", + "Description": "Disallow administrative rights on end-user workstations/PCs/laptops and provide access rights on a 'need to know' and 'need to do' basis.", + "Attributes": [ + { + "ItemId": "annex_i_7_1", + "Service": "iam" + } + ], + "Checks": [ + "iam_sa_no_administrative_privileges", + "iam_no_service_roles_at_project_level", + "iam_role_sa_enforce_separation_of_duties", + "iam_role_kms_enforce_separation_of_duties", + "iam_sa_no_user_managed_keys", + "compute_instance_default_service_account_in_use", + "compute_instance_default_service_account_in_use_with_full_api_access" + ] + }, + { + "Id": "annex_i_7_2", + "Name": "Annex I (7.2)", + "Description": "Passwords should be set as complex and lengthy and users should not use same passwords for all the applications/systems/devices.", + "Attributes": [ + { + "ItemId": "annex_i_7_2", + "Service": "iam" + } + ], + "Checks": [ + "compute_project_os_login_2fa_enabled", + "iam_account_access_approval_enabled" + ] + }, + { + "Id": "annex_i_7_3", + "Name": "Annex I (7.3)", + "Description": "Remote Desktop Protocol (RDP) which allows others to access the computer remotely over a network or over the internet should be always disabled and should be enabled only with the approval of the authorised officer of the UCB. Logs for such remote access shall be enabled and monitored for suspicious activities.", + "Attributes": [ + { + "ItemId": "annex_i_7_3", + "Service": "compute" + } + ], + "Checks": [ + "compute_firewall_rdp_access_from_the_internet_allowed", + "compute_firewall_ssh_access_from_the_internet_allowed", + "compute_project_os_login_enabled" + ] + }, + { + "Id": "annex_i_7_4", + "Name": "Annex I (7.4)", + "Description": "Implement appropriate (e.g. centralised) systems and controls to allow, manage, log and monitor privileged/super user/administrative access to critical systems (servers/databases, applications, network devices etc.)", + "Attributes": [ + { + "ItemId": "annex_i_7_4", + "Service": "logging" + } + ], + "Checks": [ + "iam_audit_logs_enabled", + "logging_sink_created", + "cloudstorage_audit_logs_enabled", + "compute_loadbalancer_logging_enabled", + "compute_subnet_flow_logs_enabled", + "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled", + "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", + "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled" + ] + }, + { + "Id": "annex_i_12", + "Name": "Annex I (12)", + "Description": "Take periodic back up of the important data and store this data 'off line' (i.e., transferring important files to a storage device that can be detached from a computer/system after copying all the files).", + "Attributes": [ + { + "ItemId": "annex_i_12", + "Service": "gcp" + } + ], + "Checks": [ + "cloudsql_instance_automated_backups", + "cloudstorage_bucket_versioning_enabled", + "cloudstorage_bucket_soft_delete_enabled", + "cloudstorage_bucket_lifecycle_management_enabled", + "compute_snapshot_not_outdated" + ] + } + ] +} From 8c2e2332d7c5354f81a31c86cb454b291926d8a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 18 Mar 2026 08:28:40 +0100 Subject: [PATCH 023/366] feat(compliance): add SecNumCloud 3.2 for Azure (#10358) --- dashboard/compliance/secnumcloud_3_2_azure.py | 24 + prowler/CHANGELOG.md | 1 + .../azure/secnumcloud_3.2_azure.json | 1504 +++++++++++++++++ 3 files changed, 1529 insertions(+) create mode 100644 dashboard/compliance/secnumcloud_3_2_azure.py create mode 100644 prowler/compliance/azure/secnumcloud_3.2_azure.json diff --git a/dashboard/compliance/secnumcloud_3_2_azure.py b/dashboard/compliance/secnumcloud_3_2_azure.py new file mode 100644 index 0000000000..2d5517aed6 --- /dev/null +++ b/dashboard/compliance/secnumcloud_3_2_azure.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_format3 + +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_format3( + aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID" + ) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 977e9c1a9d..67a4f27197 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to the **Prowler SDK** are documented in this file. -`entra_conditional_access_policy_require_mfa_for_admin_portals` check for Azure provider and update CIS compliance [(#10330)](https://github.com/prowler-cloud/prowler/pull/10330) - CheckMetadata Pydantic validators [(#8584)](https://github.com/prowler-cloud/prowler/pull/8583) - `organization_repository_deletion_limited` check for GitHub provider [(#10185)](https://github.com/prowler-cloud/prowler/pull/10185) +- SecNumCloud 3.2 for the Azure provider [(#10358)](https://github.com/prowler-cloud/prowler/pull/10358) ### 🔄 Changed diff --git a/prowler/compliance/azure/secnumcloud_3.2_azure.json b/prowler/compliance/azure/secnumcloud_3.2_azure.json new file mode 100644 index 0000000000..ef8c94113e --- /dev/null +++ b/prowler/compliance/azure/secnumcloud_3.2_azure.json @@ -0,0 +1,1504 @@ +{ + "Framework": "SecNumCloud", + "Name": "SecNumCloud Referentiel d'Exigences v3.2", + "Version": "3.2", + "Provider": "Azure", + "Description": "The SecNumCloud framework is published by ANSSI (Agence Nationale de la Securite des Systemes d'Information) to qualify cloud service providers operating in France. Version 3.2, dated March 8, 2022, covers IaaS, CaaS, PaaS, and SaaS services with requirements spanning information security policies, access control, cryptography, physical security, operational security, communications security, and data sovereignty protections against extra-European law.", + "Requirements": [ + { + "Id": "5.1", + "Description": "Le prestataire doit definir et appliquer des principes de securite de l'information adaptes a ses activites de fourniture de services cloud.", + "Name": "Principes", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit operer la prestation a l'etat de l'art pour le type d'activite retenu : utiliser des logiciels stables beneficiant d'un suivi des correctifs de securite et parametres de facon a obtenir un niveau de securite optimal. b) Le prestataire doit appliquer le guide d'hygiene informatique de l'ANSSI [HYGIENE], niveau renforce, au systeme d'information du service." + } + ], + "Checks": [] + }, + { + "Id": "5.2", + "Description": "Le prestataire doit definir, faire approuver par la direction, publier et communiquer aux salaries et aux tiers concernes un ensemble de politiques de securite de l'information.", + "Name": "Politique de securite de l'information", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de securite de l'information relative au service. b) La politique de securite de l'information doit identifier les engagements du prestataire quant au respect de la legislation et reglementation nationale en vigueur selon la nature des informations qui pourraient etre confiees par le commanditaire au prestataire ; il revient en revanche in fine au commanditaire de s'assurer du respect des contraintes legales et reglementaires applicables aux donnees qu'il confie effectivement au prestataire. c) La politique de securite de l'information doit notamment couvrir les themes abordes aux chapitres 6 a 19 du present referentiel. d) La direction du prestataire doit approuver formellement la politique de securite de l'information. e) Le prestataire doit reviser annuellement la politique de securite de l'information et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "5.3", + "Description": "Le prestataire doit definir et appliquer un processus d'appreciation des risques de securite de l'information.", + "Name": "Appreciation des risques", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter une appreciation des risques couvrant l'ensemble du perimetre du service. b) Le prestataire doit realiser son appreciation de risques en utilisant une methode documentee garantissant la reproductibilite et comparabilite de la demarche. c) Le prestataire doit prendre en compte dans l'appreciation des risques : la gestion d'informations du commanditaire ayant des besoins de securite differents ; les risques ayant des impacts sur les droits et libertes des personnes concernees en cas d'acces non autorise, de modification non desiree et de disparition de donnees a caractere personnel ; les risques de defaillance des mecanismes de cloisonnement des ressources de l'infrastructure technique (memoire, calcul, stockage, reseau) partagees entre les commanditaires ; les risques lies a l'effacement incomplet ou non securise des donnees stockees sur les espaces de memoire ou de stockage partages entre commanditaires, en particulier lors des reallocations des espaces de memoire et de stockage ; les risques lies a l'exposition des interfaces d'administration sur un reseau public ; les risques d'atteinte a la confidentialite des donnees des commanditaires par des tiers impliques dans la fourniture du service (fournisseurs, sous-traitants, etc.) ; les risques lies aux evenements naturels et sinistres physiques ; les risques lies a la separation des taches (voir 6.2.a) ; les risques lies aux environnements de developpement (voir 14.4.b). d) Le prestataire doit lister, dans un document specifique, les risques residuels lies a l'existence de lois extra-europeennes ayant pour objectif la collecte de donnees ou metadonnees des commanditaires sans leur consentement prealable. e) Le prestataire doit mettre a la disposition du commanditaire, sur demande de celui-ci, les elements d'appreciation des risques lies a la soumission des donnees du commanditaire au droit d'un etat non-membre de l'Union Europeenne. f) Lorsqu'il existe des exigences legales, reglementaires ou sectorielles specifiques liees aux types d'informations confiees par le commanditaire au prestataire, ce dernier doit les prendre en compte dans son appreciation des risques en s'assurant de respecter l'ensemble des exigences du present referentiel d'une part et de ne pas abaisser le niveau de securite etabli par le respect des exigences du present referentiel d'autre part. g) La direction du prestataire doit accepter formellement les risques residuels identifies dans l'appreciation des risques. h) Le prestataire doit reviser annuellement l'appreciation des risques et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "6.1", + "Description": "Le prestataire doit definir et attribuer toutes les responsabilites en matiere de securite de l'information.", + "Name": "Fonctions et responsabilites liees a la securite de l'information", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une organisation interne de la securite pour assurer la definition, la mise en place et le suivi du fonctionnement operationnel de la securite de l'information au sein de son organisation. b) Le prestataire doit designer un responsable de la securite des systemes d'information et un responsable de la securite physique. c) Le prestataire doit definir et attribuer les responsabilites en matiere de securite de l'information pour le personnel implique dans la fourniture du service. d) Le prestataire doit s'assurer apres tout changement majeur pouvant avoir un impact sur le service que l'attribution des responsabilites en matiere de securite de l'information est toujours pertinente. e) Le prestataire doit definir et attribuer les responsabilites en matiere de protection de donnees a caractere personnel, en coherence avec son role dans les traitements de donnees a caractere personnel (responsable de traitement, sous-traitant ou co-responsable). f) Le prestataire doit, lorsqu'il traite un grand nombre de donnees parmi lesquelles figurent des categories particulieres de donnees a caractere personnel telles que definies dans [RGPD], designer un delegue a la protection des donnees. g) Il est recommande que le prestataire, quel que soit le volume de donnees a caractere personnel qu'il traite, designe un delegue a la protection des donnees. h) Le prestataire doit realiser ou contribuer a la realisation d'une analyse d'impact relative a la protection des donnees a caractere personnel lorsque le traitement est susceptible d'engendrer un risque eleve pour les droits et libertes des personnes concernees (traitement de categories particulieres de donnees a caractere personnel telles que definies dans [RGPD], traitement de donnees a grande echelle, etc.). Cette analyse doit comporter une evaluation juridique du respect des principes et droits fondamentaux, ainsi qu'une etude plus technique des mesures techniques mises en oeuvre pour proteger les personnes des risques pour leur vie privee." + } + ], + "Checks": [] + }, + { + "Id": "6.2", + "Description": "Le prestataire doit separer les taches et les domaines de responsabilite incompatibles afin de reduire les possibilites de modification non autorisee ou de mauvais usage des actifs.", + "Name": "Separation des taches", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les risques associes a des cumuls de responsabilites ou de taches, les prendre en compte dans l'appreciation des risques et mettre en oeuvre des mesures de reduction de ces risques." + } + ], + "Checks": [] + }, + { + "Id": "6.3", + "Description": "Le prestataire doit etablir et maintenir des relations appropriees avec les autorites competentes.", + "Name": "Relations avec les autorites", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire mette en place des relations appropriees avec les autorites competentes en matiere de securite de l'information et de donnees a caractere personnel et, le cas echeant, avec les autorites sectorielles selon la nature des informations confiees par le commanditaire au prestataire." + } + ], + "Checks": [] + }, + { + "Id": "6.4", + "Description": "Le prestataire doit etablir et maintenir des relations appropriees avec des groupes de travail specialises, des associations professionnelles ou des forums traitant de la securite.", + "Name": "Relations avec les groupes de travail specialises", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire entretienne des contacts appropries avec des groupes de specialistes ou des sources reconnues, notamment pour prendre en compte de nouvelles menaces et les mesures de securite appropriees pour les contrer." + } + ], + "Checks": [] + }, + { + "Id": "6.5", + "Description": "Le prestataire doit integrer la securite de l'information dans la gestion de projet, quel que soit le type de projet.", + "Name": "La securite de l'information dans la gestion de projet", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter une estimation des risques prealablement a tout projet pouvant avoir un impact sur le service, et ce quelle que soit la nature du projet. b) Dans la mesure ou un projet affecte ou est susceptible d'affecter le niveau de securite du service, le prestataire doit avertir le commanditaire et l'informer par ecrit des impacts potentiels, des mesures mises en place pour reduire ces impacts ainsi que des risques residuels le concernant." + } + ], + "Checks": [] + }, + { + "Id": "7.1", + "Description": "Le prestataire doit s'assurer que les candidats a l'embauche font l'objet de verifications proportionnees aux exigences metier, a la classification des informations accessibles et aux risques identifies.", + "Name": "Selection des candidats", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de verification des informations concernant son personnel conforme aux lois et reglements en vigueur. Ces verifications s'appliquent a toute personne impliquee dans la fourniture du service et doivent etre proportionnelles a la sensibilite ou a la specificite des informations du commanditaire confiees au prestataire ainsi qu'aux risques identifies. b) Pour les personnels disposant de privileges d'administration eleves sur les composants logiciels et materiels de l'infrastructure, le prestataire doit renforcer les verifications destinees a verifier que les antecedents de ceux-ci ne sont pas incompatibles avec l'exercice de leurs fonctions. Il est entendu par des privileges d'administration eleves, des actions permettant l'elevation de privileges ou la possibilite de realiser des actions sans traces techniques ou de desactiver, alterer les traces techniques." + } + ], + "Checks": [] + }, + { + "Id": "7.2", + "Description": "Les accords contractuels avec les salaries et les sous-traitants doivent preciser leurs responsabilites et celles du prestataire en matiere de securite de l'information.", + "Name": "Conditions d'embauche", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit disposer d'une charte d'ethique integree au reglement interieur, prevoyant notamment que : les prestations sont realisees avec loyaute, discretion et impartialite et dans des conditions de confidentialite des informations traitees ; les personnels ne recourent qu'aux methodes, outils et techniques valides par le prestataire ; les personnels s'engagent a ne pas divulguer d'informations a un tiers, meme anonymisees et decontextualisees, obtenues ou generees dans le cadre de la prestation sauf autorisation formelle et ecrite du commanditaire ; les personnels s'engagent a signaler au prestataire tout contenu manifestement illicite decouvert pendant la prestation ; les personnels s'engagent a respecter la legislation et la reglementation nationale en vigueur et les bonnes pratiques liees a leurs activites. b) Le prestataire doit faire signer la charte d'ethique a l'ensemble des personnes impliquees dans la fourniture du service. c) Le prestataire doit introduire, dans le contrat de travail des personnels disposant de privileges d'administration eleves sur les composants et materiels de l'infrastructure du service, un engagement de responsabilite avec un renvoi aux clauses du code du travail sur la protection du secret des affaires et de la propriete intellectuelle. Il est entendu par des privileges d'administration eleves, des actions permettant l'elevation de privileges ou la possibilite de realiser des actions sans traces techniques ou de desactiver, alterer les traces techniques. d) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible le reglement interieur et la charte d'ethique." + } + ], + "Checks": [] + }, + { + "Id": "7.3", + "Description": "Les salaries du prestataire et, le cas echeant, les sous-traitants doivent suivre un programme de sensibilisation et de formation adapte et regulier concernant la securite de l'information.", + "Name": "Sensibilisation, apprentissage et formations a la securite de l'information", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit sensibiliser a la securite de l'information et aux risques lies a la protection des donnees l'ensemble des personnes impliquees dans la fourniture du service. Il doit leur communiquer les mises a jour des politiques et procedures pertinentes dans le cadre de leurs missions. b) Le prestataire doit documenter et mettre en oeuvre un plan de formation concernant la securite de l'information adapte au service et aux missions des personnels. c) Le responsable de la securite des systemes d'information du prestataire doit valider formellement le plan de formation concernant la securite de l'information." + } + ], + "Checks": [] + }, + { + "Id": "7.4", + "Description": "Le prestataire doit mettre en place un processus disciplinaire formel et communique pour prendre des mesures a l'encontre des salaries ayant enfreint les regles de securite de l'information.", + "Name": "Processus disciplinaire", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus disciplinaire applicable a l'ensemble des personnes impliquees dans la fourniture du service ayant enfreint la politique de securite. b) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible les sanctions encourues en cas d'infraction a la politique de securite." + } + ], + "Checks": [] + }, + { + "Id": "7.5", + "Description": "Les responsabilites et les obligations en matiere de securite de l'information qui restent valables apres un changement ou une rupture du contrat de travail doivent etre definies, communiquees au salarie ou au sous-traitant et appliquees.", + "Name": "Rupture, terme ou modification du contrat de travail", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit definir et attribuer les roles et les responsabilites relatives a la rupture, au terme ou a la modification de tout contrat avec une personne impliquee dans la fourniture du service." + } + ], + "Checks": [] + }, + { + "Id": "8.1", + "Description": "Le prestataire doit identifier les actifs associes a l'information et aux moyens de traitement de l'information et doit etablir et tenir a jour un inventaire de ces actifs.", + "Name": "Inventaire et propriete des actifs", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "config", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit tenir a jour l'inventaire de l'ensemble des equipements mettant en oeuvre le service. Cet inventaire doit preciser pour chaque equipement : les informations d'identification de l'equipement (noms, adresses IP, adresses MAC, etc.) ; la fonction de l'equipement ; le modele de l'equipement ; la localisation de l'equipement ; le proprietaire de l'equipement ; le besoin de securite des informations (au sens du chapitre 8.3). b) Le prestataire doit tenir a jour l'inventaire de l'ensemble des logiciels mettant en oeuvre le service. Cet inventaire doit identifier pour chaque logiciel, sa version et les equipements sur lesquels le logiciel est installe. c) Le prestataire doit s'assurer de la validite des licences des logiciels tout au long de la prestation." + } + ], + "Checks": [ + "policy_ensure_asc_enforcement_enabled" + ] + }, + { + "Id": "8.2", + "Description": "Les salaries et les utilisateurs de tiers doivent restituer tous les actifs du prestataire en leur possession au terme de la periode d'emploi, du contrat ou de l'accord.", + "Name": "Restitution des actifs", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de restitution des actifs permettant de s'assurer que chaque personne impliquee dans la fourniture du service restitue l'ensemble des actifs en sa possession a la fin de sa periode d'emploi ou de son contrat." + } + ], + "Checks": [] + }, + { + "Id": "8.3", + "Description": "Les besoins de protection de la confidentialite, de l'integrite et de la disponibilite de l'information doivent etre identifies.", + "Name": "Identification des besoins de securite de l'information", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les differents besoins de securite des informations relatives au service. b) Lorsque le commanditaire confie au prestataire des donnees soumises a des contraintes legales, reglementaires ou sectorielles specifiques, le prestataire doit identifier les besoins de securite specifiques associes a ces contraintes." + } + ], + "Checks": [] + }, + { + "Id": "8.4", + "Description": "Un ensemble de procedures appropriees pour le marquage et la manipulation de l'information doit etre elabore et mis en oeuvre conformement au plan de classification adopte par le prestataire.", + "Name": "Marquage et manipulation de l'information", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire documente et mette en oeuvre une procedure pour le marquage et la manipulation de toutes les informations participant a la delivrance du service, conformement a son besoin de securite defini au chapitre 8.3." + } + ], + "Checks": [] + }, + { + "Id": "8.5", + "Description": "Des procedures de gestion des supports amovibles doivent etre mises en oeuvre conformement au plan de classification adopte par le prestataire.", + "Name": "Gestion des supports amovibles", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure pour la gestion des supports amovibles, conformement au besoin de securite defini au chapitre 8.3. Lorsque des supports amovibles sont utilises sur l'infrastructure technique ou pour des taches d'administration, ces supports doivent etre dedies a un usage." + } + ], + "Checks": [] + }, + { + "Id": "9.1", + "Description": "Une politique de controle d'acces doit etre etablie, documentee et revue en se basant sur les exigences metier et les exigences de securite de l'information. Les regles de controle d'acces et les droits pour chaque utilisateur ou groupe d'utilisateurs doivent etre clairement definis.", + "Name": "Politiques et controle d'acces", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de controle d'acces sur la base du resultat de son appreciation des risques et du partage des responsabilites. b) Le prestataire doit reviser annuellement la politique de controle d'acces et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [ + "iam_subscription_roles_owner_custom_not_created", + "iam_custom_role_has_permissions_to_administer_resource_locks", + "iam_role_user_access_admin_restricted", + "entra_policy_ensure_default_user_cannot_create_apps", + "entra_policy_ensure_default_user_cannot_create_tenants" + ] + }, + { + "Id": "9.2", + "Description": "Un processus formel d'enregistrement et de desinscription des utilisateurs doit etre mis en oeuvre pour permettre l'attribution des droits d'acces.", + "Name": "Enregistrement et desinscription des utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure d'enregistrement et de desinscription des utilisateurs s'appuyant sur une interface de gestion des comptes et des droits d'acces. Cette procedure doit indiquer quelles donnees doivent etre supprimees au depart d'un utilisateur. b) Le prestataire doit attribuer des comptes nominatifs lors de l'enregistrement des utilisateurs places sous sa responsabilite. c) Le prestataire doit mettre en oeuvre des moyens permettant de s'assurer que la desinscription d'un utilisateur entraine la suppression de tous ses acces aux ressources du systeme d'information du service, ainsi que la suppression de ses donnees conformement a la procedure d'enregistrement et de desinscription (voir exigence 9.2 a))." + } + ], + "Checks": [] + }, + { + "Id": "9.3", + "Description": "Un processus formel de gestion des droits d'acces doit etre mis en oeuvre pour controler l'attribution des droits d'acces a tous les types d'utilisateurs et a tous les systemes et services.", + "Name": "Gestion des droits d'acces", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'assurer l'attribution, la modification et le retrait de droits d'acces aux ressources du systeme d'information du service. b) Le prestataire doit mettre a la disposition de ses commanditaires les outils et les moyens qui permettent une differenciation des roles des utilisateurs du service, par exemple suivant leur role fonctionnel. c) Le prestataire doit tenir a jour l'inventaire des utilisateurs sous sa responsabilite disposant de droits d'administration sur les ressources du systeme d'information du service. d) Le prestataire doit etre en mesure de fournir, pour une ressource donnee mettant en oeuvre le service, la liste de tous les utilisateurs y ayant acces, qu'ils soient sous la responsabilite du prestataire ou du commanditaire ainsi que les droits d'acces qui leurs ont ete attribues. e) Le prestataire doit etre en mesure de fournir, pour un utilisateur donne, qu'ils soient sous la responsabilite du prestataire ou du commanditaire, la liste de tous ses droits d'acces sur les differents elements du systeme d'information du service. f) Le prestataire doit definir une liste de droits d'acces incompatibles entre eux. Il doit s'assurer, lors de l'attribution de droits d'acces a un utilisateur qu'il ne possede pas de droits d'acces incompatibles entre eux au titre de la liste precedemment etablie. g) Le prestataire doit inclure dans la procedure de gestion des droits d'acces les actions de revocation ou de suspension des droits de tout utilisateur." + } + ], + "Checks": [ + "iam_subscription_roles_owner_custom_not_created", + "iam_custom_role_has_permissions_to_administer_resource_locks", + "iam_role_user_access_admin_restricted", + "entra_global_admin_in_less_than_five_users" + ] + }, + { + "Id": "9.4", + "Description": "Les proprietaires d'actifs doivent verifier les droits d'acces des utilisateurs a intervalles reguliers.", + "Name": "Revue des droits d'acces utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit reviser annuellement les droits d'acces des utilisateurs sur son perimetre de responsabilite. b) Le prestataire doit mettre a disposition du commanditaire un outil facilitant la revue des droits d'acces des utilisateurs places sous la responsabilite de ce dernier. c) Le prestataire doit reviser trimestriellement la liste des utilisateurs sur son perimetre de responsabilite pouvant utiliser les comptes techniques mentionnes dans l'exigence 9.2 b)." + } + ], + "Checks": [] + }, + { + "Id": "9.5", + "Description": "L'attribution et l'utilisation des informations secretes d'authentification doivent etre gerees dans le cadre d'un processus de gestion formel incluant une politique de mot de passe robuste et l'utilisation de l'authentification multi-facteur.", + "Name": "Gestion des authentifications des utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit formaliser et mettre en oeuvre des procedures de gestion de l'authentification des utilisateurs. En accord avec les exigences du chapitre 10, celles-ci doivent notamment porter sur : la gestion des moyens d'authentification (emission et reinitialisation de mot de passe, mise a jour des CRL et import des certificats racines en cas d'utilisation de certificats, etc.) ; la mise en place des moyens permettant une authentification a multiples facteurs afin de repondre aux differents cas d'usage du referentiel ; les systemes qui generent des mots de passe ou verifient leur robustesse, lorsqu'une authentification par mot de passe est utilisee. Ils doivent suivre les recommandations de [G_AUTH]. b) Tout mecanisme d'authentification doit prevoir le blocage d'un compte apres un nombre limite de tentatives infructueuses. c) Dans le cadre d'un service SaaS, le prestataire doit proposer a ses commanditaires des moyens d'authentification a multiples facteurs pour l'acces des utilisateurs finaux. d) Lorsque des comptes techniques, non nominatifs, sont necessaires, le prestataire doit mettre en place des mesures obligeant les utilisateurs a s'authentifier avec leur compte nominatif avant de pouvoir acceder a ces comptes techniques." + } + ], + "Checks": [ + "entra_non_privileged_user_has_mfa", + "entra_privileged_user_has_mfa", + "entra_security_defaults_enabled" + ] + }, + { + "Id": "9.6", + "Description": "L'acces aux interfaces d'administration du service cloud doit etre restreint et protege par des mecanismes d'authentification forte, incluant l'utilisation de dispositifs MFA materiels pour les comptes a privileges.", + "Name": "Acces aux interfaces d'administration", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Les comptes d'administration sous la responsabilite du prestataire doivent etre geres a l'aide d'outils et d'annuaires distincts de ceux utilises pour la gestion des comptes utilisateurs places sous la responsabilite du commanditaire. b) Les interfaces d'administration mises a disposition des commanditaires doivent etre distinctes des interfaces d'administration utilisees par le prestataire. c) Les interfaces d'administration mises a disposition des commanditaires ne doivent permettre aucune connexion avec des comptes d'administrateurs sous la responsabilite du prestataire. d) Les interfaces d'administration utilisees par le prestataire ne doivent pas etre accessibles a partir d'un reseau public et ainsi ne doivent permettre aucune connexion des utilisateurs sous la responsabilite du commanditaire. e) Si des interfaces d'administration sont mises a disposition des commanditaires avec un acces via un reseau public, les flux d'administration doivent etre authentifies et chiffres avec des moyens en accord avec les exigences du chapitre 10.2. f) Le prestataire doit mettre en place un systeme d'authentification multifacteur fort pour l'acces : aux interfaces d'administration utilisees par le prestataire ; aux interfaces d'administration dediees aux commanditaires. g) Dans le cadre d'un service SaaS, les interfaces d'administration mises a disposition des commanditaires doivent etre differenciees des interfaces permettant l'acces des utilisateurs finaux. h) Des lors qu'une interface d'administration est accessible depuis un reseau public, le processus d'authentification doit avoir lieu avant toute interaction entre l'utilisateur et l'interface en question. i) Lorsque le prestataire utilise un service de type IaaS comme socle d'un autre type de service (CaaS, PaaS ou SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service IaaS. j) Lorsque le prestataire utilise un service de type CaaS comme socle d'un autre type de service (PaaS ou SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service CaaS. k) Lorsque le prestataire utilise un service de type PaaS comme socle d'un autre type de service (typiquement SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service PaaS." + } + ], + "Checks": [ + "entra_privileged_user_has_mfa", + "entra_user_with_vm_access_has_mfa", + "entra_conditional_access_policy_require_mfa_for_management_api", + "entra_conditional_access_policy_require_mfa_for_admin_portals", + "entra_global_admin_in_less_than_five_users", + "network_rdp_internet_access_restricted", + "network_ssh_internet_access_restricted" + ] + }, + { + "Id": "9.7", + "Description": "L'acces a l'information et aux fonctions d'application des systemes doit etre restreint conformement a la politique de controle d'acces. Les ressources doivent etre protegees contre tout acces public non autorise.", + "Name": "Restriction des acces a l'information", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "ec2", + "Type": "Automated", + "Comment": "a) Le prestataire doit mettre en oeuvre des mesures de cloisonnement appropriees entre ses commanditaires. b) Le prestataire doit mettre en oeuvre des mesures de cloisonnement appropriees entre le systeme d'information du service et ses autres systemes d'information (bureautique, informatique de gestion, gestion technique du batiment, controle d'acces physique, etc.). c) Le prestataire doit concevoir, developper, configurer et deployer le systeme d'information du service en assurant au moins un cloisonnement entre d'une part l'infrastructure technique et d'autre part les equipements necessaires a l'administration des services et des ressources qu'elle heberge. d) Dans le cadre du support technique, si les actions necessaires au diagnostic et a la resolution d'un probleme rencontre par un commanditaire necessitent un acces aux donnees du commanditaire, alors le prestataire doit : n'autoriser l'acces aux donnees du commanditaire qu'apres consentement explicite du commanditaire ; verifier que la personne a qui l'acces doit etre autorise a satisfait aux verifications de l'exigence 7.1.b ; dans le cas d'une intervention realisee a distance par une personne localisee hors de l'Union Europeenne, mettre en oeuvre une passerelle securisee (poste de rebond) par laquelle la personne devra se connecter et permettant une supervision (autorisation ou interdiction des actions, demandes d'explications, etc.) en temps reel, par une personne ayant elle-meme satisfait aux verifications de l'exigence 7.1.b ; considerer les actions menees, une fois l'acces autorise, comme des actions d'administration et les journaliser comme telles ; supprimer l'autorisation d'acces aux donnees du commanditaire au terme de ces actions." + } + ], + "Checks": [ + "network_rdp_internet_access_restricted", + "network_ssh_internet_access_restricted", + "storage_blob_public_access_level_is_disabled", + "storage_default_network_access_rule_is_denied", + "sqlserver_unrestricted_inbound_access", + "postgresql_flexible_server_allow_access_services_disabled", + "aisearch_service_not_publicly_accessible", + "app_function_not_publicly_accessible", + "containerregistry_not_publicly_accessible", + "cosmosdb_account_use_private_endpoints" + ] + }, + { + "Id": "10.1", + "Description": "Les donnees stockees dans le cadre du service cloud doivent etre chiffrees au repos en utilisant des algorithmes et des longueurs de cle conformes a l'etat de l'art.", + "Name": "Chiffrement des donnees stockees", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "ec2", + "Type": "Automated", + "Comment": "a) Le prestataire doit definir et mettre en oeuvre un mecanisme de chiffrement empechant la recuperation des donnees des commanditaires en cas de reallocation d'une ressource ou de recuperation du support physique. Dans le cas d'un service IaaS ou CaaS, cet objectif pourra par exemple etre atteint par un chiffrement du disque ou du systeme de fichier, lorsque le protocole d'acces en mode fichiers garantit que seuls des blocs vides peuvent etre alloues, ou par un chiffrement par volume dans le cas d'un acces en mode bloc, avec au moins une cle par commanditaire. Dans le cas d'un service PaaS ou SaaS, cet objectif pourra etre atteint en utilisant un chiffrement applicatif dans le perimetre du prestataire, avec au moins une cle par commanditaire. b) Le prestataire doit utiliser une methode de chiffrement des donnees respectant les regles de [CRYPTO_B1]. c) Il est recommande d'utiliser une methode de chiffrement des donnees respectant les recommandations de [CRYPTO_B1]. d) Le prestataire doit mettre en place un chiffrement des donnees sur les supports amovibles et les supports de sauvegarde amenes a quitter le perimetre de securite physique du systeme d'information du service (au sens du chapitre 10), en fonction du besoin de securite des donnees (voir chapitre 8.3)." + } + ], + "Checks": [ + "storage_ensure_encryption_with_customer_managed_keys", + "storage_infrastructure_encryption_is_enabled", + "vm_ensure_attached_disks_encrypted_with_cmk", + "vm_ensure_unattached_disks_encrypted_with_cmk", + "sqlserver_tde_encryption_enabled", + "sqlserver_tde_encrypted_with_cmk", + "databricks_workspace_cmk_encryption_enabled", + "monitor_storage_account_with_activity_logs_cmk_encrypted" + ] + }, + { + "Id": "10.2", + "Description": "Les flux de donnees entre les composants du service cloud et entre le service et les commanditaires doivent etre chiffres en transit en utilisant des protocoles et des algorithmes conformes a l'etat de l'art.", + "Name": "Chiffrement des flux", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "elb", + "Type": "Automated", + "Comment": "a) Lorsque le prestataire met en oeuvre un mecanisme de chiffrement des flux reseau, celui-ci doit respecter les regles de [CRYPTO_B1]. b) Lorsque le prestataire met en oeuvre un mecanisme de chiffrement des flux reseau, il est recommande que celui-ci respecte les recommandations de [CRYPTO_B1]. c) Si le protocole TLS est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_TLS]. d) Si le protocole IPsec est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_IPSEC]. e) Si le protocole SSH est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_SSH]." + } + ], + "Checks": [ + "storage_secure_transfer_required_is_enabled", + "storage_ensure_minimum_tls_version_12", + "storage_smb_channel_encryption_with_secure_algorithm", + "storage_smb_protocol_version_is_latest", + "app_minimum_tls_version_12", + "app_ensure_http_is_redirected_to_https", + "app_ftp_deployment_disabled", + "app_function_ftps_deployment_disabled", + "sqlserver_recommended_minimal_tls_version", + "postgresql_flexible_server_enforce_ssl_enabled", + "mysql_flexible_server_ssl_connection_enabled", + "mysql_flexible_server_minimum_tls_version_12" + ] + }, + { + "Id": "10.3", + "Description": "Les mots de passe doivent etre stockes sous forme hachee en utilisant des algorithmes robustes conformes a l'etat de l'art et les politiques de mot de passe doivent imposer des exigences de complexite adequates.", + "Name": "Hachage des mots de passe", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "iam", + "Type": "Partially Automated", + "Comment": "a) Le prestataire ne doit stocker que l'empreinte des mots de passe des utilisateurs et des comptes techniques. b) Le prestataire doit mettre en oeuvre une fonction de hachage respectant les regles de [CRYPTO_B1]. c) Il est recommande que le prestataire mette en oeuvre une fonction de hachage respectant les recommandations de [CRYPTO_B1]. d) Le prestataire doit generer les empreintes des mots de passe avec une fonction de hachage associee a l'utilisation d'un sel cryptographique respectant les regles de [CRYPTO_B1]." + } + ], + "Checks": [] + }, + { + "Id": "10.4", + "Description": "Des mecanismes de non-repudiation doivent etre mis en oeuvre pour assurer la tracabilite des actions effectuees sur le service cloud, incluant la validation de l'integrite des journaux.", + "Name": "Non repudiation", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "cloudtrail", + "Type": "Partially Automated", + "Comment": "a) Lorsque le prestataire met en oeuvre un mecanisme de signature electronique, celui-ci doit respecter les regles de [CRYPTO_B1]. b) Lorsque le prestataire met en oeuvre un mecanisme de signature electronique, il est recommande que celui-ci respecte les recommandations de [CRYPTO_B1]." + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists" + ] + }, + { + "Id": "10.5", + "Description": "Les secrets cryptographiques (cles, certificats, mots de passe) doivent etre geres de maniere securisee tout au long de leur cycle de vie, incluant la generation, le stockage, la distribution, la rotation et la destruction.", + "Name": "Gestion des secrets", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "kms", + "Type": "Automated", + "Comment": "a) Le prestataire doit mettre en oeuvre des cles cryptographiques respectant les regles de [CRYPTO_B2]. b) Il est recommande que le prestataire mette en oeuvre des cles cryptographiques respectant les recommandations de [CRYPTO_B2]. c) Le prestataire doit proteger l'acces aux cles cryptographiques et autres secrets utilises pour le chiffrement des donnees par un moyen adapte : conteneur de securite (logiciel ou materiel) ou support disjoint. d) Le prestataire doit proteger l'acces aux cles cryptographiques et autres secrets utilises pour les taches d'administration par un conteneur de securite adapte, logiciel ou materiel." + } + ], + "Checks": [ + "keyvault_key_rotation_enabled", + "keyvault_recoverable", + "keyvault_rbac_enabled", + "keyvault_rbac_key_expiration_set", + "keyvault_rbac_secret_expiration_set", + "keyvault_key_expiration_set_in_non_rbac", + "keyvault_non_rbac_secret_expiration_set", + "keyvault_logging_enabled", + "keyvault_private_endpoints", + "keyvault_access_only_through_private_endpoints" + ] + }, + { + "Id": "10.6", + "Description": "Les racines de confiance (certificats racine, autorites de certification) utilisees dans le cadre du service cloud doivent etre gerees de maniere securisee. Les certificats doivent etre valides et utiliser des algorithmes de cle robustes.", + "Name": "Racines de confiance", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "acm", + "Type": "Partially Automated", + "Comment": "a) Sur l'infrastructure technique, le prestataire doit utiliser exclusivement des certificats de cle publique issus d'une autorite de certification d'un Etat membre de l'Union Europeenne (les ceremonies de generation des cles maitresses doivent avoir lieu dans un pays membre de l'Union Europeenne et en presence du prestataire)." + } + ], + "Checks": [] + }, + { + "Id": "11.1", + "Description": "Des perimetres de securite doivent etre definis et utilises pour proteger les zones contenant des informations sensibles ou critiques et les moyens de traitement de l'information.", + "Name": "Perimetres de securite physique", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des perimetres de securite, incluant le marquage des zones et les differents moyens de limitation et de controle des acces. b) Le prestataire doit distinguer des zones publiques, des zones privees et des zones sensibles. 11.1.1. Zones publiques : a) Les zones publiques sont accessibles a tous dans les limites de la propriete du prestataire. Le prestataire ne doit heberger aucune ressource devolue au service ou permettant d'acceder a des composantes de celui-ci dans les zones publiques. 11.1.2. Zones privees : a) Les zones privees peuvent heberger : les plateformes et moyens de developpement du service ; les postes d'administration, d'exploitation et de supervision ; les locaux a partir desquels le prestataire opere. 11.1.3. Zones sensibles : a) Les zones sensibles sont reservees a l'hebergement du systeme d'information de production du service hors postes d'administration, d'exploitation et de supervision." + } + ], + "Checks": [] + }, + { + "Id": "11.2", + "Description": "Les zones securisees doivent etre protegees par des controles d'acces physiques adequats pour s'assurer que seul le personnel autorise est admis.", + "Name": "Controle d'acces physique", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "11.2.1. Zones privees : a) Le prestataire doit proteger les zones privees contre les acces non autorises. Pour ce faire, il doit mettre en oeuvre un controle d'acces physique reposant au moins sur un facteur personnel : la connaissance d'un secret, la detention d'un objet ou la biometrie. b) Il est recommande que le prestataire respecte les recommandations de [G_CVAP] pour mettre en oeuvre du controle d'acces physique. c) Le prestataire doit definir et documenter des mesures d'acces physique derogatoires en cas d'urgence. d) Le prestataire doit afficher a l'entree des zones privees un avertissement relatif aux limites et conditions d'acces a ces zones. e) Le prestataire doit definir et documenter les plages horaires et conditions d'acces aux zones privees en fonction des profils des intervenants. f) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de s'assurer que les visiteurs sont systematiquement accompagnes par le prestataire lors de leurs acces et sejours en zone privee. Le prestataire doit conserver une trace de l'identite des visiteurs conformement a la legislation et reglementation en vigueur. g) En cas d'intervention (actions de diagnostic, de maintenance, ou d'administration) en zone privee par un tiers visiteur, le prestataire doit faire superviser (suivre, autoriser, interdire, questionner) les actions par un personnel ayant satisfait aux verifications de l'exigence 7.1.b. h) Le prestataire doit documenter et mettre en oeuvre des mecanismes de surveillance et de detection des acces non autorises aux zones privees. 11.2.2. Zones sensibles : a) Le prestataire doit proteger les zones sensibles contre les acces non autorises. Pour ce faire, il doit mettre en oeuvre un controle d'acces physique reposant au moins sur deux facteurs personnels : la connaissance d'un secret, la detention d'un objet ou la biometrie. b) Il est recommande que le prestataire respecte les recommandations de [G_CVAP] pour la mise en oeuvre du controle d'acces physique. c) Le prestataire doit definir et documenter des mesures d'acces physique derogatoires en cas d'urgence. d) Le prestataire doit afficher a l'entree des zones sensibles un avertissement relatif aux limites et conditions d'acces a ces zones. e) Le prestataire doit definir et documenter les plages horaires et conditions d'acces aux zones sensibles en fonction des profils des intervenants. f) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de s'assurer que les visiteurs sont systematiquement accompagnes par le prestataire lors de leurs acces et sejours en zone sensible. Le prestataire doit conserver une trace de l'identite des visiteurs conformement a la legislation et reglementation en vigueur. g) En cas d'intervention (actions de diagnostic, de maintenance, ou d'administration) en zone sensible par un tiers visiteur, le prestataire doit faire superviser (suivre, autoriser, interdire, questionner) les actions par un personnel ayant satisfait aux verifications de l'exigence 7.1.b. h) Le prestataire doit documenter et mettre en oeuvre des mecanismes de surveillance et de detection des acces non autorises aux zones sensibles. i) Le prestataire doit mettre en place une journalisation des acces physiques aux zones sensibles. Il doit effectuer une revue de ces journaux au moins mensuellement. j) Le prestataire doit mettre en oeuvre les moyens garantissant qu'aucun acces direct n'existe entre une zone publique et une zone sensible." + } + ], + "Checks": [] + }, + { + "Id": "11.3", + "Description": "Des mesures de protection contre les menaces exterieures et environnementales, telles que les catastrophes naturelles, les attaques malveillantes ou les accidents, doivent etre concues et appliquees.", + "Name": "Protection contre les menaces exterieures et environnementales", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de minimiser les risques inherents aux sinistres physiques (incendie, degat des eaux, etc.) et naturels (risques climatiques, inondations, seismes, etc.). b) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de limiter les risques de depart et de propagation de feu ainsi que les risques de degat des eaux. c) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de prevenir et limiter les consequences d'une coupure d'alimentation electrique et permettre une reprise du service conformement aux exigences de disponibilite du service definies dans la convention de service. d) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de maintenir des conditions de temperature et d'humidite adaptees aux equipements. De plus, il doit mettre en oeuvre des mesures permettant de prevenir les pannes de climatisation et d'en limiter les consequences. e) Le prestataire doit documenter et mettre en oeuvre des controles et tests reguliers des equipements de detection et de protection physique." + } + ], + "Checks": [] + }, + { + "Id": "11.4", + "Description": "Des mesures de securite physique pour le travail dans les zones privees et sensibles doivent etre concues et appliquees.", + "Name": "Travail dans les zones privees et sensibles", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit integrer les elements de securite physique dans la politique de securite et l'appreciation des risques conformement au niveau de securite requis par la categorie de la zone. b) Le prestataire doit documenter et mettre en oeuvre des procedures relatives au travail en zones privees et sensibles. Il doit communiquer ces procedures aux intervenants concernes." + } + ], + "Checks": [] + }, + { + "Id": "11.5", + "Description": "Les points d'acces tels que les zones de livraison et de chargement et les autres points par lesquels des personnes non autorisees peuvent penetrer dans les locaux doivent etre controles.", + "Name": "Zones de livraison et de chargement", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Les zones de livraison et de chargement et les autres points par lesquels des personnes non autorisees peuvent penetrer dans les locaux sans etre accompagnees sont considerees comme des zones publiques. b) Le prestataire doit isoler les points d'acces de ces zones vers les zones privees et sensibles, de facon a eviter les acces non autorises, ou a defaut, implementer des mesures compensatoires permettant d'assurer le meme niveau de securite." + } + ], + "Checks": [] + }, + { + "Id": "11.6", + "Description": "Le cablage electrique et de telecommunications transportant des donnees ou supportant des services d'information doit etre protege contre les interceptions, les interferences ou les dommages.", + "Name": "Securite du cablage", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de proteger le cablage electrique et de telecommunication des dommages physiques et des possibilites d'interception. b) Le prestataire doit etablir et tenir a jour un plan de cablage. c) Il est recommande que le prestataire mette en oeuvre des mesures permettant d'identifier les cables (par exemple code couleur, etiquette, etc.) afin d'en faciliter l'exploitation et limiter les erreurs de manipulation." + } + ], + "Checks": [] + }, + { + "Id": "11.7", + "Description": "Les materiels doivent etre entretenus correctement pour garantir leur disponibilite permanente et leur integrite.", + "Name": "Maintenance des materiels", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de s'assurer que les conditions d'installation, de maintenance et d'entretien des equipements du systeme d'information du service heberges en zones privees et sensibles sont compatibles avec les exigences de confidentialite et de disponibilite du service definies dans la convention de service. b) Le prestataire doit souscrire des contrats de maintenance permettant de disposer des mises a jour de securite des logiciels installes sur les equipements du systeme d'information du service. c) Le prestataire doit s'assurer que les supports ne peuvent etre retournes a un tiers que si les donnees du commanditaire y sont stockees chiffrees conformement au chapitre 10.1 ou ont prealablement ete detruites a l'aide d'un mecanisme d'effacement securise par reecriture de motifs aleatoires. d) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de s'assurer que les conditions d'installation, de maintenance et d'entretien des equipements techniques annexes (alimentation electrique, climatisation, incendie, etc.) sont compatibles avec les exigences de disponibilite du service definies dans la convention de service." + } + ], + "Checks": [] + }, + { + "Id": "11.8", + "Description": "Les materiels, les informations ou les logiciels ne doivent pas etre sortis des locaux du prestataire sans autorisation prealable.", + "Name": "Sortie des actifs", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de transfert hors site de donnees du commanditaire, equipements et logiciels. Cette procedure doit necessiter que la direction du prestataire donne son autorisation ecrite. Dans tous les cas, le prestataire doit mettre en oeuvre les moyens permettant de garantir que le niveau de protection en confidentialite et en integrite des actifs durant leur transport est equivalent a celui sur site." + } + ], + "Checks": [] + }, + { + "Id": "11.9", + "Description": "Tous les composants des equipements contenant des supports de stockage doivent etre verifies pour s'assurer que toute donnee sensible et tout logiciel sous licence ont ete supprimes ou ecrases de facon securisee avant leur mise au rebut ou leur reutilisation.", + "Name": "Recyclage securise du materiel", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des moyens permettant d'effacer de maniere securisee par reecriture de motifs aleatoires tout support de donnees mis a disposition d'un commanditaire. Si l'espace de stockage est chiffre dans le cadre de l'exigence 10.1.a), l'effacement peut etre realise par un effacement securise de la cle de chiffrement." + } + ], + "Checks": [] + }, + { + "Id": "11.10", + "Description": "Le materiel en attente d'utilisation doit etre protege de maniere adequate.", + "Name": "Materiel en attente d'utilisation", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de protection du materiel en attente d'utilisation." + } + ], + "Checks": [] + }, + { + "Id": "12.1", + "Description": "Les procedures d'exploitation doivent etre documentees et mises a disposition de tous les utilisateurs concernes.", + "Name": "Procedures d'exploitation documentees", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter les procedures d'exploitation, les tenir a jour et les rendre accessibles au personnel concerne." + } + ], + "Checks": [] + }, + { + "Id": "12.2", + "Description": "Les changements apportes au systeme d'information du prestataire, aux processus metier, aux moyens de traitement de l'information et aux systemes qui ont une incidence sur la securite de l'information doivent etre geres.", + "Name": "Gestion des changements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "config", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de gestion des changements apportes aux systemes et moyens de traitement de l'information. b) Le prestataire doit documenter et mettre en oeuvre une procedure permettant, en cas d'operations realisees par le prestataire et pouvant avoir un impact sur la securite ou la disponibilite du service, de communiquer au plus tot a l'ensemble de ses commanditaires les informations suivantes : la date et l'heure programmees du debut et de la fin des operations ; la nature des operations ; les impacts sur la securite ou la disponibilite du service ; le contact au sein du prestataire. c) Dans le cadre d'un service PaaS, le prestataire doit informer au plus tot le commanditaire de toute modification a venir sur des elements logiciels sous sa responsabilite des lors que la compatibilite complete ne peut etre assuree. d) Le prestataire doit informer au plus tot le commanditaire de toute modification a venir sur les elements du service des lors qu'elle est susceptible d'occasionner une perte de fonctionnalite pour le commanditaire." + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists", + "monitor_diagnostic_setting_with_appropriate_categories" + ] + }, + { + "Id": "12.3", + "Description": "Les environnements de developpement, de test et d'exploitation doivent etre separes pour reduire les risques d'acces non autorise ou de changements non souhaites dans l'environnement d'exploitation.", + "Name": "Separation des environnements de developpement, de test et d'exploitation", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "organizations", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de separer physiquement les environnements lies a la production du service des autres environnements, dont les environnements de developpement." + } + ], + "Checks": [] + }, + { + "Id": "12.4", + "Description": "Des mesures de detection, de prevention et de recuperation conjuguees a une sensibilisation des utilisateurs doivent etre mises en oeuvre pour proteger le systeme d'information contre les codes malveillants.", + "Name": "Mesures contre les codes malveillants", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "guardduty", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures de detection, de prevention et de restauration pour se proteger des codes malveillants. Le perimetre d'application de cette exigence sur le systeme d'information du service doit necessairement contenir les postes utilisateurs sous la responsabilite du prestataire et les flux entrants sur ce meme systeme d'information. b) Le prestataire doit documenter et mettre en oeuvre une sensibilisation de ses employes aux risques lies aux codes malveillants et aux bonnes pratiques pour reduire l'impact d'une infection." + } + ], + "Checks": [ + "defender_ensure_defender_for_server_is_on", + "defender_ensure_defender_for_containers_is_on", + "defender_ensure_defender_for_storage_is_on", + "defender_ensure_defender_for_app_services_is_on", + "defender_ensure_defender_for_arm_is_on", + "defender_ensure_defender_for_dns_is_on", + "defender_ensure_defender_for_keyvault_is_on", + "defender_ensure_defender_for_databases_is_on", + "defender_ensure_defender_for_cosmosdb_is_on", + "defender_ensure_defender_for_sql_servers_is_on", + "defender_ensure_defender_for_azure_sql_databases_is_on", + "defender_ensure_defender_for_os_relational_databases_is_on", + "defender_ensure_wdatp_is_enabled", + "defender_container_images_scan_enabled" + ] + }, + { + "Id": "12.5", + "Description": "Des copies de sauvegarde des informations, des logiciels et des images systeme doivent etre effectuees et testees regulierement conformement a une politique de sauvegarde convenue.", + "Name": "Sauvegarde des informations", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "backup", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de sauvegarde et de restauration des donnees sous sa responsabilite dans le cadre du service. Cette politique doit prevoir une sauvegarde quotidienne de l'ensemble des donnees (informations, logiciels, configurations, etc.) sous la responsabilite du prestataire dans le cadre du service. b) Le prestataire doit documenter et mettre en oeuvre des mesures de protection des sauvegardes conformement a la politique de controle d'acces (voir chapitre 9). Cette politique doit prevoir une revue mensuelle des traces d'acces aux sauvegardes. c) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester regulierement la restauration des sauvegardes. d) Le prestataire doit localiser les sauvegardes a une distance suffisante des equipements principaux en coherence avec les resultats de l'appreciation de risques et permettant de faire face a des sinistres majeurs. Les sauvegardes sont assujetties aux memes exigences de localisation que les donnees operationnelles. Le ou les sites de sauvegarde sont assujettis aux memes exigences de securite que le site principal, en particulier celles listees aux chapitres 8 et 11. Les communications entre site principal et site de sauvegarde doivent etre protegees par chiffrement, conformement aux exigences du chapitre 10." + } + ], + "Checks": [ + "vm_backup_enabled", + "vm_sufficient_daily_backup_retention_period", + "storage_ensure_soft_delete_is_enabled", + "storage_ensure_file_shares_soft_delete_is_enabled", + "storage_blob_versioning_is_enabled", + "storage_geo_redundant_enabled", + "keyvault_recoverable" + ] + }, + { + "Id": "12.6", + "Description": "Des journaux d'evenements enregistrant les activites des utilisateurs, les exceptions, les defaillances et les evenements de securite de l'information doivent etre crees, tenus a jour et regulierement revus.", + "Name": "Journalisation des evenements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "cloudtrail", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de journalisation incluant au minimum les elements suivants : la liste des sources de collecte ; la liste des evenements a journaliser par source ; l'objet de la journalisation par evenement ; la frequence de la collecte et base de temps utilisee ; la duree de retention locale et centralisee ; les mesures de protection des journaux (dont chiffrement et duplication) ; la localisation des journaux. b) Le prestataire doit generer et collecter les evenements suivants : les activites des utilisateurs liees a la securite de l'information ; la modification des droits d'acces dans le perimetre de sa responsabilite ; les evenements issus des mecanismes de lutte contre les codes malveillants (voir chapitre 12.4) ; les exceptions ; les defaillances ; tout autre evenement lie a la securite de l'information. c) Le prestataire doit conserver les evenements issus de la journalisation pendant une duree minimale de six mois sous reserve du respect des exigences legales et reglementaires. d) Le prestataire doit fournir, sur demande d'un commanditaire, l'ensemble des evenements le concernant. e) Il est recommande que le systeme de journalisation mis en place par le prestataire respecte les recommandations de [NT_JOURNAL]." + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists", + "monitor_diagnostic_setting_with_appropriate_categories", + "network_flow_log_captured_sent", + "network_flow_log_more_than_90_days", + "network_watcher_enabled", + "app_http_logs_enabled", + "keyvault_logging_enabled", + "sqlserver_auditing_enabled", + "sqlserver_auditing_retention_90_days", + "postgresql_flexible_server_log_checkpoints_on", + "postgresql_flexible_server_log_connections_on", + "postgresql_flexible_server_log_disconnections_on", + "postgresql_flexible_server_log_retention_days_greater_3", + "mysql_flexible_server_audit_log_enabled", + "mysql_flexible_server_audit_log_connection_activated" + ] + }, + { + "Id": "12.7", + "Description": "Les moyens de journalisation et les informations journalisees doivent etre proteges contre les risques de falsification et les acces non autorises.", + "Name": "Protection de l'information journalisee", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "cloudtrail", + "Type": "Automated", + "Comment": "a) Le prestataire doit proteger les equipements de journalisation et les evenements journalises contre les atteintes a leur disponibilite, integrite ou confidentialite, conformement au chapitre 3.2 de [NT_JOURNAL]. b) Le prestataire doit gerer le dimensionnement de l'espace de stockage de l'ensemble des equipements hebergeant une ou plusieurs sources de collecte afin de permettre la conservation locale des evenements journalises prevue par la politique de journalisation des evenements. Cette gestion du dimensionnement doit prendre en compte les evolutions du systeme d'information. c) Le prestataire doit transferer les evenements journalises en assurant leur protection en confidentialite et en integrite, sur un ou plusieurs serveurs centraux dedies et doit les stocker sur une machine physique distincte de celle qui les a generes. d) Le prestataire doit mettre en place une sauvegarde des evenements collectes suivant une politique adaptee. e) Le prestataire doit executer les processus de journalisation et de collecte des evenements avec des comptes disposant de privileges necessaires et suffisants et doit limiter l'acces aux evenements journalises conformement a la politique de controle d'acces (voir chapitre 9.1)." + } + ], + "Checks": [ + "monitor_storage_account_with_activity_logs_cmk_encrypted", + "monitor_storage_account_with_activity_logs_is_private" + ] + }, + { + "Id": "12.8", + "Description": "Les horloges de tous les systemes de traitement de l'information pertinents d'un organisme ou d'un domaine de securite doivent etre synchronisees sur une source de reference temporelle unique.", + "Name": "Synchronisation des horloges", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une synchronisation des horloges de l'ensemble des equipements sur une ou plusieurs sources de temps internes coherentes entre elles. Ces sources pourront elles-memes etre synchronisees sur plusieurs sources fiables externes, sauf pour les reseaux isoles. b) Le prestataire doit mettre en place l'horodatage de chaque evenement journalise." + } + ], + "Checks": [] + }, + { + "Id": "12.9", + "Description": "Les evenements de securite doivent etre analyses et correles afin de detecter les incidents de securite. Des systemes de detection et de correlation doivent etre mis en oeuvre.", + "Name": "Analyse et correlation des evenements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "securityhub", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une infrastructure permettant l'analyse et la correlation des evenements enregistres par le systeme de journalisation afin de detecter les evenements susceptibles d'affecter la securite du systeme d'information du service, en temps reel ou a posteriori pour des evenements remontant jusqu'a six mois. b) Il est recommande de s'appuyer sur le referentiel d'exigences des prestataires de detection d'incidents de securite [PDIS] pour la mise en place et l'exploitation de l'infrastructure d'analyse et de correlation des evenements. c) Le prestataire doit acquitter les alarmes remontees par l'infrastructure d'analyse et de correlation des evenements au moins quotidiennement." + } + ], + "Checks": [ + "defender_ensure_defender_for_server_is_on", + "defender_ensure_mcas_is_enabled", + "defender_ensure_notify_alerts_severity_is_high", + "defender_ensure_notify_emails_to_owners", + "defender_additional_email_configured_with_a_security_contact", + "defender_attack_path_notifications_properly_configured", + "monitor_alert_create_policy_assignment", + "monitor_alert_delete_policy_assignment", + "monitor_alert_create_update_nsg", + "monitor_alert_delete_nsg", + "monitor_alert_create_update_security_solution", + "monitor_alert_delete_security_solution", + "monitor_alert_create_update_sqlserver_fr", + "monitor_alert_delete_sqlserver_fr", + "monitor_alert_create_update_public_ip_address_rule", + "monitor_alert_delete_public_ip_address_rule", + "monitor_alert_service_health_exists" + ] + }, + { + "Id": "12.10", + "Description": "Des regles regissant l'installation de logiciels par les utilisateurs doivent etre etablies et mises en oeuvre. Les systemes doivent etre geres de maniere centralisee et les correctifs appliques regulierement.", + "Name": "Installation de logiciels sur des systemes en exploitation", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "ssm", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de controler l'installation de logiciels sur les equipements du systeme d'information du service. b) Le prestataire doit documenter et mettre en oeuvre une procedure de gestion de la configuration des environnements logiciels mis a la disposition du commanditaire, notamment pour leur maintien en condition de securite. c) Le prestataire doit fournir une capacite d'inspection et de suppression, si necessaire, des entrants (controle de l'authenticite et de l'innocuite des mises a jour, controle de l'innocuite des outils fournis, etc.) relatifs au perimetre de l'infrastructure technique : cette capacite d'inspection et de suppression doit generer des journaux d'activite et doit pouvoir faire l'objet d'un audit de code ; les entrants doivent etre traites sur des dispositifs specifiques operes et maintenus par le prestataire et heberges dans une zone cloisonnee du reste de l'infrastructure (du type zone demilitarisee telle que definie dans [G_INT])." + } + ], + "Checks": [ + "defender_ensure_system_updates_are_applied", + "defender_assessments_vm_endpoint_protection_installed" + ] + }, + { + "Id": "12.11", + "Description": "Les informations sur les vulnerabilites techniques des systemes d'information utilises doivent etre obtenues en temps voulu, l'exposition du prestataire a ces vulnerabilites doit etre evaluee et les mesures appropriees doivent etre prises pour traiter le risque associe.", + "Name": "Gestion des vulnerabilites techniques", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "inspector", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus de veille permettant de gerer les vulnerabilites techniques des logiciels et des systemes utilises dans le systeme d'information du service. b) Le prestataire doit evaluer son exposition a ces vulnerabilites en les incluant dans l'appreciation des risques et appliquer les mesures de traitement du risque adaptees." + } + ], + "Checks": [ + "sqlserver_vulnerability_assessment_enabled", + "sqlserver_va_periodic_recurring_scans_enabled", + "sqlserver_va_emails_notifications_admins_enabled", + "sqlserver_va_scan_reports_configured", + "defender_container_images_resolved_vulnerabilities", + "defender_container_images_scan_enabled" + ] + }, + { + "Id": "12.12", + "Description": "L'administration des systemes d'information du service cloud doit etre effectuee de maniere securisee via des canaux dedies et des protocoles securises.", + "Name": "Administration", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "ec2", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure obligeant les administrateurs sous sa responsabilite a utiliser des terminaux dedies pour la realisation exclusive des taches d'administration, en accord avec le chapitre 4.1 intitule 'poste et reseau d'administration' de [NT_ADMIN]. Il doit les maitriser et les maintenir a jour. b) Le prestataire doit mettre en place des mesures de durcissement de la configuration des terminaux utilises pour les taches d'administration, notamment celles du chapitre 4.2 intitule 'securisation du socle' de [NT_ADMIN]. c) Lorsque le prestataire autorise une situation de mobilite pour les administrateurs sous sa responsabilite, il doit l'encadrer par une politique documentee. La solution mise en oeuvre doit assurer que le niveau de securite de cette situation de mobilite est au moins equivalent au niveau de securite hors situation de mobilite (voir chapitres 9.6 et 9.7). Cette solution doit notamment inclure : l'utilisation d'un tunnel chiffre, non debrayable et non contournable, pour l'ensemble des flux (voir chapitre 10.2) ; le chiffrement integral du disque (voir chapitre 10.1)." + } + ], + "Checks": [ + "vm_jit_access_enabled", + "network_bastion_host_exists" + ] + }, + { + "Id": "12.13", + "Description": "Le telediagnostic et la telemaintenance des composants de l'infrastructure doivent etre encadres par des procedures de securite specifiques.", + "Name": "Telediagnostic et telemaintenance des composants de l'infrastructure", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Dans le cadre du telediagnostic ou de la telemaintenance de composants de l'infrastructure, considerant les risques d'atteinte a la confidentialite des donnees des commanditaires, le prestataire doit : verifier que la personne a qui l'acces doit etre autorise a satisfait aux verifications de l'exigence 7.1.b ; dans le cas d'une intervention realisee par une personne n'ayant pas satisfait aux verifications de l'exigence 7.1.b, mettre en oeuvre une passerelle securisee (poste de rebond) par laquelle la personne devra se connecter et permettant une supervision des actions (autorisation ou interdiction des actions, demande d'explications, etc.) en temps reel, par une personne ayant elle-meme satisfait aux verifications de l'exigence 7.1.b. La passerelle securisee devra repondre aux objectifs de securite specifies dans [G_EXT] ; considerer les actions menees, une fois l'acces autorise, comme des actions d'administration et les journaliser comme telles ; supprimer l'autorisation d'acces a l'issue de l'intervention." + } + ], + "Checks": [] + }, + { + "Id": "12.14", + "Description": "Les flux sortants de l'infrastructure du service cloud doivent etre surveilles afin de detecter et de prevenir les exfiltrations de donnees et les communications non autorisees.", + "Name": "Surveillance des flux sortants de l'infrastructure", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "vpc", + "Type": "Automated", + "Comment": "a) Le prestataire doit fournir une capacite d'inspection et de suppression des sortants de l'infrastructure technique relatifs au perimetre du service (informations de facturation, les eventuels journaux necessaires au traitement d'incidents, etc.) : les sortants doivent pouvoir etre expurges des donnees pouvant porter atteinte a la confidentialite des donnees des commanditaires ; cette capacite d'inspection et de suppression doit generer des journaux d'activite et doit pouvoir faire l'objet d'un audit de code ; les sortants sont traites sur des dispositifs specifiques operes et maintenus par le prestataire, et heberges dans une zone cloisonnee du reste de l'infrastructure (du type zone demilitarisee telle que definie dans [G_INT])." + } + ], + "Checks": [ + "network_flow_log_captured_sent", + "network_watcher_enabled" + ] + }, + { + "Id": "13.1", + "Description": "Le prestataire doit etablir et maintenir une cartographie complete et a jour de son systeme d'information, incluant les reseaux, les flux et les composants.", + "Name": "Cartographie du systeme d'information", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "config", + "Type": "Automated", + "Comment": "a) Le prestataire doit etablir et tenir a jour une cartographie du systeme d'information du service, en lien avec l'inventaire des actifs (voir chapitre 8.1), comprenant au minimum les elements suivants : la liste des ressources materielles ou virtualisees ; les noms et fonctions des applications, supportant le service ; le schema d'architecture reseau au niveau 3 du modele OSI sur lequel les points nevralgiques sont identifies : les points d'interconnexions, notamment avec les reseaux tiers et publics ; les reseaux, sous-reseaux, notamment les reseaux d'administration ; les equipements assurant des fonctions de securite (filtrage, authentification, chiffrement, etc.) ; les serveurs hebergeant des donnees ou assurant des fonctions sensibles ; la matrice des flux reseau autorises en precisant : leur description technique (services, protocoles et ports) ; la justification metier ou d'infrastructure technique ; le cas echeant, lorsque des services, protocoles ou ports reputes non surs sont utilises, les mesures compensatoires mises en place, dans la logique de defense en profondeur. b) Le prestataire doit reviser au moins annuellement la cartographie." + } + ], + "Checks": [ + "policy_ensure_asc_enforcement_enabled", + "network_watcher_enabled", + "network_flow_log_captured_sent" + ] + }, + { + "Id": "13.2", + "Description": "Les reseaux doivent etre cloisonnes et les flux entre les segments doivent etre filtres selon le principe du moindre privilege. Les groupes de securite et les listes de controle d'acces reseau doivent etre configures de maniere restrictive.", + "Name": "Cloisonnement des reseaux", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "ec2", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre, pour le systeme d'information du service, les mesures de cloisonnement (logique, physique ou par chiffrement) pour separer les flux reseau selon : la sensibilite des informations transmises ; la nature des flux (production, administration, supervision, etc.) ; le domaine d'appartenance des flux (des commanditaires - avec distinction par commanditaire ou ensemble de commanditaires, du prestataire, des tiers, etc.) ; le domaine technique (traitement, stockage, etc.). b) Le prestataire doit cloisonner, physiquement ou par chiffrement, tous les flux de donnees internes au systeme d'information du service vis-a-vis de tout autre systeme d'information. Lorsque ce cloisonnement est realise par chiffrement, il est realise en accord avec les exigences du chapitre 10.2. c) Dans le cas ou le reseau d'administration de l'infrastructure technique ne fait pas l'objet d'un cloisonnement physique, les flux d'administration doivent transiter dans un tunnel chiffre, en accord avec les exigences du chapitre 10.2. d) Le prestataire doit mettre en place et configurer un pare-feu applicatif pour proteger les interfaces d'administration destinees a ses commanditaires et exposees sur un reseau public. e) Le prestataire doit mettre en oeuvre sur l'ensemble des interfaces d'administration et de supervision de l'infrastructure technique du service un mecanisme de filtrage n'autorisant que les connexions legitimes identifiees dans la matrice des flux autorises." + } + ], + "Checks": [ + "network_rdp_internet_access_restricted", + "network_ssh_internet_access_restricted", + "network_http_internet_access_restricted", + "network_udp_internet_access_restricted", + "aks_clusters_public_access_disabled", + "aks_network_policy_enabled", + "cosmosdb_account_firewall_use_selected_networks", + "cosmosdb_account_use_private_endpoints", + "storage_default_network_access_rule_is_denied", + "storage_ensure_private_endpoints_in_storage_accounts", + "containerregistry_not_publicly_accessible", + "containerregistry_uses_private_link", + "keyvault_access_only_through_private_endpoints" + ] + }, + { + "Id": "13.3", + "Description": "Les reseaux doivent etre surveilles de maniere continue afin de detecter les activites anormales ou malveillantes.", + "Name": "Surveillance des reseaux", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "guardduty", + "Type": "Automated", + "Comment": "a) Le prestataire doit disposer une ou plusieurs sondes de detection d'incidents de securite sur le systeme d'information du service. Ces sondes doivent notamment permettre la supervision de chacune des interconnexions du systeme d'information du service avec des systemes d'information tiers et des reseaux publics. Ces sondes doivent etre des sources de collecte pour l'infrastructure d'analyse et de correlation des evenements (voir chapitre 12.9)." + } + ], + "Checks": [ + "defender_ensure_defender_for_server_is_on", + "network_flow_log_captured_sent", + "network_watcher_enabled" + ] + }, + { + "Id": "14.1", + "Description": "Des regles de developpement securise des logiciels et des systemes doivent etre etablies et appliquees au sein du prestataire.", + "Name": "Politique de developpement securise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des regles de developpement securise des logiciels et des systemes, et les appliquer aux developpements internes. b) Le prestataire doit documenter et mettre en oeuvre une formation adaptee en developpement securise aux employes concernes." + } + ], + "Checks": [] + }, + { + "Id": "14.2", + "Description": "Les changements apportes aux systemes dans le cycle de developpement doivent etre geres a l'aide de procedures formelles de controle des changements.", + "Name": "Procedures de controle des changements de systeme", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "config", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de controle des changements apportes au systeme d'information du service. b) Le prestataire doit documenter et mettre en oeuvre une procedure de validation des changements apportes au systeme d'information du service sur un environnement de pre-production avant leur mise en production. c) Le prestataire doit conserver un historique des versions des logiciels et des systemes (developpements internes ou externes, produits commerciaux) mis en oeuvre pour permettre de reconstituer, le cas echeant dans un environnement de test, un environnement complet tel qu'il etait mis en oeuvre a une date donnee. La duree de conservation de cet historique doit etre en accord avec celle des sauvegardes (voir chapitre 12.5)." + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists", + "monitor_diagnostic_setting_with_appropriate_categories" + ] + }, + { + "Id": "14.3", + "Description": "Lorsque les plateformes d'exploitation sont modifiees, les applications critiques metier doivent etre revues et testees afin de verifier qu'il n'y a pas d'effet indesirable sur l'activite ou la securite du prestataire.", + "Name": "Revue technique des applications apres changement apporte a la plateforme d'exploitation", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester, prealablement a leur mise en production, l'ensemble des applications afin de verifier l'absence de tout effet indesirable sur l'activite ou sur la securite du service." + } + ], + "Checks": [] + }, + { + "Id": "14.4", + "Description": "Les environnements de developpement doivent etre securises et isoles des environnements de production.", + "Name": "Environnement de developpement securise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "organizations", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit mettre en oeuvre un environnement securise de developpement permettant de gerer l'integralite du cycle de developpement du systeme d'information du service. b) Le prestataire doit prendre en compte les environnements de developpement dans l'appreciation des risques et en assurer la protection conformement au present referentiel." + } + ], + "Checks": [] + }, + { + "Id": "14.5", + "Description": "Le prestataire doit superviser et surveiller l'activite de developpement externalise du systeme.", + "Name": "Developpement externalise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de superviser et de controler l'activite de developpement externalise des logiciels et des systemes. Cette procedure doit s'assurer que l'activite de developpement externalise soit conforme a la politique de developpement securise du prestataire et permette d'atteindre un niveau de securite du developpement externe equivalent a celui d'un developpement interne (voir exigence 14.1 a))." + } + ], + "Checks": [] + }, + { + "Id": "14.6", + "Description": "Des tests de securite et de conformite doivent etre effectues tout au long du cycle de developpement et apres chaque changement significatif.", + "Name": "Test de la securite et conformite du systeme", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "inspector", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit soumettre les systemes d'information, nouveaux ou mis a jour, a des tests de conformite et de fonctionnalite de securite pendant le developpement. Il doit documenter et mettre en oeuvre une procedure de test qui identifie : les taches a realiser ; les donnees d'entree ; les resultats attendus en sortie." + } + ], + "Checks": [ + "sqlserver_vulnerability_assessment_enabled", + "defender_container_images_scan_enabled" + ] + }, + { + "Id": "14.7", + "Description": "Les donnees de test doivent etre soigneusement selectionnees, protegees et controlees.", + "Name": "Protection des donnees de test", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'assurer l'integrite des donnees de tests utilises en pre-production. b) Si le prestataire souhaite utiliser des donnees du commanditaire issues de la production pour realiser des tests, le prestataire doit prealablement obtenir l'accord du commanditaire et les anonymiser. Le prestataire doit assurer la confidentialite des donnees lors de leur anonymisation." + } + ], + "Checks": [] + }, + { + "Id": "15.1", + "Description": "Le prestataire doit identifier les tiers ayant acces a l'information ou aux moyens de traitement de l'information et evaluer les risques associes.", + "Name": "Identification des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit tenir a jour une liste exhaustive des tiers participant a la mise en oeuvre du service (hebergeur, developpeur, integrateur, archiveur, sous-traitant operant sur site ou a distance, fournisseurs de climatisation, etc.). Cette liste doit preciser la contribution du tiers au service et au traitement des donnees a caractere personnel. Elle doit tenir compte des cas de sous-traitance a plusieurs niveaux. b) Le prestataire doit tenir a disposition du commanditaire la liste de l'ensemble des tiers qui peuvent acceder aux donnees et l'informer de tout changement de sous-traitants au sens de l'article 28 du [RGPD] afin que le commanditaire puisse emettre des objections a cet egard." + } + ], + "Checks": [] + }, + { + "Id": "15.2", + "Description": "Tous les aspects pertinents de la securite de l'information doivent etre traites dans les accords conclus avec les tiers.", + "Name": "La securite dans les accords conclus avec les tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit exiger des tiers participant a la mise en oeuvre du service, dans leur contribution au service, un niveau de securite au moins equivalent a celui qu'il s'engage a maintenir dans sa propre politique de securite. Il doit le faire au travers d'exigences, adaptees a chaque tiers et a sa contribution au service, dans les cahiers des charges ou dans les clauses de securite des accords de partenariat. Le prestataire doit inclure ces exigences dans les contrats conclus avec les tiers. b) Le prestataire doit contractualiser, avec chacun des tiers participant a la mise en oeuvre du service, des clauses d'audit permettant a un organisme de qualification de verifier que ces tiers respectent les exigences du present referentiel. c) Le prestataire doit definir et attribuer les roles et les responsabilites relatives a la modification ou a la fin du contrat le liant a un tiers participant a la mise en oeuvre du service." + } + ], + "Checks": [] + }, + { + "Id": "15.3", + "Description": "Le prestataire doit surveiller, revoir et auditer a intervalles reguliers la prestation des services des tiers.", + "Name": "Surveillance et revue des services des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de controler regulierement les mesures mises en place par les tiers participant a la mise en oeuvre du service pour respecter les exigences du present referentiel, conformement au chapitre 18.3." + } + ], + "Checks": [] + }, + { + "Id": "15.4", + "Description": "Les changements dans les services des tiers, incluant le maintien et l'amelioration des politiques, procedures et mesures existantes de securite de l'information, doivent etre geres.", + "Name": "Gestion des changements apportes dans les services des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de suivi des changements apportes par les tiers participant a la mise en oeuvre du service susceptibles d'affecter le niveau de securite du systeme d'information du service. b) Dans la mesure ou un changement de tiers participant a la mise en oeuvre du service affecte le niveau de securite du service, le prestataire doit en informer l'ensemble des commanditaires sans delais conformement au chapitre 12.2 et mettre en oeuvre les mesures permettant de retablir le niveau de securite precedent." + } + ], + "Checks": [] + }, + { + "Id": "15.5", + "Description": "Les personnes intervenant dans le cadre du service cloud doivent etre soumises a des engagements de confidentialite.", + "Name": "Engagements de confidentialite", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de reviser au moins annuellement les exigences en matiere d'engagements de confidentialite ou de non-divulgation vis-a-vis des tiers participant a la mise en oeuvre du service." + } + ], + "Checks": [] + }, + { + "Id": "16.1", + "Description": "Des responsabilites et des procedures de gestion doivent etre etablies pour garantir une reponse rapide, efficace et ordonnee aux incidents lies a la securite de l'information.", + "Name": "Responsabilites et procedures", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'apporter des reponses rapides et efficaces aux incidents de securite. Ces procedures doivent definir les moyens et delais de communication des incidents de securite a l'ensemble des commanditaires concernes ainsi que le niveau de confidentialite exige pour cette communication. b) Le prestataire doit informer ses employes et l'ensemble des tiers participant a la mise en oeuvre du service de cette procedure. c) Le prestataire doit documenter toute violation de donnees a caractere personnel et en informer son commanditaire. La violation doit etre notifiee a la CNIL si elle presente un risque pour les droits et libertes des personnes concernees. Elle doit faire l'objet d'une information aupres des personnes concernees lorsque le risque pour leur vie privee est eleve." + } + ], + "Checks": [] + }, + { + "Id": "16.2", + "Description": "Les evenements lies a la securite de l'information doivent etre signales dans les meilleurs delais par les voies hierarchiques appropriees. Des mecanismes de detection et de notification automatises doivent etre mis en oeuvre.", + "Name": "Signalements lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "guardduty", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure exigeant de ses employes et des tiers participant a la mise en oeuvre du service qu'ils lui rendent compte de tout incident de securite, avere ou suspecte ainsi que de toute faille de securite. b) Le prestataire doit documenter et mettre en oeuvre une procedure permettant a l'ensemble des commanditaires de signaler tout incident de securite, avere ou suspecte et toute faille de securite. c) Le prestataire doit communiquer sans delai aux commanditaires les incidents de securite et les preconisations associees pour en limiter les impacts. Il doit permettre au commanditaire de choisir les niveaux de gravite des incidents pour lesquels il souhaite etre informe. d) Le prestataire doit communiquer les incidents de securite aux autorites competentes conformement aux exigences legales et reglementaires en vigueur." + } + ], + "Checks": [ + "defender_ensure_defender_for_server_is_on", + "defender_ensure_notify_alerts_severity_is_high", + "defender_ensure_notify_emails_to_owners", + "defender_additional_email_configured_with_a_security_contact" + ] + }, + { + "Id": "16.3", + "Description": "Les evenements lies a la securite de l'information doivent etre apprecies et il doit etre decide s'il est necessaire de les classer comme incidents lies a la securite de l'information.", + "Name": "Appreciation des evenements et prise de decision", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "guardduty", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit apprecier les evenements lies a la securite de l'information et decider s'il faut les qualifier en incidents de securite. Pour l'appreciation, il doit s'appuyer sur une ou plusieurs echelles (estimation, evaluation, etc.) partagees avec le commanditaire. Note : Les incidents de securite incluent les violations de donnees a caractere personnel. b) Le prestataire doit utiliser une classification permettant d'identifier clairement les incidents de securite touchant des donnees relatives aux commanditaires, conformement aux resultats de l'appreciation des risques. Cette classification doit inclure les violations de donnees a caractere personnel." + } + ], + "Checks": [ + "defender_ensure_defender_for_server_is_on" + ] + }, + { + "Id": "16.4", + "Description": "Les incidents lies a la securite de l'information doivent etre traites conformement aux procedures documentees.", + "Name": "Reponse aux incidents lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit traiter les incidents de securite jusqu'a leur resolution et doit informer les commanditaires conformement aux procedures." + } + ], + "Checks": [] + }, + { + "Id": "16.5", + "Description": "Les connaissances acquises lors de l'analyse et du traitement des incidents lies a la securite de l'information doivent etre exploitees pour reduire la probabilite ou l'impact d'incidents futurs.", + "Name": "Tirer des enseignements des incidents lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus d'amelioration continue afin de diminuer l'occurrence et l'impact de types d'incidents de securite deja traites." + } + ], + "Checks": [] + }, + { + "Id": "16.6", + "Description": "Le prestataire doit definir et appliquer des procedures pour l'identification, le recueil, l'acquisition et la preservation de preuves. Les journaux d'audit doivent etre proteges et valides.", + "Name": "Recueil de preuves", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "cloudtrail", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'enregistrer les informations relatives aux incidents de securite et pouvant servir d'elements de preuve." + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists", + "monitor_diagnostic_setting_with_appropriate_categories" + ] + }, + { + "Id": "17.1", + "Description": "Le prestataire doit determiner ses exigences en matiere de securite de l'information et de continuite du management de la securite de l'information dans des situations defavorables, par exemple lors d'une crise ou d'un sinistre.", + "Name": "Organisation de la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre oeuvre un plan de continuite d'activite prenant en compte la securite de l'information. b) Le prestataire doit reviser annuellement le plan de continuite d'activite du service et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "17.2", + "Description": "Le prestataire doit etablir, documenter, mettre en oeuvre et maintenir des processus, des procedures et des mesures de controle pour assurer le niveau requis de continuite de la securite de l'information au cours d'une situation defavorable. Les services doivent etre deployes en multi-AZ.", + "Name": "Mise en oeuvre de la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "rds", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des procedures permettant de maintenir ou de restaurer l'exploitation du service et d'assurer la disponibilite des informations au niveau et dans les delais pour lesquels le prestataire s'est engage vis-a-vis du commanditaire dans la convention de service." + } + ], + "Checks": [ + "storage_geo_redundant_enabled", + "vm_scaleset_associated_with_load_balancer" + ] + }, + { + "Id": "17.3", + "Description": "Le prestataire doit verifier a intervalles reguliers les mesures de continuite de la securite de l'information mises en oeuvre afin de s'assurer qu'elles sont valables et efficaces dans des situations defavorables.", + "Name": "Verifier, revoir et evaluer la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester le plan de continuite d'activites afin de s'assurer qu'il est pertinent et efficace en situation de crise." + } + ], + "Checks": [] + }, + { + "Id": "17.4", + "Description": "Les moyens de traitement de l'information doivent etre mis en oeuvre avec suffisamment de redondance pour repondre aux exigences de disponibilite. Les mecanismes de protection contre la suppression accidentelle doivent etre actives.", + "Name": "Disponibilite des moyens de traitement de l'information", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "rds", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures qui lui permettent de repondre au besoin de disponibilite du service defini dans la convention de service (voir chapitre 19.1)." + } + ], + "Checks": [ + "storage_geo_redundant_enabled", + "keyvault_recoverable" + ] + }, + { + "Id": "17.5", + "Description": "La configuration de l'infrastructure technique du service cloud doit etre sauvegardee regulierement afin de permettre sa restauration en cas de sinistre.", + "Name": "Sauvegarde de la configuration de l'infrastructure technique", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "backup", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de sauvegarde hors-ligne de la configuration de l'infrastructure technique." + } + ], + "Checks": [ + "policy_ensure_asc_enforcement_enabled", + "vm_backup_enabled" + ] + }, + { + "Id": "17.6", + "Description": "Le prestataire doit mettre a disposition du commanditaire un dispositif de sauvegarde de ses donnees, permettant la restauration en cas de sinistre.", + "Name": "Mise a disposition d'un dispositif de sauvegarde des donnees du commanditaire", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "backup", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre a disposition du commanditaire un service de sauvegarde de ses donnees." + } + ], + "Checks": [ + "vm_backup_enabled", + "storage_ensure_soft_delete_is_enabled", + "storage_blob_versioning_is_enabled" + ] + }, + { + "Id": "18.1", + "Description": "Toutes les exigences legales, reglementaires et contractuelles en vigueur, ainsi que l'approche du prestataire pour satisfaire ces exigences, doivent etre explicitement definies, documentees et tenues a jour pour chaque systeme d'information et pour le prestataire.", + "Name": "Identification de la legislation et des exigences contractuelles applicables", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les exigences legales, reglementaires et contractuelles en vigueur applicables au service. En France, le prestataire doit considerer au minimum les textes suivants : les donnees a caractere personnel [LOI_IL], [RGPD] ; le secret professionnel [CP_ART_226_13], le cas echeant sans prejudice de l'application de l'article 40 alinea 2 du Code de procedure penale relatif au signalement a une autorite judiciaire ; l'abus de confiance [CP_ART_314-1] ; le secret des correspondances privees [CP_ART_226-15] ; l'atteinte a la vie privee [CP_ART_226-1] ; l'acces ou le maintien frauduleux a un systeme d'information [CP_ART_323-1]. b) Le prestataire doit, selon son role dans les traitements de donnees a caractere personnel (responsable de traitement, sous-traitant ou co-responsable) justifier et documenter les choix de mesures techniques et organisationnelles realises en vue de repondre aux exigences de protection des donnees a caractere personnel du present referentiel (voir partie 19.5). c) Le prestataire doit documenter et mettre en oeuvre les procedures permettant de respecter les exigences legales, reglementaires et contractuelles en vigueur applicables au service, ainsi que les besoins de securite specifiques (voir exigence 8.3b)). d) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible l'ensemble de ces procedures. e) Le prestataire doit documenter et mettre en oeuvre un processus de veille actif des exigences legales, reglementaires et contractuelles en vigueur applicables au service." + } + ], + "Checks": [] + }, + { + "Id": "18.2", + "Description": "L'approche du prestataire vis-a-vis de la gestion de la securite de l'information et sa mise en oeuvre (c'est-a-dire les objectifs de controle, les mesures, les politiques, les procedures et les processus relatifs a la securite de l'information) doivent etre revues de maniere independante a intervalles definis ou en cas de changement significatif.", + "Name": "Revue independante de la securite de l'information", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un programme d'audit sur trois ans definissant le perimetre et la frequence des audits en accord avec la gestion du changement, les politiques, et les resultats de l'appreciation des risques. Le prestataire doit inclure dans le programme d'audit un audit qualifie par an realise par un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie. L'ensemble du programme d'audit doit notamment couvrir : l'audit de la configuration de l'infrastructure technique du service (par echantillonnage et doit inclure tous types d'equipements et de serveurs presents dans le systeme d'information du service) ; le test d'intrusion des interfaces d'administration exposees sur un reseau public ; le test d'intrusion de l'interface utilisateur pour les services SaaS ; si le service beneficie de developpements internes, l'audit de code source portant sur les fonctionnalites de securite implementees (l'approche en continue doit etre privilegiee). b) Il est recommande que le prestataire mette en oeuvre des mecanismes automatises d'audit de la configuration adaptes a l'infrastructure technique du service." + } + ], + "Checks": [] + }, + { + "Id": "18.3", + "Description": "Les responsables doivent regulierement s'assurer de la conformite du traitement de l'information et des procedures au sein de leur domaine de responsabilite, au regard des politiques et des normes de securite.", + "Name": "Conformite avec les politiques et les normes de securite", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "securityhub", + "Type": "Partially Automated", + "Comment": "a) Le prestataire via le responsable de la securite de l'information doit s'assurer regulierement de l'execution correcte de l'ensemble des procedures de securite placees sous sa responsabilite en vue de garantir leur conformite avec les politiques et normes de securite." + } + ], + "Checks": [ + "policy_ensure_asc_enforcement_enabled", + "defender_ensure_mcas_is_enabled" + ] + }, + { + "Id": "18.4", + "Description": "Les systemes d'information doivent etre examines regulierement quant a leur conformite avec les politiques et les normes de securite de l'information du prestataire.", + "Name": "Examen de la conformite technique", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "securityhub", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique permettant de verifier la conformite technique du service aux exigences du present referentiel. Cette politique doit definir les objectifs, methodes, frequences, resultats attendus et mesures correctrices." + } + ], + "Checks": [ + "sqlserver_vulnerability_assessment_enabled", + "defender_ensure_defender_for_server_is_on" + ] + }, + { + "Id": "19.1", + "Description": "Le prestataire doit etablir une convention de service avec le commanditaire definissant les engagements de niveau de service, les responsabilites et les conditions d'utilisation du service cloud.", + "Name": "Convention de service", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit etablir une convention de service avec chacun des commanditaires du service. Toute modification de la convention de service doit etre soumise a acceptation du commanditaire. b) Le prestataire doit identifier dans la convention de service : les obligations, droits et responsabilites de chacune des parties : prestataire et tiers impliques dans la fourniture du service, commanditaires, etc. ; les elements explicitement exclus des responsabilites du prestataire dans la limite de ce que prevoient les exigences legales et reglementaires en vigueur, notamment l'article 28 du [RGPD] ; la localisation du service. La localisation du support doit etre precisee lorsqu'il est realise depuis un Etat hors l'Union Europeenne, comme le permet l'exigence 19.2.e. c) Le prestataire doit proposer une convention de service appliquant le droit d'un Etat membre de l'Union Europeenne. Le droit applicable doit etre identifie dans la convention de service. d) La convention de service doit indiquer que la collecte, la manipulation, le stockage, et plus generalement le traitement des donnees faits dans le cadre de l'avant-vente, de la mise en oeuvre, de la maintenance et l'arret du service sont realises conformement aux exigences edictees par la legislation en vigueur. e) La convention de service doit indiquer que le prestataire doit mettre a la disposition du commanditaire, sur demande de celui-ci, les elements d'appreciation des risques lies a la soumission des donnees du commanditaire au droit d'un etat non-membre de l'Union Europeenne (voir 5.3.e). f) Le prestataire doit decrire dans la convention de service les moyens techniques et organisationnels qu'il met en oeuvre pour assurer le respect du droit applicable. g) Le prestataire doit inclure dans la convention de service une clause de revision de la convention prevoyant notamment une resiliation sans penalite pour le commanditaire en cas de perte de la qualification octroyee au service. h) Le prestataire doit inclure dans la convention de service une clause de reversibilite permettant au commanditaire de recuperer l'ensemble de ses donnees (fournies directement par le commanditaire ou produites dans le cadre du service a partir des donnees ou des actions du commanditaire). i) Le prestataire doit assurer cette reversibilite via l'une des modalites techniques suivantes : la mise a disposition de fichiers suivant un ou plusieurs formats documentes et exploitables en dehors du service fourni par le prestataire ; la mise en place d'interfaces techniques permettant l'acces aux donnees suivant un schema documente et exploitable (API, format pivot, etc.). Les modalites techniques de la reversibilite figurent dans la convention de service. j) Le prestataire doit indiquer dans la convention de service le niveau de disponibilite du service. k) Le prestataire doit indiquer dans la convention de service qu'il ne peut disposer des donnees transmises et generees par le commanditaire, leur disposition etant reservee au commanditaire. l) Le prestataire doit indiquer dans la convention de service qu'il ne divulgue aucune information relative a la prestation a des tiers, sauf autorisation formelle et ecrite du commanditaire. m) Le prestataire doit indiquer dans la convention de service si les donnees du commanditaire sont automatiquement sauvegardees ou non. Dans la negative, le prestataire doit sensibiliser le commanditaire aux risques encourus et clairement indiquer les operations a mener par le commanditaire pour que ses donnees soient sauvegardees. n) Le prestataire doit indiquer dans la convention de service s'il autorise l'acces distant pour des actions d'administration ou de support au systeme d'information du service. o) Le prestataire doit preciser dans la convention de service que : le service est qualifie et inclure l'attestation de qualification ; le commanditaire peut deposer une reclamation relative au service qualifie aupres de l'ANSSI ; le commanditaire autorise l'ANSSI et l'organisme de qualification a auditer le service et son systeme d'information du service afin de verifier qu'ils respectent les exigences du present referentiel. p) Le prestataire doit preciser dans la convention de service que le commanditaire autorise, conformement au present referentiel (voir chapitre 18.2, un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie mandate par le prestataire a auditer le service et son systeme d'information dans le cadre du plan de controle. q) Le prestataire doit preciser dans la convention de service qu'il s'engage a mettre a disposition toutes les informations necessaires a la realisation d'audits de conformite aux dispositions de l'article 28 du [RGPD], menes par le commanditaire ou un tiers mandate. r) Il est recommande que le tiers mandate pour les audits soit un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie." + } + ], + "Checks": [] + }, + { + "Id": "19.2", + "Description": "Les donnees du commanditaire doivent etre stockees et traitees dans des centres de donnees situes sur le territoire de l'Union europeenne. Les politiques de restriction de region doivent etre appliquees.", + "Name": "Localisation des donnees", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "organizations", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et communiquer au commanditaire la localisation du stockage et du traitement des donnees de ce dernier. b) Le prestataire doit stocker et traiter les donnees du commanditaire au sein de l'Union Europeenne. c) Les operations d'administration et de supervision du service doivent etre realisees depuis le territoire de l'Union Europeenne. d) Le prestataire doit stocker et traiter les donnees techniques (identites des beneficiaires et des administrateurs de l'infrastructure technique, donnees manipulees par le Software Defined Network, journaux de l'infrastructure technique, annuaire, certificats, configuration des acces, etc.) au sein de l'Union Europeenne. e) Le prestataire peut realiser des operations de support aux commanditaires depuis un Etat hors de l'Union Europeenne. Il doit documenter la liste des operations qui peuvent etre effectuees par le support au commanditaire depuis un Etat hors de l'Union Europeenne, et les mecanismes permettant d'en assurer le controle d'acces et la supervision depuis l'Union Europeenne." + } + ], + "Checks": [] + }, + { + "Id": "19.3", + "Description": "Les services cloud qualifies SecNumCloud doivent etre operes depuis le territoire de l'Union europeenne.", + "Name": "Regionalisation", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit s'assurer que les interfaces du service accessibles au commanditaire soient au moins disponibles en langue francaise. b) Le prestataire doit fournir un support de premier niveau en langue francaise." + } + ], + "Checks": [] + }, + { + "Id": "19.4", + "Description": "Le prestataire doit definir les conditions de fin de contrat, incluant les modalites de restitution et de suppression des donnees du commanditaire.", + "Name": "Fin de contrat", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) A la fin du contrat liant le prestataire et le commanditaire, que le contrat soit arrive a son terme ou pour toute autre cause, le prestataire doit assurer un effacement securise de l'integralite des donnees du commanditaire. Cet effacement doit faire l'objet d'un preavis formel au commanditaire de la part du prestataire respectant un delai de vingt et un jours calendaires. L'effacement peut etre realise suivant l'une des methodes suivantes, et ce dans un delai precise dans la convention de service : effacement par reecriture complete de tout support ayant heberge ces donnees ; effacement des cles utilisees pour le chiffrement des espaces de stockage du commanditaire decrit au chapitre 10.1 ; recyclage securise, dans les conditions enoncees au chapitre 11.9. b) A la fin du contrat, le prestataire doit supprimer les donnees techniques relatives au commanditaire (annuaire, certificats, configuration des acces, etc.)." + } + ], + "Checks": [] + }, + { + "Id": "19.5", + "Description": "Le prestataire doit mettre en oeuvre des mesures techniques et organisationnelles appropriees pour garantir la protection des donnees a caractere personnel conformement a la reglementation en vigueur.", + "Name": "Protection des donnees a caractere personnel", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit justifier du respect des principes de protection des donnees pour les traitements de donnees a caractere personnel mis en oeuvre pour son propre compte. Il doit justifier au minimum les points suivants : les finalites des traitements determinees, explicites et legitimes ; la tracabilite des activites de traitement pour son compte et celui de son commanditaire ; le fondement licite des traitements ; l'interdiction du detournement de finalite des traitements ; les donnees utilisees respectent le principe du minimum necessaire et suffisant pour les traitements ; ainsi sont adequates, pertinentes et limitees ; la qualite des donnees utilisees pour les traitements maintenue : donnees exactes et tenues a jour ; les durees de conservation definies et limitees. b) Le prestataire doit justifier, pour les traitements de donnees a caractere personnel mis en oeuvre pour son propre compte, du respect des droits des personnes concernees. Il doit justifier au minimum les points suivants : l'information des usagers via un traitement loyal et transparent ; le recueil du consentement des usagers : expres, demontrable et retirable ; la possibilite pour les usagers d'exercer les droits d'acces, de rectification et d'effacement ; la possibilite pour les usagers d'exercer les droits de limitation du traitement, de portabilite et d'opposition. c) Lorsqu'il agit en qualite de sous-traitant au sens de l'article 28 de [RGPD], le prestataire doit apporter assistance et conseil au commanditaire en l'informant si une instruction de ce dernier constitue une violation des regles de protection des donnees." + } + ], + "Checks": [] + }, + { + "Id": "19.6", + "Description": "Le prestataire doit mettre en oeuvre des mesures de protection vis-a-vis du droit extra-europeen, afin de garantir que les donnees du commanditaire ne puissent etre soumises a des legislations extra-europeennes.", + "Name": "Protection vis-a-vis du droit extra-europeen", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le siege statutaire, administration centrale et principal etablissement du prestataire doivent etre etablis au sein d'un Etat membre de l'Union Europeenne. b) Le capital social et les droits de vote dans la societe du prestataire ne doivent pas etre, directement ou indirectement : individuellement detenus a plus de 24% ; et collectivement detenus a plus de 39% ; par des entites tierces possedant leur siege statutaire, administration centrale ou principal etablissement au sein d'un Etat non membre de l'Union europeenne. Ces entites tierces susmentionnees ne peuvent pas individuellement ou collectivement : en vertu d'un contrat ou de clauses statutaires, disposer d'un droit de veto ; en vertu d'un contrat ou de clauses statutaires, designer la majorite des membres des organes d'administration, de direction ou de surveillance du prestataire. c) En cas de recours par le prestataire, dans le cadre des services fournis au commanditaire, aux services d'une societe tierce - y compris un sous-traitant - possedant son siege statutaire, administration centrale ou principal etablissement au sein d'un Etat non membre de l'Union Europeenne ou appartenant ou etant controlee par une societe tierce domiciliee en dehors l'Union Europeenne, cette susdite societe tierce ne doit pas avoir la possibilite technique d'obtenir les donnees operees au travers du service. d) Dans le cadre de l'exigence 19.6.c, toute societe tierce a laquelle le prestataire recourt pour fournir tout ou partie du service rendu au commanditaire, doit garantir au prestataire une autonomie d'exploitation continue dans la fourniture des services d'informatique en nuage qu'il opere ou doit etre qualifie SecNumCloud. e) Le service fourni par le prestataire doit respecter la legislation en vigueur en matiere de droits fondamentaux et les valeurs de l'Union relatives au respect de la dignite humaine, a la liberte, a l'egalite, a la democratie et a l'Etat de droit. f) Le prestataire doit informer formellement le commanditaire, et dans un delai d'un mois, de tout changement juridique, organisationnel ou technique pouvant avoir un impact sur la conformite de la prestation aux exigences du chapitre 19.6." + } + ], + "Checks": [] + } + ] +} From 907664093ff2c3f5c5e7dab4e6b28e0edb26833b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 18 Mar 2026 08:38:06 +0100 Subject: [PATCH 024/366] feat(compliance): add SecNumCloud 3.2 for GCP (#10364) --- dashboard/compliance/secnumcloud_3_2_gcp.py | 24 + prowler/CHANGELOG.md | 1 + .../compliance/gcp/secnumcloud_3.2_gcp.json | 1460 +++++++++++++++++ 3 files changed, 1485 insertions(+) create mode 100644 dashboard/compliance/secnumcloud_3_2_gcp.py create mode 100644 prowler/compliance/gcp/secnumcloud_3.2_gcp.json diff --git a/dashboard/compliance/secnumcloud_3_2_gcp.py b/dashboard/compliance/secnumcloud_3_2_gcp.py new file mode 100644 index 0000000000..2d5517aed6 --- /dev/null +++ b/dashboard/compliance/secnumcloud_3_2_gcp.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_format3 + +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_format3( + aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID" + ) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 67a4f27197..d84169771f 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to the **Prowler SDK** are documented in this file. -`entra_conditional_access_policy_require_mfa_for_admin_portals` check for Azure provider and update CIS compliance [(#10330)](https://github.com/prowler-cloud/prowler/pull/10330) - CheckMetadata Pydantic validators [(#8584)](https://github.com/prowler-cloud/prowler/pull/8583) - `organization_repository_deletion_limited` check for GitHub provider [(#10185)](https://github.com/prowler-cloud/prowler/pull/10185) +- SecNumCloud 3.2 compliance framework for the GCP provider [(#10364)](https://github.com/prowler-cloud/prowler/pull/10364) - SecNumCloud 3.2 for the Azure provider [(#10358)](https://github.com/prowler-cloud/prowler/pull/10358) ### 🔄 Changed diff --git a/prowler/compliance/gcp/secnumcloud_3.2_gcp.json b/prowler/compliance/gcp/secnumcloud_3.2_gcp.json new file mode 100644 index 0000000000..e709545da4 --- /dev/null +++ b/prowler/compliance/gcp/secnumcloud_3.2_gcp.json @@ -0,0 +1,1460 @@ +{ + "Framework": "SecNumCloud", + "Name": "SecNumCloud Referentiel d'Exigences v3.2", + "Version": "3.2", + "Provider": "GCP", + "Description": "The SecNumCloud framework is published by ANSSI (Agence Nationale de la Securite des Systemes d'Information) to qualify cloud service providers operating in France. Version 3.2, dated March 8, 2022, covers IaaS, CaaS, PaaS, and SaaS services with requirements spanning information security policies, access control, cryptography, physical security, operational security, communications security, and data sovereignty protections against extra-European law.", + "Requirements": [ + { + "Id": "5.1", + "Description": "Le prestataire doit definir et appliquer des principes de securite de l'information adaptes a ses activites de fourniture de services cloud.", + "Name": "Principes", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit operer la prestation a l'etat de l'art pour le type d'activite retenu : utiliser des logiciels stables beneficiant d'un suivi des correctifs de securite et parametres de facon a obtenir un niveau de securite optimal. b) Le prestataire doit appliquer le guide d'hygiene informatique de l'ANSSI [HYGIENE], niveau renforce, au systeme d'information du service." + } + ], + "Checks": [] + }, + { + "Id": "5.2", + "Description": "Le prestataire doit definir, faire approuver par la direction, publier et communiquer aux salaries et aux tiers concernes un ensemble de politiques de securite de l'information.", + "Name": "Politique de securite de l'information", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de securite de l'information relative au service. b) La politique de securite de l'information doit identifier les engagements du prestataire quant au respect de la legislation et reglementation nationale en vigueur selon la nature des informations qui pourraient etre confiees par le commanditaire au prestataire ; il revient en revanche in fine au commanditaire de s'assurer du respect des contraintes legales et reglementaires applicables aux donnees qu'il confie effectivement au prestataire. c) La politique de securite de l'information doit notamment couvrir les themes abordes aux chapitres 6 a 19 du present referentiel. d) La direction du prestataire doit approuver formellement la politique de securite de l'information. e) Le prestataire doit reviser annuellement la politique de securite de l'information et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "5.3", + "Description": "Le prestataire doit definir et appliquer un processus d'appreciation des risques de securite de l'information.", + "Name": "Appreciation des risques", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter une appreciation des risques couvrant l'ensemble du perimetre du service. b) Le prestataire doit realiser son appreciation de risques en utilisant une methode documentee garantissant la reproductibilite et comparabilite de la demarche. c) Le prestataire doit prendre en compte dans l'appreciation des risques : la gestion d'informations du commanditaire ayant des besoins de securite differents ; les risques ayant des impacts sur les droits et libertes des personnes concernees en cas d'acces non autorise, de modification non desiree et de disparition de donnees a caractere personnel ; les risques de defaillance des mecanismes de cloisonnement des ressources de l'infrastructure technique (memoire, calcul, stockage, reseau) partagees entre les commanditaires ; les risques lies a l'effacement incomplet ou non securise des donnees stockees sur les espaces de memoire ou de stockage partages entre commanditaires, en particulier lors des reallocations des espaces de memoire et de stockage ; les risques lies a l'exposition des interfaces d'administration sur un reseau public ; les risques d'atteinte a la confidentialite des donnees des commanditaires par des tiers impliques dans la fourniture du service (fournisseurs, sous-traitants, etc.) ; les risques lies aux evenements naturels et sinistres physiques ; les risques lies a la separation des taches (voir 6.2.a) ; les risques lies aux environnements de developpement (voir 14.4.b). d) Le prestataire doit lister, dans un document specifique, les risques residuels lies a l'existence de lois extra-europeennes ayant pour objectif la collecte de donnees ou metadonnees des commanditaires sans leur consentement prealable. e) Le prestataire doit mettre a la disposition du commanditaire, sur demande de celui-ci, les elements d'appreciation des risques lies a la soumission des donnees du commanditaire au droit d'un etat non-membre de l'Union Europeenne. f) Lorsqu'il existe des exigences legales, reglementaires ou sectorielles specifiques liees aux types d'informations confiees par le commanditaire au prestataire, ce dernier doit les prendre en compte dans son appreciation des risques en s'assurant de respecter l'ensemble des exigences du present referentiel d'une part et de ne pas abaisser le niveau de securite etabli par le respect des exigences du present referentiel d'autre part. g) La direction du prestataire doit accepter formellement les risques residuels identifies dans l'appreciation des risques. h) Le prestataire doit reviser annuellement l'appreciation des risques et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "6.1", + "Description": "Le prestataire doit definir et attribuer toutes les responsabilites en matiere de securite de l'information.", + "Name": "Fonctions et responsabilites liees a la securite de l'information", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une organisation interne de la securite pour assurer la definition, la mise en place et le suivi du fonctionnement operationnel de la securite de l'information au sein de son organisation. b) Le prestataire doit designer un responsable de la securite des systemes d'information et un responsable de la securite physique. c) Le prestataire doit definir et attribuer les responsabilites en matiere de securite de l'information pour le personnel implique dans la fourniture du service. d) Le prestataire doit s'assurer apres tout changement majeur pouvant avoir un impact sur le service que l'attribution des responsabilites en matiere de securite de l'information est toujours pertinente. e) Le prestataire doit definir et attribuer les responsabilites en matiere de protection de donnees a caractere personnel, en coherence avec son role dans les traitements de donnees a caractere personnel (responsable de traitement, sous-traitant ou co-responsable). f) Le prestataire doit, lorsqu'il traite un grand nombre de donnees parmi lesquelles figurent des categories particulieres de donnees a caractere personnel telles que definies dans [RGPD], designer un delegue a la protection des donnees. g) Il est recommande que le prestataire, quel que soit le volume de donnees a caractere personnel qu'il traite, designe un delegue a la protection des donnees. h) Le prestataire doit realiser ou contribuer a la realisation d'une analyse d'impact relative a la protection des donnees a caractere personnel lorsque le traitement est susceptible d'engendrer un risque eleve pour les droits et libertes des personnes concernees (traitement de categories particulieres de donnees a caractere personnel telles que definies dans [RGPD], traitement de donnees a grande echelle, etc.). Cette analyse doit comporter une evaluation juridique du respect des principes et droits fondamentaux, ainsi qu'une etude plus technique des mesures techniques mises en oeuvre pour proteger les personnes des risques pour leur vie privee." + } + ], + "Checks": [] + }, + { + "Id": "6.2", + "Description": "Le prestataire doit separer les taches et les domaines de responsabilite incompatibles afin de reduire les possibilites de modification non autorisee ou de mauvais usage des actifs.", + "Name": "Separation des taches", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les risques associes a des cumuls de responsabilites ou de taches, les prendre en compte dans l'appreciation des risques et mettre en oeuvre des mesures de reduction de ces risques." + } + ], + "Checks": [] + }, + { + "Id": "6.3", + "Description": "Le prestataire doit etablir et maintenir des relations appropriees avec les autorites competentes.", + "Name": "Relations avec les autorites", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire mette en place des relations appropriees avec les autorites competentes en matiere de securite de l'information et de donnees a caractere personnel et, le cas echeant, avec les autorites sectorielles selon la nature des informations confiees par le commanditaire au prestataire." + } + ], + "Checks": [] + }, + { + "Id": "6.4", + "Description": "Le prestataire doit etablir et maintenir des relations appropriees avec des groupes de travail specialises, des associations professionnelles ou des forums traitant de la securite.", + "Name": "Relations avec les groupes de travail specialises", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire entretienne des contacts appropries avec des groupes de specialistes ou des sources reconnues, notamment pour prendre en compte de nouvelles menaces et les mesures de securite appropriees pour les contrer." + } + ], + "Checks": [] + }, + { + "Id": "6.5", + "Description": "Le prestataire doit integrer la securite de l'information dans la gestion de projet, quel que soit le type de projet.", + "Name": "La securite de l'information dans la gestion de projet", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter une estimation des risques prealablement a tout projet pouvant avoir un impact sur le service, et ce quelle que soit la nature du projet. b) Dans la mesure ou un projet affecte ou est susceptible d'affecter le niveau de securite du service, le prestataire doit avertir le commanditaire et l'informer par ecrit des impacts potentiels, des mesures mises en place pour reduire ces impacts ainsi que des risques residuels le concernant." + } + ], + "Checks": [] + }, + { + "Id": "7.1", + "Description": "Le prestataire doit s'assurer que les candidats a l'embauche font l'objet de verifications proportionnees aux exigences metier, a la classification des informations accessibles et aux risques identifies.", + "Name": "Selection des candidats", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de verification des informations concernant son personnel conforme aux lois et reglements en vigueur. Ces verifications s'appliquent a toute personne impliquee dans la fourniture du service et doivent etre proportionnelles a la sensibilite ou a la specificite des informations du commanditaire confiees au prestataire ainsi qu'aux risques identifies. b) Pour les personnels disposant de privileges d'administration eleves sur les composants logiciels et materiels de l'infrastructure, le prestataire doit renforcer les verifications destinees a verifier que les antecedents de ceux-ci ne sont pas incompatibles avec l'exercice de leurs fonctions. Il est entendu par des privileges d'administration eleves, des actions permettant l'elevation de privileges ou la possibilite de realiser des actions sans traces techniques ou de desactiver, alterer les traces techniques." + } + ], + "Checks": [] + }, + { + "Id": "7.2", + "Description": "Les accords contractuels avec les salaries et les sous-traitants doivent preciser leurs responsabilites et celles du prestataire en matiere de securite de l'information.", + "Name": "Conditions d'embauche", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit disposer d'une charte d'ethique integree au reglement interieur, prevoyant notamment que : les prestations sont realisees avec loyaute, discretion et impartialite et dans des conditions de confidentialite des informations traitees ; les personnels ne recourent qu'aux methodes, outils et techniques valides par le prestataire ; les personnels s'engagent a ne pas divulguer d'informations a un tiers, meme anonymisees et decontextualisees, obtenues ou generees dans le cadre de la prestation sauf autorisation formelle et ecrite du commanditaire ; les personnels s'engagent a signaler au prestataire tout contenu manifestement illicite decouvert pendant la prestation ; les personnels s'engagent a respecter la legislation et la reglementation nationale en vigueur et les bonnes pratiques liees a leurs activites. b) Le prestataire doit faire signer la charte d'ethique a l'ensemble des personnes impliquees dans la fourniture du service. c) Le prestataire doit introduire, dans le contrat de travail des personnels disposant de privileges d'administration eleves sur les composants et materiels de l'infrastructure du service, un engagement de responsabilite avec un renvoi aux clauses du code du travail sur la protection du secret des affaires et de la propriete intellectuelle. Il est entendu par des privileges d'administration eleves, des actions permettant l'elevation de privileges ou la possibilite de realiser des actions sans traces techniques ou de desactiver, alterer les traces techniques. d) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible le reglement interieur et la charte d'ethique." + } + ], + "Checks": [] + }, + { + "Id": "7.3", + "Description": "Les salaries du prestataire et, le cas echeant, les sous-traitants doivent suivre un programme de sensibilisation et de formation adapte et regulier concernant la securite de l'information.", + "Name": "Sensibilisation, apprentissage et formations a la securite de l'information", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit sensibiliser a la securite de l'information et aux risques lies a la protection des donnees l'ensemble des personnes impliquees dans la fourniture du service. Il doit leur communiquer les mises a jour des politiques et procedures pertinentes dans le cadre de leurs missions. b) Le prestataire doit documenter et mettre en oeuvre un plan de formation concernant la securite de l'information adapte au service et aux missions des personnels. c) Le responsable de la securite des systemes d'information du prestataire doit valider formellement le plan de formation concernant la securite de l'information." + } + ], + "Checks": [] + }, + { + "Id": "7.4", + "Description": "Le prestataire doit mettre en place un processus disciplinaire formel et communique pour prendre des mesures a l'encontre des salaries ayant enfreint les regles de securite de l'information.", + "Name": "Processus disciplinaire", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus disciplinaire applicable a l'ensemble des personnes impliquees dans la fourniture du service ayant enfreint la politique de securite. b) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible les sanctions encourues en cas d'infraction a la politique de securite." + } + ], + "Checks": [] + }, + { + "Id": "7.5", + "Description": "Les responsabilites et les obligations en matiere de securite de l'information qui restent valables apres un changement ou une rupture du contrat de travail doivent etre definies, communiquees au salarie ou au sous-traitant et appliquees.", + "Name": "Rupture, terme ou modification du contrat de travail", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit definir et attribuer les roles et les responsabilites relatives a la rupture, au terme ou a la modification de tout contrat avec une personne impliquee dans la fourniture du service." + } + ], + "Checks": [] + }, + { + "Id": "8.1", + "Description": "Le prestataire doit identifier les actifs associes a l'information et aux moyens de traitement de l'information et doit etablir et tenir a jour un inventaire de ces actifs.", + "Name": "Inventaire et propriete des actifs", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "iam", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit tenir a jour l'inventaire de l'ensemble des equipements mettant en oeuvre le service. Cet inventaire doit preciser pour chaque equipement : les informations d'identification de l'equipement (noms, adresses IP, adresses MAC, etc.) ; la fonction de l'equipement ; le modele de l'equipement ; la localisation de l'equipement ; le proprietaire de l'equipement ; le besoin de securite des informations (au sens du chapitre 8.3). b) Le prestataire doit tenir a jour l'inventaire de l'ensemble des logiciels mettant en oeuvre le service. Cet inventaire doit identifier pour chaque logiciel, sa version et les equipements sur lesquels le logiciel est installe. c) Le prestataire doit s'assurer de la validite des licences des logiciels tout au long de la prestation." + } + ], + "Checks": [ + "iam_cloud_asset_inventory_enabled" + ] + }, + { + "Id": "8.2", + "Description": "Les salaries et les utilisateurs de tiers doivent restituer tous les actifs du prestataire en leur possession au terme de la periode d'emploi, du contrat ou de l'accord.", + "Name": "Restitution des actifs", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de restitution des actifs permettant de s'assurer que chaque personne impliquee dans la fourniture du service restitue l'ensemble des actifs en sa possession a la fin de sa periode d'emploi ou de son contrat." + } + ], + "Checks": [] + }, + { + "Id": "8.3", + "Description": "Les besoins de protection de la confidentialite, de l'integrite et de la disponibilite de l'information doivent etre identifies.", + "Name": "Identification des besoins de securite de l'information", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les differents besoins de securite des informations relatives au service. b) Lorsque le commanditaire confie au prestataire des donnees soumises a des contraintes legales, reglementaires ou sectorielles specifiques, le prestataire doit identifier les besoins de securite specifiques associes a ces contraintes." + } + ], + "Checks": [] + }, + { + "Id": "8.4", + "Description": "Un ensemble de procedures appropriees pour le marquage et la manipulation de l'information doit etre elabore et mis en oeuvre conformement au plan de classification adopte par le prestataire.", + "Name": "Marquage et manipulation de l'information", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire documente et mette en oeuvre une procedure pour le marquage et la manipulation de toutes les informations participant a la delivrance du service, conformement a son besoin de securite defini au chapitre 8.3." + } + ], + "Checks": [] + }, + { + "Id": "8.5", + "Description": "Des procedures de gestion des supports amovibles doivent etre mises en oeuvre conformement au plan de classification adopte par le prestataire.", + "Name": "Gestion des supports amovibles", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure pour la gestion des supports amovibles, conformement au besoin de securite defini au chapitre 8.3. Lorsque des supports amovibles sont utilises sur l'infrastructure technique ou pour des taches d'administration, ces supports doivent etre dedies a un usage." + } + ], + "Checks": [] + }, + { + "Id": "9.1", + "Description": "Une politique de controle d'acces doit etre etablie, documentee et revue en se basant sur les exigences metier et les exigences de securite de l'information. Les regles de controle d'acces et les droits pour chaque utilisateur ou groupe d'utilisateurs doivent etre clairement definis.", + "Name": "Politiques et controle d'acces", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de controle d'acces sur la base du resultat de son appreciation des risques et du partage des responsabilites. b) Le prestataire doit reviser annuellement la politique de controle d'acces et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [ + "iam_sa_no_administrative_privileges", + "iam_no_service_roles_at_project_level", + "iam_role_sa_enforce_separation_of_duties" + ] + }, + { + "Id": "9.2", + "Description": "Un processus formel d'enregistrement et de desinscription des utilisateurs doit etre mis en oeuvre pour permettre l'attribution des droits d'acces.", + "Name": "Enregistrement et desinscription des utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure d'enregistrement et de desinscription des utilisateurs s'appuyant sur une interface de gestion des comptes et des droits d'acces. Cette procedure doit indiquer quelles donnees doivent etre supprimees au depart d'un utilisateur. b) Le prestataire doit attribuer des comptes nominatifs lors de l'enregistrement des utilisateurs places sous sa responsabilite. c) Le prestataire doit mettre en oeuvre des moyens permettant de s'assurer que la desinscription d'un utilisateur entraine la suppression de tous ses acces aux ressources du systeme d'information du service, ainsi que la suppression de ses donnees conformement a la procedure d'enregistrement et de desinscription (voir exigence 9.2 a))." + } + ], + "Checks": [ + "iam_sa_user_managed_key_unused", + "iam_service_account_unused" + ] + }, + { + "Id": "9.3", + "Description": "Un processus formel de gestion des droits d'acces doit etre mis en oeuvre pour controler l'attribution des droits d'acces a tous les types d'utilisateurs et a tous les systemes et services.", + "Name": "Gestion des droits d'acces", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'assurer l'attribution, la modification et le retrait de droits d'acces aux ressources du systeme d'information du service. b) Le prestataire doit mettre a la disposition de ses commanditaires les outils et les moyens qui permettent une differenciation des roles des utilisateurs du service, par exemple suivant leur role fonctionnel. c) Le prestataire doit tenir a jour l'inventaire des utilisateurs sous sa responsabilite disposant de droits d'administration sur les ressources du systeme d'information du service. d) Le prestataire doit etre en mesure de fournir, pour une ressource donnee mettant en oeuvre le service, la liste de tous les utilisateurs y ayant acces, qu'ils soient sous la responsabilite du prestataire ou du commanditaire ainsi que les droits d'acces qui leurs ont ete attribues. e) Le prestataire doit etre en mesure de fournir, pour un utilisateur donne, qu'ils soient sous la responsabilite du prestataire ou du commanditaire, la liste de tous ses droits d'acces sur les differents elements du systeme d'information du service. f) Le prestataire doit definir une liste de droits d'acces incompatibles entre eux. Il doit s'assurer, lors de l'attribution de droits d'acces a un utilisateur qu'il ne possede pas de droits d'acces incompatibles entre eux au titre de la liste precedemment etablie. g) Le prestataire doit inclure dans la procedure de gestion des droits d'acces les actions de revocation ou de suspension des droits de tout utilisateur." + } + ], + "Checks": [ + "iam_sa_no_administrative_privileges", + "iam_no_service_roles_at_project_level", + "iam_role_sa_enforce_separation_of_duties", + "iam_role_kms_enforce_separation_of_duties" + ] + }, + { + "Id": "9.4", + "Description": "Les proprietaires d'actifs doivent verifier les droits d'acces des utilisateurs a intervalles reguliers.", + "Name": "Revue des droits d'acces utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit reviser annuellement les droits d'acces des utilisateurs sur son perimetre de responsabilite. b) Le prestataire doit mettre a disposition du commanditaire un outil facilitant la revue des droits d'acces des utilisateurs places sous la responsabilite de ce dernier. c) Le prestataire doit reviser trimestriellement la liste des utilisateurs sur son perimetre de responsabilite pouvant utiliser les comptes techniques mentionnes dans l'exigence 9.2 b)." + } + ], + "Checks": [ + "iam_sa_user_managed_key_rotate_90_days", + "iam_sa_user_managed_key_unused", + "iam_service_account_unused" + ] + }, + { + "Id": "9.5", + "Description": "L'attribution et l'utilisation des informations secretes d'authentification doivent etre gerees dans le cadre d'un processus de gestion formel incluant une politique de mot de passe robuste et l'utilisation de l'authentification multi-facteur.", + "Name": "Gestion des authentifications des utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "compute", + "Type": "Automated", + "Comment": "a) Le prestataire doit formaliser et mettre en oeuvre des procedures de gestion de l'authentification des utilisateurs. En accord avec les exigences du chapitre 10, celles-ci doivent notamment porter sur : la gestion des moyens d'authentification (emission et reinitialisation de mot de passe, mise a jour des CRL et import des certificats racines en cas d'utilisation de certificats, etc.) ; la mise en place des moyens permettant une authentification a multiples facteurs afin de repondre aux differents cas d'usage du referentiel ; les systemes qui generent des mots de passe ou verifient leur robustesse, lorsqu'une authentification par mot de passe est utilisee. Ils doivent suivre les recommandations de [G_AUTH]. b) Tout mecanisme d'authentification doit prevoir le blocage d'un compte apres un nombre limite de tentatives infructueuses. c) Dans le cadre d'un service SaaS, le prestataire doit proposer a ses commanditaires des moyens d'authentification a multiples facteurs pour l'acces des utilisateurs finaux. d) Lorsque des comptes techniques, non nominatifs, sont necessaires, le prestataire doit mettre en place des mesures obligeant les utilisateurs a s'authentifier avec leur compte nominatif avant de pouvoir acceder a ces comptes techniques." + } + ], + "Checks": [ + "compute_project_os_login_enabled", + "compute_project_os_login_2fa_enabled", + "apikeys_key_rotated_in_90_days" + ] + }, + { + "Id": "9.6", + "Description": "L'acces aux interfaces d'administration du service cloud doit etre restreint et protege par des mecanismes d'authentification forte, incluant l'utilisation de dispositifs MFA materiels pour les comptes a privileges.", + "Name": "Acces aux interfaces d'administration", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "compute", + "Type": "Automated", + "Comment": "a) Les comptes d'administration sous la responsabilite du prestataire doivent etre geres a l'aide d'outils et d'annuaires distincts de ceux utilises pour la gestion des comptes utilisateurs places sous la responsabilite du commanditaire. b) Les interfaces d'administration mises a disposition des commanditaires doivent etre distinctes des interfaces d'administration utilisees par le prestataire. c) Les interfaces d'administration mises a disposition des commanditaires ne doivent permettre aucune connexion avec des comptes d'administrateurs sous la responsabilite du prestataire. d) Les interfaces d'administration utilisees par le prestataire ne doivent pas etre accessibles a partir d'un reseau public et ainsi ne doivent permettre aucune connexion des utilisateurs sous la responsabilite du commanditaire. e) Si des interfaces d'administration sont mises a disposition des commanditaires avec un acces via un reseau public, les flux d'administration doivent etre authentifies et chiffres avec des moyens en accord avec les exigences du chapitre 10.2. f) Le prestataire doit mettre en place un systeme d'authentification multifacteur fort pour l'acces : aux interfaces d'administration utilisees par le prestataire ; aux interfaces d'administration dediees aux commanditaires. g) Dans le cadre d'un service SaaS, les interfaces d'administration mises a disposition des commanditaires doivent etre differenciees des interfaces permettant l'acces des utilisateurs finaux. h) Des lors qu'une interface d'administration est accessible depuis un reseau public, le processus d'authentification doit avoir lieu avant toute interaction entre l'utilisateur et l'interface en question. i) Lorsque le prestataire utilise un service de type IaaS comme socle d'un autre type de service (CaaS, PaaS ou SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service IaaS. j) Lorsque le prestataire utilise un service de type CaaS comme socle d'un autre type de service (PaaS ou SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service CaaS. k) Lorsque le prestataire utilise un service de type PaaS comme socle d'un autre type de service (typiquement SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service PaaS." + } + ], + "Checks": [ + "compute_project_os_login_2fa_enabled", + "compute_instance_default_service_account_in_use", + "compute_instance_default_service_account_in_use_with_full_api_access", + "compute_firewall_rdp_access_from_the_internet_allowed", + "compute_firewall_ssh_access_from_the_internet_allowed", + "compute_instance_block_project_wide_ssh_keys_disabled", + "iam_account_access_approval_enabled", + "gke_cluster_no_default_service_account" + ] + }, + { + "Id": "9.7", + "Description": "L'acces a l'information et aux fonctions d'application des systemes doit etre restreint conformement a la politique de controle d'acces. Les ressources doivent etre protegees contre tout acces public non autorise.", + "Name": "Restriction des acces a l'information", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "compute", + "Type": "Automated", + "Comment": "a) Le prestataire doit mettre en oeuvre des mesures de cloisonnement appropriees entre ses commanditaires. b) Le prestataire doit mettre en oeuvre des mesures de cloisonnement appropriees entre le systeme d'information du service et ses autres systemes d'information (bureautique, informatique de gestion, gestion technique du batiment, controle d'acces physique, etc.). c) Le prestataire doit concevoir, developper, configurer et deployer le systeme d'information du service en assurant au moins un cloisonnement entre d'une part l'infrastructure technique et d'autre part les equipements necessaires a l'administration des services et des ressources qu'elle heberge. d) Dans le cadre du support technique, si les actions necessaires au diagnostic et a la resolution d'un probleme rencontre par un commanditaire necessitent un acces aux donnees du commanditaire, alors le prestataire doit : n'autoriser l'acces aux donnees du commanditaire qu'apres consentement explicite du commanditaire ; verifier que la personne a qui l'acces doit etre autorise a satisfait aux verifications de l'exigence 7.1.b ; dans le cas d'une intervention realisee a distance par une personne localisee hors de l'Union Europeenne, mettre en oeuvre une passerelle securisee (poste de rebond) par laquelle la personne devra se connecter et permettant une supervision (autorisation ou interdiction des actions, demandes d'explications, etc.) en temps reel, par une personne ayant elle-meme satisfait aux verifications de l'exigence 7.1.b ; considerer les actions menees, une fois l'acces autorise, comme des actions d'administration et les journaliser comme telles ; supprimer l'autorisation d'acces aux donnees du commanditaire au terme de ces actions." + } + ], + "Checks": [ + "compute_network_default_in_use", + "compute_instance_public_ip", + "cloudstorage_bucket_public_access", + "cloudstorage_bucket_uniform_bucket_level_access", + "cloudsql_instance_public_access", + "cloudsql_instance_public_ip", + "cloudsql_instance_private_ip_assignment", + "bigquery_dataset_public_access", + "kms_key_not_publicly_accessible", + "compute_image_not_publicly_shared" + ] + }, + { + "Id": "10.1", + "Description": "Les donnees stockees dans le cadre du service cloud doivent etre chiffrees au repos en utilisant des algorithmes et des longueurs de cle conformes a l'etat de l'art.", + "Name": "Chiffrement des donnees stockees", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "compute", + "Type": "Automated", + "Comment": "a) Le prestataire doit definir et mettre en oeuvre un mecanisme de chiffrement empechant la recuperation des donnees des commanditaires en cas de reallocation d'une ressource ou de recuperation du support physique. Dans le cas d'un service IaaS ou CaaS, cet objectif pourra par exemple etre atteint par un chiffrement du disque ou du systeme de fichier, lorsque le protocole d'acces en mode fichiers garantit que seuls des blocs vides peuvent etre alloues, ou par un chiffrement par volume dans le cas d'un acces en mode bloc, avec au moins une cle par commanditaire. Dans le cas d'un service PaaS ou SaaS, cet objectif pourra etre atteint en utilisant un chiffrement applicatif dans le perimetre du prestataire, avec au moins une cle par commanditaire. b) Le prestataire doit utiliser une methode de chiffrement des donnees respectant les regles de [CRYPTO_B1]. c) Il est recommande d'utiliser une methode de chiffrement des donnees respectant les recommandations de [CRYPTO_B1]. d) Le prestataire doit mettre en place un chiffrement des donnees sur les supports amovibles et les supports de sauvegarde amenes a quitter le perimetre de securite physique du systeme d'information du service (au sens du chapitre 10), en fonction du besoin de securite des donnees (voir chapitre 8.3)." + } + ], + "Checks": [ + "compute_instance_encryption_with_csek_enabled", + "bigquery_dataset_cmk_encryption", + "bigquery_table_cmk_encryption", + "dataproc_encrypted_with_cmks_disabled" + ] + }, + { + "Id": "10.2", + "Description": "Les flux de donnees entre les composants du service cloud et entre le service et les commanditaires doivent etre chiffres en transit en utilisant des protocoles et des algorithmes conformes a l'etat de l'art.", + "Name": "Chiffrement des flux", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "cloudsql", + "Type": "Partially Automated", + "Comment": "a) Lorsque le prestataire met en oeuvre un mecanisme de chiffrement des flux reseau, celui-ci doit respecter les regles de [CRYPTO_B1]. b) Lorsque le prestataire met en oeuvre un mecanisme de chiffrement des flux reseau, il est recommande que celui-ci respecte les recommandations de [CRYPTO_B1]. c) Si le protocole TLS est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_TLS]. d) Si le protocole IPsec est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_IPSEC]. e) Si le protocole SSH est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_SSH]." + } + ], + "Checks": [ + "cloudsql_instance_ssl_connections" + ] + }, + { + "Id": "10.3", + "Description": "Les mots de passe doivent etre stockes sous forme hachee en utilisant des algorithmes robustes conformes a l'etat de l'art et les politiques de mot de passe doivent imposer des exigences de complexite adequates.", + "Name": "Hachage des mots de passe", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "iam", + "Type": "Manual", + "Comment": "a) Le prestataire ne doit stocker que l'empreinte des mots de passe des utilisateurs et des comptes techniques. b) Le prestataire doit mettre en oeuvre une fonction de hachage respectant les regles de [CRYPTO_B1]. c) Il est recommande que le prestataire mette en oeuvre une fonction de hachage respectant les recommandations de [CRYPTO_B1]. d) Le prestataire doit generer les empreintes des mots de passe avec une fonction de hachage associee a l'utilisation d'un sel cryptographique respectant les regles de [CRYPTO_B1]." + } + ], + "Checks": [] + }, + { + "Id": "10.4", + "Description": "Des mecanismes de non-repudiation doivent etre mis en oeuvre pour assurer la tracabilite des actions effectuees sur le service cloud, incluant la validation de l'integrite des journaux.", + "Name": "Non repudiation", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "cloudstorage", + "Type": "Partially Automated", + "Comment": "a) Lorsque le prestataire met en oeuvre un mecanisme de signature electronique, celui-ci doit respecter les regles de [CRYPTO_B1]. b) Lorsque le prestataire met en oeuvre un mecanisme de signature electronique, il est recommande que celui-ci respecte les recommandations de [CRYPTO_B1]." + } + ], + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock" + ] + }, + { + "Id": "10.5", + "Description": "Les secrets cryptographiques (cles, certificats, mots de passe) doivent etre geres de maniere securisee tout au long de leur cycle de vie, incluant la generation, le stockage, la distribution, la rotation et la destruction.", + "Name": "Gestion des secrets", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "kms", + "Type": "Automated", + "Comment": "a) Le prestataire doit mettre en oeuvre des cles cryptographiques respectant les regles de [CRYPTO_B2]. b) Il est recommande que le prestataire mette en oeuvre des cles cryptographiques respectant les recommandations de [CRYPTO_B2]. c) Le prestataire doit proteger l'acces aux cles cryptographiques et autres secrets utilises pour le chiffrement des donnees par un moyen adapte : conteneur de securite (logiciel ou materiel) ou support disjoint. d) Le prestataire doit proteger l'acces aux cles cryptographiques et autres secrets utilises pour les taches d'administration par un conteneur de securite adapte, logiciel ou materiel." + } + ], + "Checks": [ + "kms_key_rotation_enabled", + "kms_key_not_publicly_accessible", + "iam_sa_no_user_managed_keys", + "iam_sa_user_managed_key_rotate_90_days", + "iam_sa_user_managed_key_unused", + "apikeys_key_rotated_in_90_days", + "apikeys_api_restrictions_configured" + ] + }, + { + "Id": "10.6", + "Description": "Les racines de confiance (certificats racine, autorites de certification) utilisees dans le cadre du service cloud doivent etre gerees de maniere securisee. Les certificats doivent etre valides et utiliser des algorithmes de cle robustes.", + "Name": "Racines de confiance", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "dns", + "Type": "Partially Automated", + "Comment": "a) Sur l'infrastructure technique, le prestataire doit utiliser exclusivement des certificats de cle publique issus d'une autorite de certification d'un Etat membre de l'Union Europeenne (les ceremonies de generation des cles maitresses doivent avoir lieu dans un pays membre de l'Union Europeenne et en presence du prestataire)." + } + ], + "Checks": [ + "dns_rsasha1_in_use_to_key_sign_in_dnssec", + "dns_rsasha1_in_use_to_zone_sign_in_dnssec" + ] + }, + { + "Id": "11.1", + "Description": "Des perimetres de securite doivent etre definis et utilises pour proteger les zones contenant des informations sensibles ou critiques et les moyens de traitement de l'information.", + "Name": "Perimetres de securite physique", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des perimetres de securite, incluant le marquage des zones et les differents moyens de limitation et de controle des acces. b) Le prestataire doit distinguer des zones publiques, des zones privees et des zones sensibles. 11.1.1. Zones publiques : a) Les zones publiques sont accessibles a tous dans les limites de la propriete du prestataire. Le prestataire ne doit heberger aucune ressource devolue au service ou permettant d'acceder a des composantes de celui-ci dans les zones publiques. 11.1.2. Zones privees : a) Les zones privees peuvent heberger : les plateformes et moyens de developpement du service ; les postes d'administration, d'exploitation et de supervision ; les locaux a partir desquels le prestataire opere. 11.1.3. Zones sensibles : a) Les zones sensibles sont reservees a l'hebergement du systeme d'information de production du service hors postes d'administration, d'exploitation et de supervision." + } + ], + "Checks": [] + }, + { + "Id": "11.2", + "Description": "Les zones securisees doivent etre protegees par des controles d'acces physiques adequats pour s'assurer que seul le personnel autorise est admis.", + "Name": "Controle d'acces physique", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "11.2.1. Zones privees : a) Le prestataire doit proteger les zones privees contre les acces non autorises. Pour ce faire, il doit mettre en oeuvre un controle d'acces physique reposant au moins sur un facteur personnel : la connaissance d'un secret, la detention d'un objet ou la biometrie. b) Il est recommande que le prestataire respecte les recommandations de [G_CVAP] pour mettre en oeuvre du controle d'acces physique. c) Le prestataire doit definir et documenter des mesures d'acces physique derogatoires en cas d'urgence. d) Le prestataire doit afficher a l'entree des zones privees un avertissement relatif aux limites et conditions d'acces a ces zones. e) Le prestataire doit definir et documenter les plages horaires et conditions d'acces aux zones privees en fonction des profils des intervenants. f) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de s'assurer que les visiteurs sont systematiquement accompagnes par le prestataire lors de leurs acces et sejours en zone privee. Le prestataire doit conserver une trace de l'identite des visiteurs conformement a la legislation et reglementation en vigueur. g) En cas d'intervention (actions de diagnostic, de maintenance, ou d'administration) en zone privee par un tiers visiteur, le prestataire doit faire superviser (suivre, autoriser, interdire, questionner) les actions par un personnel ayant satisfait aux verifications de l'exigence 7.1.b. h) Le prestataire doit documenter et mettre en oeuvre des mecanismes de surveillance et de detection des acces non autorises aux zones privees. 11.2.2. Zones sensibles : a) Le prestataire doit proteger les zones sensibles contre les acces non autorises. Pour ce faire, il doit mettre en oeuvre un controle d'acces physique reposant au moins sur deux facteurs personnels : la connaissance d'un secret, la detention d'un objet ou la biometrie. b) Il est recommande que le prestataire respecte les recommandations de [G_CVAP] pour la mise en oeuvre du controle d'acces physique. c) Le prestataire doit definir et documenter des mesures d'acces physique derogatoires en cas d'urgence. d) Le prestataire doit afficher a l'entree des zones sensibles un avertissement relatif aux limites et conditions d'acces a ces zones. e) Le prestataire doit definir et documenter les plages horaires et conditions d'acces aux zones sensibles en fonction des profils des intervenants. f) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de s'assurer que les visiteurs sont systematiquement accompagnes par le prestataire lors de leurs acces et sejours en zone sensible. Le prestataire doit conserver une trace de l'identite des visiteurs conformement a la legislation et reglementation en vigueur. g) En cas d'intervention (actions de diagnostic, de maintenance, ou d'administration) en zone sensible par un tiers visiteur, le prestataire doit faire superviser (suivre, autoriser, interdire, questionner) les actions par un personnel ayant satisfait aux verifications de l'exigence 7.1.b. h) Le prestataire doit documenter et mettre en oeuvre des mecanismes de surveillance et de detection des acces non autorises aux zones sensibles. i) Le prestataire doit mettre en place une journalisation des acces physiques aux zones sensibles. Il doit effectuer une revue de ces journaux au moins mensuellement. j) Le prestataire doit mettre en oeuvre les moyens garantissant qu'aucun acces direct n'existe entre une zone publique et une zone sensible." + } + ], + "Checks": [] + }, + { + "Id": "11.3", + "Description": "Des mesures de protection contre les menaces exterieures et environnementales, telles que les catastrophes naturelles, les attaques malveillantes ou les accidents, doivent etre concues et appliquees.", + "Name": "Protection contre les menaces exterieures et environnementales", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de minimiser les risques inherents aux sinistres physiques (incendie, degat des eaux, etc.) et naturels (risques climatiques, inondations, seismes, etc.). b) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de limiter les risques de depart et de propagation de feu ainsi que les risques de degat des eaux. c) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de prevenir et limiter les consequences d'une coupure d'alimentation electrique et permettre une reprise du service conformement aux exigences de disponibilite du service definies dans la convention de service. d) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de maintenir des conditions de temperature et d'humidite adaptees aux equipements. De plus, il doit mettre en oeuvre des mesures permettant de prevenir les pannes de climatisation et d'en limiter les consequences. e) Le prestataire doit documenter et mettre en oeuvre des controles et tests reguliers des equipements de detection et de protection physique." + } + ], + "Checks": [] + }, + { + "Id": "11.4", + "Description": "Des mesures de securite physique pour le travail dans les zones privees et sensibles doivent etre concues et appliquees.", + "Name": "Travail dans les zones privees et sensibles", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit integrer les elements de securite physique dans la politique de securite et l'appreciation des risques conformement au niveau de securite requis par la categorie de la zone. b) Le prestataire doit documenter et mettre en oeuvre des procedures relatives au travail en zones privees et sensibles. Il doit communiquer ces procedures aux intervenants concernes." + } + ], + "Checks": [] + }, + { + "Id": "11.5", + "Description": "Les points d'acces tels que les zones de livraison et de chargement et les autres points par lesquels des personnes non autorisees peuvent penetrer dans les locaux doivent etre controles.", + "Name": "Zones de livraison et de chargement", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Les zones de livraison et de chargement et les autres points par lesquels des personnes non autorisees peuvent penetrer dans les locaux sans etre accompagnees sont considerees comme des zones publiques. b) Le prestataire doit isoler les points d'acces de ces zones vers les zones privees et sensibles, de facon a eviter les acces non autorises, ou a defaut, implementer des mesures compensatoires permettant d'assurer le meme niveau de securite." + } + ], + "Checks": [] + }, + { + "Id": "11.6", + "Description": "Le cablage electrique et de telecommunications transportant des donnees ou supportant des services d'information doit etre protege contre les interceptions, les interferences ou les dommages.", + "Name": "Securite du cablage", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de proteger le cablage electrique et de telecommunication des dommages physiques et des possibilites d'interception. b) Le prestataire doit etablir et tenir a jour un plan de cablage. c) Il est recommande que le prestataire mette en oeuvre des mesures permettant d'identifier les cables (par exemple code couleur, etiquette, etc.) afin d'en faciliter l'exploitation et limiter les erreurs de manipulation." + } + ], + "Checks": [] + }, + { + "Id": "11.7", + "Description": "Les materiels doivent etre entretenus correctement pour garantir leur disponibilite permanente et leur integrite.", + "Name": "Maintenance des materiels", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de s'assurer que les conditions d'installation, de maintenance et d'entretien des equipements du systeme d'information du service heberges en zones privees et sensibles sont compatibles avec les exigences de confidentialite et de disponibilite du service definies dans la convention de service. b) Le prestataire doit souscrire des contrats de maintenance permettant de disposer des mises a jour de securite des logiciels installes sur les equipements du systeme d'information du service. c) Le prestataire doit s'assurer que les supports ne peuvent etre retournes a un tiers que si les donnees du commanditaire y sont stockees chiffrees conformement au chapitre 10.1 ou ont prealablement ete detruites a l'aide d'un mecanisme d'effacement securise par reecriture de motifs aleatoires. d) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de s'assurer que les conditions d'installation, de maintenance et d'entretien des equipements techniques annexes (alimentation electrique, climatisation, incendie, etc.) sont compatibles avec les exigences de disponibilite du service definies dans la convention de service." + } + ], + "Checks": [] + }, + { + "Id": "11.8", + "Description": "Les materiels, les informations ou les logiciels ne doivent pas etre sortis des locaux du prestataire sans autorisation prealable.", + "Name": "Sortie des actifs", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de transfert hors site de donnees du commanditaire, equipements et logiciels. Cette procedure doit necessiter que la direction du prestataire donne son autorisation ecrite. Dans tous les cas, le prestataire doit mettre en oeuvre les moyens permettant de garantir que le niveau de protection en confidentialite et en integrite des actifs durant leur transport est equivalent a celui sur site." + } + ], + "Checks": [] + }, + { + "Id": "11.9", + "Description": "Tous les composants des equipements contenant des supports de stockage doivent etre verifies pour s'assurer que toute donnee sensible et tout logiciel sous licence ont ete supprimes ou ecrases de facon securisee avant leur mise au rebut ou leur reutilisation.", + "Name": "Recyclage securise du materiel", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des moyens permettant d'effacer de maniere securisee par reecriture de motifs aleatoires tout support de donnees mis a disposition d'un commanditaire. Si l'espace de stockage est chiffre dans le cadre de l'exigence 10.1.a), l'effacement peut etre realise par un effacement securise de la cle de chiffrement." + } + ], + "Checks": [] + }, + { + "Id": "11.10", + "Description": "Le materiel en attente d'utilisation doit etre protege de maniere adequate.", + "Name": "Materiel en attente d'utilisation", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de protection du materiel en attente d'utilisation." + } + ], + "Checks": [] + }, + { + "Id": "12.1", + "Description": "Les procedures d'exploitation doivent etre documentees et mises a disposition de tous les utilisateurs concernes.", + "Name": "Procedures d'exploitation documentees", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter les procedures d'exploitation, les tenir a jour et les rendre accessibles au personnel concerne." + } + ], + "Checks": [] + }, + { + "Id": "12.2", + "Description": "Les changements apportes au systeme d'information du prestataire, aux processus metier, aux moyens de traitement de l'information et aux systemes qui ont une incidence sur la securite de l'information doivent etre geres.", + "Name": "Gestion des changements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de gestion des changements apportes aux systemes et moyens de traitement de l'information. b) Le prestataire doit documenter et mettre en oeuvre une procedure permettant, en cas d'operations realisees par le prestataire et pouvant avoir un impact sur la securite ou la disponibilite du service, de communiquer au plus tot a l'ensemble de ses commanditaires les informations suivantes : la date et l'heure programmees du debut et de la fin des operations ; la nature des operations ; les impacts sur la securite ou la disponibilite du service ; le contact au sein du prestataire. c) Dans le cadre d'un service PaaS, le prestataire doit informer au plus tot le commanditaire de toute modification a venir sur des elements logiciels sous sa responsabilite des lors que la compatibilite complete ne peut etre assuree. d) Le prestataire doit informer au plus tot le commanditaire de toute modification a venir sur les elements du service des lors qu'elle est susceptible d'occasionner une perte de fonctionnalite pour le commanditaire." + } + ], + "Checks": [ + "iam_cloud_asset_inventory_enabled", + "iam_audit_logs_enabled", + "logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled", + "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled" + ] + }, + { + "Id": "12.3", + "Description": "Les environnements de developpement, de test et d'exploitation doivent etre separes pour reduire les risques d'acces non autorise ou de changements non souhaites dans l'environnement d'exploitation.", + "Name": "Separation des environnements de developpement, de test et d'exploitation", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "organizations", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de separer physiquement les environnements lies a la production du service des autres environnements, dont les environnements de developpement." + } + ], + "Checks": [] + }, + { + "Id": "12.4", + "Description": "Des mesures de detection, de prevention et de recuperation conjuguees a une sensibilisation des utilisateurs doivent etre mises en oeuvre pour proteger le systeme d'information contre les codes malveillants.", + "Name": "Mesures contre les codes malveillants", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "artifacts", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures de detection, de prevention et de restauration pour se proteger des codes malveillants. Le perimetre d'application de cette exigence sur le systeme d'information du service doit necessairement contenir les postes utilisateurs sous la responsabilite du prestataire et les flux entrants sur ce meme systeme d'information. b) Le prestataire doit documenter et mettre en oeuvre une sensibilisation de ses employes aux risques lies aux codes malveillants et aux bonnes pratiques pour reduire l'impact d'une infection." + } + ], + "Checks": [ + "artifacts_container_analysis_enabled", + "gcr_container_scanning_enabled", + "compute_instance_shielded_vm_enabled" + ] + }, + { + "Id": "12.5", + "Description": "Des copies de sauvegarde des informations, des logiciels et des images systeme doivent etre effectuees et testees regulierement conformement a une politique de sauvegarde convenue.", + "Name": "Sauvegarde des informations", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "cloudsql", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de sauvegarde et de restauration des donnees sous sa responsabilite dans le cadre du service. Cette politique doit prevoir une sauvegarde quotidienne de l'ensemble des donnees (informations, logiciels, configurations, etc.) sous la responsabilite du prestataire dans le cadre du service. b) Le prestataire doit documenter et mettre en oeuvre des mesures de protection des sauvegardes conformement a la politique de controle d'acces (voir chapitre 9). Cette politique doit prevoir une revue mensuelle des traces d'acces aux sauvegardes. c) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester regulierement la restauration des sauvegardes. d) Le prestataire doit localiser les sauvegardes a une distance suffisante des equipements principaux en coherence avec les resultats de l'appreciation de risques et permettant de faire face a des sinistres majeurs. Les sauvegardes sont assujetties aux memes exigences de localisation que les donnees operationnelles. Le ou les sites de sauvegarde sont assujettis aux memes exigences de securite que le site principal, en particulier celles listees aux chapitres 8 et 11. Les communications entre site principal et site de sauvegarde doivent etre protegees par chiffrement, conformement aux exigences du chapitre 10." + } + ], + "Checks": [ + "cloudsql_instance_automated_backups", + "cloudstorage_bucket_versioning_enabled", + "cloudstorage_bucket_soft_delete_enabled" + ] + }, + { + "Id": "12.6", + "Description": "Des journaux d'evenements enregistrant les activites des utilisateurs, les exceptions, les defaillances et les evenements de securite de l'information doivent etre crees, tenus a jour et regulierement revus.", + "Name": "Journalisation des evenements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de journalisation incluant au minimum les elements suivants : la liste des sources de collecte ; la liste des evenements a journaliser par source ; l'objet de la journalisation par evenement ; la frequence de la collecte et base de temps utilisee ; la duree de retention locale et centralisee ; les mesures de protection des journaux (dont chiffrement et duplication) ; la localisation des journaux. b) Le prestataire doit generer et collecter les evenements suivants : les activites des utilisateurs liees a la securite de l'information ; la modification des droits d'acces dans le perimetre de sa responsabilite ; les evenements issus des mecanismes de lutte contre les codes malveillants (voir chapitre 12.4) ; les exceptions ; les defaillances ; tout autre evenement lie a la securite de l'information. c) Le prestataire doit conserver les evenements issus de la journalisation pendant une duree minimale de six mois sous reserve du respect des exigences legales et reglementaires. d) Le prestataire doit fournir, sur demande d'un commanditaire, l'ensemble des evenements le concernant. e) Il est recommande que le systeme de journalisation mis en place par le prestataire respecte les recommandations de [NT_JOURNAL]." + } + ], + "Checks": [ + "iam_audit_logs_enabled", + "cloudstorage_audit_logs_enabled", + "logging_sink_created", + "cloudstorage_bucket_sufficient_retention_period", + "compute_subnet_flow_logs_enabled", + "cloudstorage_bucket_logging_enabled", + "compute_loadbalancer_logging_enabled", + "compute_network_dns_logging_enabled", + "cloudsql_instance_postgres_enable_pgaudit_flag", + "cloudsql_instance_postgres_log_connections_flag", + "cloudsql_instance_postgres_log_disconnections_flag", + "cloudsql_instance_postgres_log_statement_flag", + "cloudsql_instance_postgres_log_min_error_statement_flag", + "cloudsql_instance_postgres_log_min_messages_flag" + ] + }, + { + "Id": "12.7", + "Description": "Les moyens de journalisation et les informations journalisees doivent etre proteges contre les risques de falsification et les acces non autorises.", + "Name": "Protection de l'information journalisee", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "cloudstorage", + "Type": "Automated", + "Comment": "a) Le prestataire doit proteger les equipements de journalisation et les evenements journalises contre les atteintes a leur disponibilite, integrite ou confidentialite, conformement au chapitre 3.2 de [NT_JOURNAL]. b) Le prestataire doit gerer le dimensionnement de l'espace de stockage de l'ensemble des equipements hebergeant une ou plusieurs sources de collecte afin de permettre la conservation locale des evenements journalises prevue par la politique de journalisation des evenements. Cette gestion du dimensionnement doit prendre en compte les evolutions du systeme d'information. c) Le prestataire doit transferer les evenements journalises en assurant leur protection en confidentialite et en integrite, sur un ou plusieurs serveurs centraux dedies et doit les stocker sur une machine physique distincte de celle qui les a generes. d) Le prestataire doit mettre en place une sauvegarde des evenements collectes suivant une politique adaptee. e) Le prestataire doit executer les processus de journalisation et de collecte des evenements avec des comptes disposant de privileges necessaires et suffisants et doit limiter l'acces aux evenements journalises conformement a la politique de controle d'acces (voir chapitre 9.1)." + } + ], + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock", + "cloudstorage_bucket_public_access", + "cloudstorage_bucket_logging_enabled", + "cloudstorage_bucket_uniform_bucket_level_access" + ] + }, + { + "Id": "12.8", + "Description": "Les horloges de tous les systemes de traitement de l'information pertinents d'un organisme ou d'un domaine de securite doivent etre synchronisees sur une source de reference temporelle unique.", + "Name": "Synchronisation des horloges", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une synchronisation des horloges de l'ensemble des equipements sur une ou plusieurs sources de temps internes coherentes entre elles. Ces sources pourront elles-memes etre synchronisees sur plusieurs sources fiables externes, sauf pour les reseaux isoles. b) Le prestataire doit mettre en place l'horodatage de chaque evenement journalise." + } + ], + "Checks": [] + }, + { + "Id": "12.9", + "Description": "Les evenements de securite doivent etre analyses et correles afin de detecter les incidents de securite. Des systemes de detection et de correlation doivent etre mis en oeuvre.", + "Name": "Analyse et correlation des evenements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "logging", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une infrastructure permettant l'analyse et la correlation des evenements enregistres par le systeme de journalisation afin de detecter les evenements susceptibles d'affecter la securite du systeme d'information du service, en temps reel ou a posteriori pour des evenements remontant jusqu'a six mois. b) Il est recommande de s'appuyer sur le referentiel d'exigences des prestataires de detection d'incidents de securite [PDIS] pour la mise en place et l'exploitation de l'infrastructure d'analyse et de correlation des evenements. c) Le prestataire doit acquitter les alarmes remontees par l'infrastructure d'analyse et de correlation des evenements au moins quotidiennement." + } + ], + "Checks": [ + "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", + "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled", + "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", + "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", + "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", + "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", + "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", + "logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled", + "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled" + ] + }, + { + "Id": "12.10", + "Description": "Des regles regissant l'installation de logiciels par les utilisateurs doivent etre etablies et mises en oeuvre. Les systemes doivent etre geres de maniere centralisee et les correctifs appliques regulierement.", + "Name": "Installation de logiciels sur des systemes en exploitation", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "ssm", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de controler l'installation de logiciels sur les equipements du systeme d'information du service. b) Le prestataire doit documenter et mettre en oeuvre une procedure de gestion de la configuration des environnements logiciels mis a la disposition du commanditaire, notamment pour leur maintien en condition de securite. c) Le prestataire doit fournir une capacite d'inspection et de suppression, si necessaire, des entrants (controle de l'authenticite et de l'innocuite des mises a jour, controle de l'innocuite des outils fournis, etc.) relatifs au perimetre de l'infrastructure technique : cette capacite d'inspection et de suppression doit generer des journaux d'activite et doit pouvoir faire l'objet d'un audit de code ; les entrants doivent etre traites sur des dispositifs specifiques operes et maintenus par le prestataire et heberges dans une zone cloisonnee du reste de l'infrastructure (du type zone demilitarisee telle que definie dans [G_INT])." + } + ], + "Checks": [] + }, + { + "Id": "12.11", + "Description": "Les informations sur les vulnerabilites techniques des systemes d'information utilises doivent etre obtenues en temps voulu, l'exposition du prestataire a ces vulnerabilites doit etre evaluee et les mesures appropriees doivent etre prises pour traiter le risque associe.", + "Name": "Gestion des vulnerabilites techniques", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "artifacts", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus de veille permettant de gerer les vulnerabilites techniques des logiciels et des systemes utilises dans le systeme d'information du service. b) Le prestataire doit evaluer son exposition a ces vulnerabilites en les incluant dans l'appreciation des risques et appliquer les mesures de traitement du risque adaptees." + } + ], + "Checks": [ + "artifacts_container_analysis_enabled", + "gcr_container_scanning_enabled" + ] + }, + { + "Id": "12.12", + "Description": "L'administration des systemes d'information du service cloud doit etre effectuee de maniere securisee via des canaux dedies et des protocoles securises.", + "Name": "Administration", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "compute", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure obligeant les administrateurs sous sa responsabilite a utiliser des terminaux dedies pour la realisation exclusive des taches d'administration, en accord avec le chapitre 4.1 intitule 'poste et reseau d'administration' de [NT_ADMIN]. Il doit les maitriser et les maintenir a jour. b) Le prestataire doit mettre en place des mesures de durcissement de la configuration des terminaux utilises pour les taches d'administration, notamment celles du chapitre 4.2 intitule 'securisation du socle' de [NT_ADMIN]. c) Lorsque le prestataire autorise une situation de mobilite pour les administrateurs sous sa responsabilite, il doit l'encadrer par une politique documentee. La solution mise en oeuvre doit assurer que le niveau de securite de cette situation de mobilite est au moins equivalent au niveau de securite hors situation de mobilite (voir chapitres 9.6 et 9.7). Cette solution doit notamment inclure : l'utilisation d'un tunnel chiffre, non debrayable et non contournable, pour l'ensemble des flux (voir chapitre 10.2) ; le chiffrement integral du disque (voir chapitre 10.1)." + } + ], + "Checks": [ + "compute_project_os_login_enabled", + "compute_project_os_login_2fa_enabled", + "compute_instance_serial_ports_in_use" + ] + }, + { + "Id": "12.13", + "Description": "Le telediagnostic et la telemaintenance des composants de l'infrastructure doivent etre encadres par des procedures de securite specifiques.", + "Name": "Telediagnostic et telemaintenance des composants de l'infrastructure", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Dans le cadre du telediagnostic ou de la telemaintenance de composants de l'infrastructure, considerant les risques d'atteinte a la confidentialite des donnees des commanditaires, le prestataire doit : verifier que la personne a qui l'acces doit etre autorise a satisfait aux verifications de l'exigence 7.1.b ; dans le cas d'une intervention realisee par une personne n'ayant pas satisfait aux verifications de l'exigence 7.1.b, mettre en oeuvre une passerelle securisee (poste de rebond) par laquelle la personne devra se connecter et permettant une supervision des actions (autorisation ou interdiction des actions, demande d'explications, etc.) en temps reel, par une personne ayant elle-meme satisfait aux verifications de l'exigence 7.1.b. La passerelle securisee devra repondre aux objectifs de securite specifies dans [G_EXT] ; considerer les actions menees, une fois l'acces autorise, comme des actions d'administration et les journaliser comme telles ; supprimer l'autorisation d'acces a l'issue de l'intervention." + } + ], + "Checks": [] + }, + { + "Id": "12.14", + "Description": "Les flux sortants de l'infrastructure du service cloud doivent etre surveilles afin de detecter et de prevenir les exfiltrations de donnees et les communications non autorisees.", + "Name": "Surveillance des flux sortants de l'infrastructure", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "compute", + "Type": "Automated", + "Comment": "a) Le prestataire doit fournir une capacite d'inspection et de suppression des sortants de l'infrastructure technique relatifs au perimetre du service (informations de facturation, les eventuels journaux necessaires au traitement d'incidents, etc.) : les sortants doivent pouvoir etre expurges des donnees pouvant porter atteinte a la confidentialite des donnees des commanditaires ; cette capacite d'inspection et de suppression doit generer des journaux d'activite et doit pouvoir faire l'objet d'un audit de code ; les sortants sont traites sur des dispositifs specifiques operes et maintenus par le prestataire, et heberges dans une zone cloisonnee du reste de l'infrastructure (du type zone demilitarisee telle que definie dans [G_INT])." + } + ], + "Checks": [ + "compute_subnet_flow_logs_enabled" + ] + }, + { + "Id": "13.1", + "Description": "Le prestataire doit etablir et maintenir une cartographie complete et a jour de son systeme d'information, incluant les reseaux, les flux et les composants.", + "Name": "Cartographie du systeme d'information", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "iam", + "Type": "Automated", + "Comment": "a) Le prestataire doit etablir et tenir a jour une cartographie du systeme d'information du service, en lien avec l'inventaire des actifs (voir chapitre 8.1), comprenant au minimum les elements suivants : la liste des ressources materielles ou virtualisees ; les noms et fonctions des applications, supportant le service ; le schema d'architecture reseau au niveau 3 du modele OSI sur lequel les points nevralgiques sont identifies : les points d'interconnexions, notamment avec les reseaux tiers et publics ; les reseaux, sous-reseaux, notamment les reseaux d'administration ; les equipements assurant des fonctions de securite (filtrage, authentification, chiffrement, etc.) ; les serveurs hebergeant des donnees ou assurant des fonctions sensibles ; la matrice des flux reseau autorises en precisant : leur description technique (services, protocoles et ports) ; la justification metier ou d'infrastructure technique ; le cas echeant, lorsque des services, protocoles ou ports reputes non surs sont utilises, les mesures compensatoires mises en place, dans la logique de defense en profondeur. b) Le prestataire doit reviser au moins annuellement la cartographie." + } + ], + "Checks": [ + "iam_cloud_asset_inventory_enabled", + "compute_subnet_flow_logs_enabled", + "compute_network_dns_logging_enabled" + ] + }, + { + "Id": "13.2", + "Description": "Les reseaux doivent etre cloisonnes et les flux entre les segments doivent etre filtres selon le principe du moindre privilege. Les groupes de securite et les listes de controle d'acces reseau doivent etre configures de maniere restrictive.", + "Name": "Cloisonnement des reseaux", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "compute", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre, pour le systeme d'information du service, les mesures de cloisonnement (logique, physique ou par chiffrement) pour separer les flux reseau selon : la sensibilite des informations transmises ; la nature des flux (production, administration, supervision, etc.) ; le domaine d'appartenance des flux (des commanditaires - avec distinction par commanditaire ou ensemble de commanditaires, du prestataire, des tiers, etc.) ; le domaine technique (traitement, stockage, etc.). b) Le prestataire doit cloisonner, physiquement ou par chiffrement, tous les flux de donnees internes au systeme d'information du service vis-a-vis de tout autre systeme d'information. Lorsque ce cloisonnement est realise par chiffrement, il est realise en accord avec les exigences du chapitre 10.2. c) Dans le cas ou le reseau d'administration de l'infrastructure technique ne fait pas l'objet d'un cloisonnement physique, les flux d'administration doivent transiter dans un tunnel chiffre, en accord avec les exigences du chapitre 10.2. d) Le prestataire doit mettre en place et configurer un pare-feu applicatif pour proteger les interfaces d'administration destinees a ses commanditaires et exposees sur un reseau public. e) Le prestataire doit mettre en oeuvre sur l'ensemble des interfaces d'administration et de supervision de l'infrastructure technique du service un mecanisme de filtrage n'autorisant que les connexions legitimes identifiees dans la matrice des flux autorises." + } + ], + "Checks": [ + "compute_network_default_in_use", + "compute_network_not_legacy", + "compute_firewall_rdp_access_from_the_internet_allowed", + "compute_firewall_ssh_access_from_the_internet_allowed", + "cloudstorage_uses_vpc_service_controls", + "compute_instance_single_network_interface", + "compute_instance_ip_forwarding_is_enabled" + ] + }, + { + "Id": "13.3", + "Description": "Les reseaux doivent etre surveilles de maniere continue afin de detecter les activites anormales ou malveillantes.", + "Name": "Surveillance des reseaux", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "compute", + "Type": "Automated", + "Comment": "a) Le prestataire doit disposer une ou plusieurs sondes de detection d'incidents de securite sur le systeme d'information du service. Ces sondes doivent notamment permettre la supervision de chacune des interconnexions du systeme d'information du service avec des systemes d'information tiers et des reseaux publics. Ces sondes doivent etre des sources de collecte pour l'infrastructure d'analyse et de correlation des evenements (voir chapitre 12.9)." + } + ], + "Checks": [ + "compute_subnet_flow_logs_enabled", + "compute_network_dns_logging_enabled" + ] + }, + { + "Id": "14.1", + "Description": "Des regles de developpement securise des logiciels et des systemes doivent etre etablies et appliquees au sein du prestataire.", + "Name": "Politique de developpement securise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des regles de developpement securise des logiciels et des systemes, et les appliquer aux developpements internes. b) Le prestataire doit documenter et mettre en oeuvre une formation adaptee en developpement securise aux employes concernes." + } + ], + "Checks": [] + }, + { + "Id": "14.2", + "Description": "Les changements apportes aux systemes dans le cycle de developpement doivent etre geres a l'aide de procedures formelles de controle des changements.", + "Name": "Procedures de controle des changements de systeme", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "iam", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de controle des changements apportes au systeme d'information du service. b) Le prestataire doit documenter et mettre en oeuvre une procedure de validation des changements apportes au systeme d'information du service sur un environnement de pre-production avant leur mise en production. c) Le prestataire doit conserver un historique des versions des logiciels et des systemes (developpements internes ou externes, produits commerciaux) mis en oeuvre pour permettre de reconstituer, le cas echeant dans un environnement de test, un environnement complet tel qu'il etait mis en oeuvre a une date donnee. La duree de conservation de cet historique doit etre en accord avec celle des sauvegardes (voir chapitre 12.5)." + } + ], + "Checks": [ + "iam_cloud_asset_inventory_enabled", + "iam_audit_logs_enabled" + ] + }, + { + "Id": "14.3", + "Description": "Lorsque les plateformes d'exploitation sont modifiees, les applications critiques metier doivent etre revues et testees afin de verifier qu'il n'y a pas d'effet indesirable sur l'activite ou la securite du prestataire.", + "Name": "Revue technique des applications apres changement apporte a la plateforme d'exploitation", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester, prealablement a leur mise en production, l'ensemble des applications afin de verifier l'absence de tout effet indesirable sur l'activite ou sur la securite du service." + } + ], + "Checks": [] + }, + { + "Id": "14.4", + "Description": "Les environnements de developpement doivent etre securises et isoles des environnements de production.", + "Name": "Environnement de developpement securise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "organizations", + "Type": "Manual", + "Comment": "a) Le prestataire doit mettre en oeuvre un environnement securise de developpement permettant de gerer l'integralite du cycle de developpement du systeme d'information du service. b) Le prestataire doit prendre en compte les environnements de developpement dans l'appreciation des risques et en assurer la protection conformement au present referentiel." + } + ], + "Checks": [] + }, + { + "Id": "14.5", + "Description": "Le prestataire doit superviser et surveiller l'activite de developpement externalise du systeme.", + "Name": "Developpement externalise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de superviser et de controler l'activite de developpement externalise des logiciels et des systemes. Cette procedure doit s'assurer que l'activite de developpement externalise soit conforme a la politique de developpement securise du prestataire et permette d'atteindre un niveau de securite du developpement externe equivalent a celui d'un developpement interne (voir exigence 14.1 a))." + } + ], + "Checks": [] + }, + { + "Id": "14.6", + "Description": "Des tests de securite et de conformite doivent etre effectues tout au long du cycle de developpement et apres chaque changement significatif.", + "Name": "Test de la securite et conformite du systeme", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "artifacts", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit soumettre les systemes d'information, nouveaux ou mis a jour, a des tests de conformite et de fonctionnalite de securite pendant le developpement. Il doit documenter et mettre en oeuvre une procedure de test qui identifie : les taches a realiser ; les donnees d'entree ; les resultats attendus en sortie." + } + ], + "Checks": [ + "artifacts_container_analysis_enabled", + "gcr_container_scanning_enabled" + ] + }, + { + "Id": "14.7", + "Description": "Les donnees de test doivent etre soigneusement selectionnees, protegees et controlees.", + "Name": "Protection des donnees de test", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'assurer l'integrite des donnees de tests utilises en pre-production. b) Si le prestataire souhaite utiliser des donnees du commanditaire issues de la production pour realiser des tests, le prestataire doit prealablement obtenir l'accord du commanditaire et les anonymiser. Le prestataire doit assurer la confidentialite des donnees lors de leur anonymisation." + } + ], + "Checks": [] + }, + { + "Id": "15.1", + "Description": "Le prestataire doit identifier les tiers ayant acces a l'information ou aux moyens de traitement de l'information et evaluer les risques associes.", + "Name": "Identification des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit tenir a jour une liste exhaustive des tiers participant a la mise en oeuvre du service (hebergeur, developpeur, integrateur, archiveur, sous-traitant operant sur site ou a distance, fournisseurs de climatisation, etc.). Cette liste doit preciser la contribution du tiers au service et au traitement des donnees a caractere personnel. Elle doit tenir compte des cas de sous-traitance a plusieurs niveaux. b) Le prestataire doit tenir a disposition du commanditaire la liste de l'ensemble des tiers qui peuvent acceder aux donnees et l'informer de tout changement de sous-traitants au sens de l'article 28 du [RGPD] afin que le commanditaire puisse emettre des objections a cet egard." + } + ], + "Checks": [] + }, + { + "Id": "15.2", + "Description": "Tous les aspects pertinents de la securite de l'information doivent etre traites dans les accords conclus avec les tiers.", + "Name": "La securite dans les accords conclus avec les tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit exiger des tiers participant a la mise en oeuvre du service, dans leur contribution au service, un niveau de securite au moins equivalent a celui qu'il s'engage a maintenir dans sa propre politique de securite. Il doit le faire au travers d'exigences, adaptees a chaque tiers et a sa contribution au service, dans les cahiers des charges ou dans les clauses de securite des accords de partenariat. Le prestataire doit inclure ces exigences dans les contrats conclus avec les tiers. b) Le prestataire doit contractualiser, avec chacun des tiers participant a la mise en oeuvre du service, des clauses d'audit permettant a un organisme de qualification de verifier que ces tiers respectent les exigences du present referentiel. c) Le prestataire doit definir et attribuer les roles et les responsabilites relatives a la modification ou a la fin du contrat le liant a un tiers participant a la mise en oeuvre du service." + } + ], + "Checks": [] + }, + { + "Id": "15.3", + "Description": "Le prestataire doit surveiller, revoir et auditer a intervalles reguliers la prestation des services des tiers.", + "Name": "Surveillance et revue des services des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de controler regulierement les mesures mises en place par les tiers participant a la mise en oeuvre du service pour respecter les exigences du present referentiel, conformement au chapitre 18.3." + } + ], + "Checks": [] + }, + { + "Id": "15.4", + "Description": "Les changements dans les services des tiers, incluant le maintien et l'amelioration des politiques, procedures et mesures existantes de securite de l'information, doivent etre geres.", + "Name": "Gestion des changements apportes dans les services des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de suivi des changements apportes par les tiers participant a la mise en oeuvre du service susceptibles d'affecter le niveau de securite du systeme d'information du service. b) Dans la mesure ou un changement de tiers participant a la mise en oeuvre du service affecte le niveau de securite du service, le prestataire doit en informer l'ensemble des commanditaires sans delais conformement au chapitre 12.2 et mettre en oeuvre les mesures permettant de retablir le niveau de securite precedent." + } + ], + "Checks": [] + }, + { + "Id": "15.5", + "Description": "Les personnes intervenant dans le cadre du service cloud doivent etre soumises a des engagements de confidentialite.", + "Name": "Engagements de confidentialite", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de reviser au moins annuellement les exigences en matiere d'engagements de confidentialite ou de non-divulgation vis-a-vis des tiers participant a la mise en oeuvre du service." + } + ], + "Checks": [] + }, + { + "Id": "16.1", + "Description": "Des responsabilites et des procedures de gestion doivent etre etablies pour garantir une reponse rapide, efficace et ordonnee aux incidents lies a la securite de l'information.", + "Name": "Responsabilites et procedures", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'apporter des reponses rapides et efficaces aux incidents de securite. Ces procedures doivent definir les moyens et delais de communication des incidents de securite a l'ensemble des commanditaires concernes ainsi que le niveau de confidentialite exige pour cette communication. b) Le prestataire doit informer ses employes et l'ensemble des tiers participant a la mise en oeuvre du service de cette procedure. c) Le prestataire doit documenter toute violation de donnees a caractere personnel et en informer son commanditaire. La violation doit etre notifiee a la CNIL si elle presente un risque pour les droits et libertes des personnes concernees. Elle doit faire l'objet d'une information aupres des personnes concernees lorsque le risque pour leur vie privee est eleve." + } + ], + "Checks": [] + }, + { + "Id": "16.2", + "Description": "Les evenements lies a la securite de l'information doivent etre signales dans les meilleurs delais par les voies hierarchiques appropriees. Des mecanismes de detection et de notification automatises doivent etre mis en oeuvre.", + "Name": "Signalements lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "iam", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure exigeant de ses employes et des tiers participant a la mise en oeuvre du service qu'ils lui rendent compte de tout incident de securite, avere ou suspecte ainsi que de toute faille de securite. b) Le prestataire doit documenter et mettre en oeuvre une procedure permettant a l'ensemble des commanditaires de signaler tout incident de securite, avere ou suspecte et toute faille de securite. c) Le prestataire doit communiquer sans delai aux commanditaires les incidents de securite et les preconisations associees pour en limiter les impacts. Il doit permettre au commanditaire de choisir les niveaux de gravite des incidents pour lesquels il souhaite etre informe. d) Le prestataire doit communiquer les incidents de securite aux autorites competentes conformement aux exigences legales et reglementaires en vigueur." + } + ], + "Checks": [ + "iam_organization_essential_contacts_configured" + ] + }, + { + "Id": "16.3", + "Description": "Les evenements lies a la securite de l'information doivent etre apprecies et il doit etre decide s'il est necessaire de les classer comme incidents lies a la securite de l'information.", + "Name": "Appreciation des evenements et prise de decision", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "guardduty", + "Type": "Manual", + "Comment": "a) Le prestataire doit apprecier les evenements lies a la securite de l'information et decider s'il faut les qualifier en incidents de securite. Pour l'appreciation, il doit s'appuyer sur une ou plusieurs echelles (estimation, evaluation, etc.) partagees avec le commanditaire. Note : Les incidents de securite incluent les violations de donnees a caractere personnel. b) Le prestataire doit utiliser une classification permettant d'identifier clairement les incidents de securite touchant des donnees relatives aux commanditaires, conformement aux resultats de l'appreciation des risques. Cette classification doit inclure les violations de donnees a caractere personnel." + } + ], + "Checks": [] + }, + { + "Id": "16.4", + "Description": "Les incidents lies a la securite de l'information doivent etre traites conformement aux procedures documentees.", + "Name": "Reponse aux incidents lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit traiter les incidents de securite jusqu'a leur resolution et doit informer les commanditaires conformement aux procedures." + } + ], + "Checks": [] + }, + { + "Id": "16.5", + "Description": "Les connaissances acquises lors de l'analyse et du traitement des incidents lies a la securite de l'information doivent etre exploitees pour reduire la probabilite ou l'impact d'incidents futurs.", + "Name": "Tirer des enseignements des incidents lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus d'amelioration continue afin de diminuer l'occurrence et l'impact de types d'incidents de securite deja traites." + } + ], + "Checks": [] + }, + { + "Id": "16.6", + "Description": "Le prestataire doit definir et appliquer des procedures pour l'identification, le recueil, l'acquisition et la preservation de preuves. Les journaux d'audit doivent etre proteges et valides.", + "Name": "Recueil de preuves", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "iam", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'enregistrer les informations relatives aux incidents de securite et pouvant servir d'elements de preuve." + } + ], + "Checks": [ + "iam_audit_logs_enabled", + "cloudstorage_bucket_log_retention_policy_lock" + ] + }, + { + "Id": "17.1", + "Description": "Le prestataire doit determiner ses exigences en matiere de securite de l'information et de continuite du management de la securite de l'information dans des situations defavorables, par exemple lors d'une crise ou d'un sinistre.", + "Name": "Organisation de la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre oeuvre un plan de continuite d'activite prenant en compte la securite de l'information. b) Le prestataire doit reviser annuellement le plan de continuite d'activite du service et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "17.2", + "Description": "Le prestataire doit etablir, documenter, mettre en oeuvre et maintenir des processus, des procedures et des mesures de controle pour assurer le niveau requis de continuite de la securite de l'information au cours d'une situation defavorable. Les services doivent etre deployes en multi-AZ.", + "Name": "Mise en oeuvre de la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "compute", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des procedures permettant de maintenir ou de restaurer l'exploitation du service et d'assurer la disponibilite des informations au niveau et dans les delais pour lesquels le prestataire s'est engage vis-a-vis du commanditaire dans la convention de service." + } + ], + "Checks": [ + "compute_instance_group_multiple_zones", + "compute_instance_group_autohealing_enabled", + "compute_instance_automatic_restart_enabled", + "compute_instance_on_host_maintenance_migrate" + ] + }, + { + "Id": "17.3", + "Description": "Le prestataire doit verifier a intervalles reguliers les mesures de continuite de la securite de l'information mises en oeuvre afin de s'assurer qu'elles sont valables et efficaces dans des situations defavorables.", + "Name": "Verifier, revoir et evaluer la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester le plan de continuite d'activites afin de s'assurer qu'il est pertinent et efficace en situation de crise." + } + ], + "Checks": [] + }, + { + "Id": "17.4", + "Description": "Les moyens de traitement de l'information doivent etre mis en oeuvre avec suffisamment de redondance pour repondre aux exigences de disponibilite. Les mecanismes de protection contre la suppression accidentelle doivent etre actives.", + "Name": "Disponibilite des moyens de traitement de l'information", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "compute", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures qui lui permettent de repondre au besoin de disponibilite du service defini dans la convention de service (voir chapitre 19.1)." + } + ], + "Checks": [ + "compute_instance_group_multiple_zones", + "compute_instance_deletion_protection_enabled", + "compute_instance_group_autohealing_enabled", + "compute_instance_disk_auto_delete_disabled" + ] + }, + { + "Id": "17.5", + "Description": "La configuration de l'infrastructure technique du service cloud doit etre sauvegardee regulierement afin de permettre sa restauration en cas de sinistre.", + "Name": "Sauvegarde de la configuration de l'infrastructure technique", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "cloudsql", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de sauvegarde hors-ligne de la configuration de l'infrastructure technique." + } + ], + "Checks": [ + "cloudsql_instance_automated_backups", + "iam_cloud_asset_inventory_enabled" + ] + }, + { + "Id": "17.6", + "Description": "Le prestataire doit mettre a disposition du commanditaire un dispositif de sauvegarde de ses donnees, permettant la restauration en cas de sinistre.", + "Name": "Mise a disposition d'un dispositif de sauvegarde des donnees du commanditaire", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "cloudsql", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre a disposition du commanditaire un service de sauvegarde de ses donnees." + } + ], + "Checks": [ + "cloudsql_instance_automated_backups", + "cloudstorage_bucket_versioning_enabled", + "cloudstorage_bucket_soft_delete_enabled" + ] + }, + { + "Id": "18.1", + "Description": "Toutes les exigences legales, reglementaires et contractuelles en vigueur, ainsi que l'approche du prestataire pour satisfaire ces exigences, doivent etre explicitement definies, documentees et tenues a jour pour chaque systeme d'information et pour le prestataire.", + "Name": "Identification de la legislation et des exigences contractuelles applicables", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les exigences legales, reglementaires et contractuelles en vigueur applicables au service. En France, le prestataire doit considerer au minimum les textes suivants : les donnees a caractere personnel [LOI_IL], [RGPD] ; le secret professionnel [CP_ART_226_13], le cas echeant sans prejudice de l'application de l'article 40 alinea 2 du Code de procedure penale relatif au signalement a une autorite judiciaire ; l'abus de confiance [CP_ART_314-1] ; le secret des correspondances privees [CP_ART_226-15] ; l'atteinte a la vie privee [CP_ART_226-1] ; l'acces ou le maintien frauduleux a un systeme d'information [CP_ART_323-1]. b) Le prestataire doit, selon son role dans les traitements de donnees a caractere personnel (responsable de traitement, sous-traitant ou co-responsable) justifier et documenter les choix de mesures techniques et organisationnelles realises en vue de repondre aux exigences de protection des donnees a caractere personnel du present referentiel (voir partie 19.5). c) Le prestataire doit documenter et mettre en oeuvre les procedures permettant de respecter les exigences legales, reglementaires et contractuelles en vigueur applicables au service, ainsi que les besoins de securite specifiques (voir exigence 8.3b)). d) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible l'ensemble de ces procedures. e) Le prestataire doit documenter et mettre en oeuvre un processus de veille actif des exigences legales, reglementaires et contractuelles en vigueur applicables au service." + } + ], + "Checks": [] + }, + { + "Id": "18.2", + "Description": "L'approche du prestataire vis-a-vis de la gestion de la securite de l'information et sa mise en oeuvre (c'est-a-dire les objectifs de controle, les mesures, les politiques, les procedures et les processus relatifs a la securite de l'information) doivent etre revues de maniere independante a intervalles definis ou en cas de changement significatif.", + "Name": "Revue independante de la securite de l'information", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un programme d'audit sur trois ans definissant le perimetre et la frequence des audits en accord avec la gestion du changement, les politiques, et les resultats de l'appreciation des risques. Le prestataire doit inclure dans le programme d'audit un audit qualifie par an realise par un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie. L'ensemble du programme d'audit doit notamment couvrir : l'audit de la configuration de l'infrastructure technique du service (par echantillonnage et doit inclure tous types d'equipements et de serveurs presents dans le systeme d'information du service) ; le test d'intrusion des interfaces d'administration exposees sur un reseau public ; le test d'intrusion de l'interface utilisateur pour les services SaaS ; si le service beneficie de developpements internes, l'audit de code source portant sur les fonctionnalites de securite implementees (l'approche en continue doit etre privilegiee). b) Il est recommande que le prestataire mette en oeuvre des mecanismes automatises d'audit de la configuration adaptes a l'infrastructure technique du service." + } + ], + "Checks": [] + }, + { + "Id": "18.3", + "Description": "Les responsables doivent regulierement s'assurer de la conformite du traitement de l'information et des procedures au sein de leur domaine de responsabilite, au regard des politiques et des normes de securite.", + "Name": "Conformite avec les politiques et les normes de securite", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "iam", + "Type": "Partially Automated", + "Comment": "a) Le prestataire via le responsable de la securite de l'information doit s'assurer regulierement de l'execution correcte de l'ensemble des procedures de securite placees sous sa responsabilite en vue de garantir leur conformite avec les politiques et normes de securite." + } + ], + "Checks": [ + "iam_cloud_asset_inventory_enabled", + "iam_audit_logs_enabled" + ] + }, + { + "Id": "18.4", + "Description": "Les systemes d'information doivent etre examines regulierement quant a leur conformite avec les politiques et les normes de securite de l'information du prestataire.", + "Name": "Examen de la conformite technique", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "artifacts", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique permettant de verifier la conformite technique du service aux exigences du present referentiel. Cette politique doit definir les objectifs, methodes, frequences, resultats attendus et mesures correctrices." + } + ], + "Checks": [ + "artifacts_container_analysis_enabled", + "gcr_container_scanning_enabled" + ] + }, + { + "Id": "19.1", + "Description": "Le prestataire doit etablir une convention de service avec le commanditaire definissant les engagements de niveau de service, les responsabilites et les conditions d'utilisation du service cloud.", + "Name": "Convention de service", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit etablir une convention de service avec chacun des commanditaires du service. Toute modification de la convention de service doit etre soumise a acceptation du commanditaire. b) Le prestataire doit identifier dans la convention de service : les obligations, droits et responsabilites de chacune des parties : prestataire et tiers impliques dans la fourniture du service, commanditaires, etc. ; les elements explicitement exclus des responsabilites du prestataire dans la limite de ce que prevoient les exigences legales et reglementaires en vigueur, notamment l'article 28 du [RGPD] ; la localisation du service. La localisation du support doit etre precisee lorsqu'il est realise depuis un Etat hors l'Union Europeenne, comme le permet l'exigence 19.2.e. c) Le prestataire doit proposer une convention de service appliquant le droit d'un Etat membre de l'Union Europeenne. Le droit applicable doit etre identifie dans la convention de service. d) La convention de service doit indiquer que la collecte, la manipulation, le stockage, et plus generalement le traitement des donnees faits dans le cadre de l'avant-vente, de la mise en oeuvre, de la maintenance et l'arret du service sont realises conformement aux exigences edictees par la legislation en vigueur. e) La convention de service doit indiquer que le prestataire doit mettre a la disposition du commanditaire, sur demande de celui-ci, les elements d'appreciation des risques lies a la soumission des donnees du commanditaire au droit d'un etat non-membre de l'Union Europeenne (voir 5.3.e). f) Le prestataire doit decrire dans la convention de service les moyens techniques et organisationnels qu'il met en oeuvre pour assurer le respect du droit applicable. g) Le prestataire doit inclure dans la convention de service une clause de revision de la convention prevoyant notamment une resiliation sans penalite pour le commanditaire en cas de perte de la qualification octroyee au service. h) Le prestataire doit inclure dans la convention de service une clause de reversibilite permettant au commanditaire de recuperer l'ensemble de ses donnees (fournies directement par le commanditaire ou produites dans le cadre du service a partir des donnees ou des actions du commanditaire). i) Le prestataire doit assurer cette reversibilite via l'une des modalites techniques suivantes : la mise a disposition de fichiers suivant un ou plusieurs formats documentes et exploitables en dehors du service fourni par le prestataire ; la mise en place d'interfaces techniques permettant l'acces aux donnees suivant un schema documente et exploitable (API, format pivot, etc.). Les modalites techniques de la reversibilite figurent dans la convention de service. j) Le prestataire doit indiquer dans la convention de service le niveau de disponibilite du service. k) Le prestataire doit indiquer dans la convention de service qu'il ne peut disposer des donnees transmises et generees par le commanditaire, leur disposition etant reservee au commanditaire. l) Le prestataire doit indiquer dans la convention de service qu'il ne divulgue aucune information relative a la prestation a des tiers, sauf autorisation formelle et ecrite du commanditaire. m) Le prestataire doit indiquer dans la convention de service si les donnees du commanditaire sont automatiquement sauvegardees ou non. Dans la negative, le prestataire doit sensibiliser le commanditaire aux risques encourus et clairement indiquer les operations a mener par le commanditaire pour que ses donnees soient sauvegardees. n) Le prestataire doit indiquer dans la convention de service s'il autorise l'acces distant pour des actions d'administration ou de support au systeme d'information du service. o) Le prestataire doit preciser dans la convention de service que : le service est qualifie et inclure l'attestation de qualification ; le commanditaire peut deposer une reclamation relative au service qualifie aupres de l'ANSSI ; le commanditaire autorise l'ANSSI et l'organisme de qualification a auditer le service et son systeme d'information du service afin de verifier qu'ils respectent les exigences du present referentiel. p) Le prestataire doit preciser dans la convention de service que le commanditaire autorise, conformement au present referentiel (voir chapitre 18.2, un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie mandate par le prestataire a auditer le service et son systeme d'information dans le cadre du plan de controle. q) Le prestataire doit preciser dans la convention de service qu'il s'engage a mettre a disposition toutes les informations necessaires a la realisation d'audits de conformite aux dispositions de l'article 28 du [RGPD], menes par le commanditaire ou un tiers mandate. r) Il est recommande que le tiers mandate pour les audits soit un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie." + } + ], + "Checks": [] + }, + { + "Id": "19.2", + "Description": "Les donnees du commanditaire doivent etre stockees et traitees dans des centres de donnees situes sur le territoire de l'Union europeenne. Les politiques de restriction de region doivent etre appliquees.", + "Name": "Localisation des donnees", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "organizations", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et communiquer au commanditaire la localisation du stockage et du traitement des donnees de ce dernier. b) Le prestataire doit stocker et traiter les donnees du commanditaire au sein de l'Union Europeenne. c) Les operations d'administration et de supervision du service doivent etre realisees depuis le territoire de l'Union Europeenne. d) Le prestataire doit stocker et traiter les donnees techniques (identites des beneficiaires et des administrateurs de l'infrastructure technique, donnees manipulees par le Software Defined Network, journaux de l'infrastructure technique, annuaire, certificats, configuration des acces, etc.) au sein de l'Union Europeenne. e) Le prestataire peut realiser des operations de support aux commanditaires depuis un Etat hors de l'Union Europeenne. Il doit documenter la liste des operations qui peuvent etre effectuees par le support au commanditaire depuis un Etat hors de l'Union Europeenne, et les mecanismes permettant d'en assurer le controle d'acces et la supervision depuis l'Union Europeenne." + } + ], + "Checks": [] + }, + { + "Id": "19.3", + "Description": "Les services cloud qualifies SecNumCloud doivent etre operes depuis le territoire de l'Union europeenne.", + "Name": "Regionalisation", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit s'assurer que les interfaces du service accessibles au commanditaire soient au moins disponibles en langue francaise. b) Le prestataire doit fournir un support de premier niveau en langue francaise." + } + ], + "Checks": [] + }, + { + "Id": "19.4", + "Description": "Le prestataire doit definir les conditions de fin de contrat, incluant les modalites de restitution et de suppression des donnees du commanditaire.", + "Name": "Fin de contrat", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) A la fin du contrat liant le prestataire et le commanditaire, que le contrat soit arrive a son terme ou pour toute autre cause, le prestataire doit assurer un effacement securise de l'integralite des donnees du commanditaire. Cet effacement doit faire l'objet d'un preavis formel au commanditaire de la part du prestataire respectant un delai de vingt et un jours calendaires. L'effacement peut etre realise suivant l'une des methodes suivantes, et ce dans un delai precise dans la convention de service : effacement par reecriture complete de tout support ayant heberge ces donnees ; effacement des cles utilisees pour le chiffrement des espaces de stockage du commanditaire decrit au chapitre 10.1 ; recyclage securise, dans les conditions enoncees au chapitre 11.9. b) A la fin du contrat, le prestataire doit supprimer les donnees techniques relatives au commanditaire (annuaire, certificats, configuration des acces, etc.)." + } + ], + "Checks": [] + }, + { + "Id": "19.5", + "Description": "Le prestataire doit mettre en oeuvre des mesures techniques et organisationnelles appropriees pour garantir la protection des donnees a caractere personnel conformement a la reglementation en vigueur.", + "Name": "Protection des donnees a caractere personnel", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit justifier du respect des principes de protection des donnees pour les traitements de donnees a caractere personnel mis en oeuvre pour son propre compte. Il doit justifier au minimum les points suivants : les finalites des traitements determinees, explicites et legitimes ; la tracabilite des activites de traitement pour son compte et celui de son commanditaire ; le fondement licite des traitements ; l'interdiction du detournement de finalite des traitements ; les donnees utilisees respectent le principe du minimum necessaire et suffisant pour les traitements ; ainsi sont adequates, pertinentes et limitees ; la qualite des donnees utilisees pour les traitements maintenue : donnees exactes et tenues a jour ; les durees de conservation definies et limitees. b) Le prestataire doit justifier, pour les traitements de donnees a caractere personnel mis en oeuvre pour son propre compte, du respect des droits des personnes concernees. Il doit justifier au minimum les points suivants : l'information des usagers via un traitement loyal et transparent ; le recueil du consentement des usagers : expres, demontrable et retirable ; la possibilite pour les usagers d'exercer les droits d'acces, de rectification et d'effacement ; la possibilite pour les usagers d'exercer les droits de limitation du traitement, de portabilite et d'opposition. c) Lorsqu'il agit en qualite de sous-traitant au sens de l'article 28 de [RGPD], le prestataire doit apporter assistance et conseil au commanditaire en l'informant si une instruction de ce dernier constitue une violation des regles de protection des donnees." + } + ], + "Checks": [] + }, + { + "Id": "19.6", + "Description": "Le prestataire doit mettre en oeuvre des mesures de protection vis-a-vis du droit extra-europeen, afin de garantir que les donnees du commanditaire ne puissent etre soumises a des legislations extra-europeennes.", + "Name": "Protection vis-a-vis du droit extra-europeen", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le siege statutaire, administration centrale et principal etablissement du prestataire doivent etre etablis au sein d'un Etat membre de l'Union Europeenne. b) Le capital social et les droits de vote dans la societe du prestataire ne doivent pas etre, directement ou indirectement : individuellement detenus a plus de 24% ; et collectivement detenus a plus de 39% ; par des entites tierces possedant leur siege statutaire, administration centrale ou principal etablissement au sein d'un Etat non membre de l'Union europeenne. Ces entites tierces susmentionnees ne peuvent pas individuellement ou collectivement : en vertu d'un contrat ou de clauses statutaires, disposer d'un droit de veto ; en vertu d'un contrat ou de clauses statutaires, designer la majorite des membres des organes d'administration, de direction ou de surveillance du prestataire. c) En cas de recours par le prestataire, dans le cadre des services fournis au commanditaire, aux services d'une societe tierce - y compris un sous-traitant - possedant son siege statutaire, administration centrale ou principal etablissement au sein d'un Etat non membre de l'Union Europeenne ou appartenant ou etant controlee par une societe tierce domiciliee en dehors l'Union Europeenne, cette susdite societe tierce ne doit pas avoir la possibilite technique d'obtenir les donnees operees au travers du service. d) Dans le cadre de l'exigence 19.6.c, toute societe tierce a laquelle le prestataire recourt pour fournir tout ou partie du service rendu au commanditaire, doit garantir au prestataire une autonomie d'exploitation continue dans la fourniture des services d'informatique en nuage qu'il opere ou doit etre qualifie SecNumCloud. e) Le service fourni par le prestataire doit respecter la legislation en vigueur en matiere de droits fondamentaux et les valeurs de l'Union relatives au respect de la dignite humaine, a la liberte, a l'egalite, a la democratie et a l'Etat de droit. f) Le prestataire doit informer formellement le commanditaire, et dans un delai d'un mois, de tout changement juridique, organisationnel ou technique pouvant avoir un impact sur la conformite de la prestation aux exigences du chapitre 19.6." + } + ], + "Checks": [] + } + ] +} From 65e745d77930cd076e7e4a6826356addf371f709 Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:11:39 +0100 Subject: [PATCH 025/366] fix(sdk): skip strict CheckMetadata validators for external tool providers (#10363) --- prowler/CHANGELOG.md | 1 + prowler/lib/check/models.py | 65 ++--- prowler/providers/image/image_provider.py | 7 +- tests/lib/check/models_test.py | 246 +++++++++++++++++++ tests/providers/image/image_provider_test.py | 2 +- 5 files changed, 289 insertions(+), 32 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index d84169771f..b8cc8b8f04 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -31,6 +31,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Route53 dangling IP check false positive when using `--region` flag [(#9952)](https://github.com/prowler-cloud/prowler/pull/9952) - RBI compliance framework support on Prowler Dashboard for the Azure provider [(#10360)](https://github.com/prowler-cloud/prowler/pull/10360) +- CheckMetadata strict validators rejecting valid external tool provider data (image, iac, llm) [(#10363)](https://github.com/prowler-cloud/prowler/pull/10363) --- diff --git a/prowler/lib/check/models.py b/prowler/lib/check/models.py index 6711026023..524248d26f 100644 --- a/prowler/lib/check/models.py +++ b/prowler/lib/check/models.py @@ -245,7 +245,7 @@ class CheckMetadata(BaseModel): Compliance: Optional[list[Any]] = Field(default_factory=list) @validator("Categories", each_item=True, pre=True, always=True) - def valid_category(value): + def valid_category(cls, value, values): if not isinstance(value, str): raise ValueError("Categories must be a list of strings") value_lower = value.lower() @@ -253,7 +253,10 @@ class CheckMetadata(BaseModel): raise ValueError( f"Invalid category: {value}. Categories can only contain lowercase letters, numbers and hyphen '-'" ) - if value_lower not in VALID_CATEGORIES: + if ( + value_lower not in VALID_CATEGORIES + and values.get("Provider") not in EXTERNAL_TOOL_PROVIDERS + ): raise ValueError( f"Invalid category: '{value_lower}'. Must be one of: {', '.join(sorted(VALID_CATEGORIES))}." ) @@ -306,30 +309,32 @@ class CheckMetadata(BaseModel): return check_id @validator("CheckTitle", pre=True, always=True) - def validate_check_title(cls, check_title): - if len(check_title) > 150: - raise ValueError( - f"CheckTitle must not exceed 150 characters, got {len(check_title)} characters" - ) - if check_title.startswith("Ensure"): - raise ValueError( - "CheckTitle must not start with 'Ensure'. Use a descriptive title that focuses on the security state." - ) + def validate_check_title(cls, check_title, values): + if values.get("Provider") not in EXTERNAL_TOOL_PROVIDERS: + if len(check_title) > 150: + raise ValueError( + f"CheckTitle must not exceed 150 characters, got {len(check_title)} characters" + ) + if check_title.startswith("Ensure"): + raise ValueError( + "CheckTitle must not start with 'Ensure'. Use a descriptive title that focuses on the security state." + ) return check_title @validator("RelatedUrl", pre=True, always=True) - def validate_related_url(cls, related_url): - if related_url: + def validate_related_url(cls, related_url, values): + if related_url and values.get("Provider") not in EXTERNAL_TOOL_PROVIDERS: raise ValueError("RelatedUrl must be empty. This field is deprecated.") return related_url @validator("Remediation") - def validate_recommendation_url(remediation): - url = remediation.Recommendation.Url - if url and not url.startswith("https://hub.prowler.com/"): - raise ValueError( - f"Remediation Recommendation URL must point to Prowler Hub (https://hub.prowler.com/...), got '{url}'." - ) + def validate_recommendation_url(cls, remediation, values): + if values.get("Provider") not in EXTERNAL_TOOL_PROVIDERS: + url = remediation.Recommendation.Url + if url and not url.startswith("https://hub.prowler.com/"): + raise ValueError( + f"Remediation Recommendation URL must point to Prowler Hub (https://hub.prowler.com/...), got '{url}'." + ) return remediation @validator("CheckType", pre=True, always=True) @@ -362,19 +367,21 @@ class CheckMetadata(BaseModel): return check_type @validator("Description", pre=True, always=True) - def validate_description(cls, description): - if len(description) > 400: - raise ValueError( - f"Description must not exceed 400 characters, got {len(description)} characters" - ) + def validate_description(cls, description, values): + if values.get("Provider") not in EXTERNAL_TOOL_PROVIDERS: + if len(description) > 400: + raise ValueError( + f"Description must not exceed 400 characters, got {len(description)} characters" + ) return description @validator("Risk", pre=True, always=True) - def validate_risk(cls, risk): - if len(risk) > 400: - raise ValueError( - f"Risk must not exceed 400 characters, got {len(risk)} characters" - ) + def validate_risk(cls, risk, values): + if values.get("Provider") not in EXTERNAL_TOOL_PROVIDERS: + if len(risk) > 400: + raise ValueError( + f"Risk must not exceed 400 characters, got {len(risk)} characters" + ) return risk @validator("ResourceGroup", pre=True, always=True) diff --git a/prowler/providers/image/image_provider.py b/prowler/providers/image/image_provider.py index 4beb5eca4e..d724542e28 100644 --- a/prowler/providers/image/image_provider.py +++ b/prowler/providers/image/image_provider.py @@ -383,7 +383,7 @@ class ImageProvider(Provider): "Description", finding.get("Title", "") ) finding_status = "FAIL" - finding_categories = ["vulnerability"] + finding_categories = ["vulnerabilities"] elif "RuleID" in finding: # Secret finding finding_id = finding["RuleID"] @@ -433,10 +433,13 @@ class ImageProvider(Provider): }, "Recommendation": { "Text": remediation_text, - "Url": finding.get("PrimaryURL", ""), + "Url": "", }, }, "Categories": finding_categories, + "AdditionalURLs": ( + [url] if (url := finding.get("PrimaryURL", "")) else [] + ), "DependsOn": [], "RelatedTo": [], "Notes": "", diff --git a/tests/lib/check/models_test.py b/tests/lib/check/models_test.py index 8efa307c54..815479cdfa 100644 --- a/tests/lib/check/models_test.py +++ b/tests/lib/check/models_test.py @@ -2418,3 +2418,249 @@ class TestCheck: msg = str(excinfo.value) assert "!= class name" in msg assert "!= file name" in msg + + +class TestExternalToolProviderValidatorBypass: + """Validators skip strict rules for external tool providers (image, iac, llm).""" + + EXTERNAL_METADATA_BASE = { + "Provider": "image", + "CheckID": "CVE-2024-1234", + "CheckTitle": "OpenSSL Buffer Overflow", + "CheckType": ["Container Image Security"], + "ServiceName": "container-image", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "container-image", + "ResourceGroup": "container", + "Description": "A buffer overflow vulnerability.", + "Risk": "Remote code execution.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "", + }, + "Recommendation": { + "Text": "Upgrade openssl", + "Url": "https://avd.aquasec.com/nvd/cve-2024-1234", + }, + }, + "Categories": ["vulnerability"], + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + } + + def test_external_provider_allows_non_hub_recommendation_url(self): + metadata = CheckMetadata(**self.EXTERNAL_METADATA_BASE) + assert ( + metadata.Remediation.Recommendation.Url + == "https://avd.aquasec.com/nvd/cve-2024-1234" + ) + + def test_native_provider_rejects_non_hub_recommendation_url(self): + data = { + **self.EXTERNAL_METADATA_BASE, + "Provider": "azure", + "CheckID": "test_check", + "ServiceName": "test", + "CheckType": [], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "", + }, + "Recommendation": { + "Text": "Fix it", + "Url": "https://avd.aquasec.com/nvd/cve-2024-1234", + }, + }, + } + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**data) + assert "Prowler Hub" in str(exc_info.value) + + def test_external_provider_allows_long_description(self): + data = {**self.EXTERNAL_METADATA_BASE, "Description": "A" * 500} + metadata = CheckMetadata(**data) + assert len(metadata.Description) == 500 + + def test_native_provider_rejects_long_description(self): + data = { + **self.EXTERNAL_METADATA_BASE, + "Provider": "azure", + "CheckID": "test_check", + "ServiceName": "test", + "CheckType": [], + "Categories": ["encryption"], + "Description": "A" * 401, + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "", + }, + "Recommendation": { + "Text": "", + "Url": "", + }, + }, + } + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**data) + assert "Description must not exceed 400 characters" in str(exc_info.value) + + def test_external_provider_allows_long_risk(self): + data = {**self.EXTERNAL_METADATA_BASE, "Risk": "R" * 500} + metadata = CheckMetadata(**data) + assert len(metadata.Risk) == 500 + + def test_native_provider_rejects_long_risk(self): + data = { + **self.EXTERNAL_METADATA_BASE, + "Provider": "azure", + "CheckID": "test_check", + "ServiceName": "test", + "CheckType": [], + "Categories": ["encryption"], + "Risk": "R" * 401, + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "", + }, + "Recommendation": { + "Text": "", + "Url": "", + }, + }, + } + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**data) + assert "Risk must not exceed 400 characters" in str(exc_info.value) + + def test_external_provider_allows_long_check_title(self): + data = {**self.EXTERNAL_METADATA_BASE, "CheckTitle": "T" * 200} + metadata = CheckMetadata(**data) + assert len(metadata.CheckTitle) == 200 + + def test_native_provider_rejects_long_check_title(self): + data = { + **self.EXTERNAL_METADATA_BASE, + "Provider": "azure", + "CheckID": "test_check", + "ServiceName": "test", + "CheckType": [], + "Categories": ["encryption"], + "CheckTitle": "T" * 151, + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "", + }, + "Recommendation": { + "Text": "", + "Url": "", + }, + }, + } + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**data) + assert "CheckTitle must not exceed 150 characters" in str(exc_info.value) + + def test_external_provider_allows_non_standard_category(self): + data = {**self.EXTERNAL_METADATA_BASE, "Categories": ["vulnerability"]} + metadata = CheckMetadata(**data) + assert metadata.Categories == ["vulnerability"] + + def test_native_provider_rejects_non_standard_category(self): + data = { + **self.EXTERNAL_METADATA_BASE, + "Provider": "azure", + "CheckID": "test_check", + "ServiceName": "test", + "CheckType": [], + "Categories": ["vulnerability"], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "", + }, + "Recommendation": { + "Text": "", + "Url": "", + }, + }, + } + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**data) + assert "Invalid category" in str(exc_info.value) + + def test_external_provider_allows_ensure_prefix_in_title(self): + data = { + **self.EXTERNAL_METADATA_BASE, + "CheckTitle": "Ensure containers run as non-root", + } + metadata = CheckMetadata(**data) + assert metadata.CheckTitle == "Ensure containers run as non-root" + + def test_external_provider_allows_non_empty_related_url(self): + data = { + **self.EXTERNAL_METADATA_BASE, + "RelatedUrl": "https://avd.aquasec.com/nvd/cve-2024-1234", + } + metadata = CheckMetadata(**data) + assert metadata.RelatedUrl == "https://avd.aquasec.com/nvd/cve-2024-1234" + + def test_native_provider_rejects_non_empty_related_url(self): + data = { + **self.EXTERNAL_METADATA_BASE, + "Provider": "azure", + "CheckID": "test_check", + "ServiceName": "test", + "CheckType": [], + "Categories": ["encryption"], + "RelatedUrl": "https://example.com", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "", + }, + "Recommendation": { + "Text": "", + "Url": "", + }, + }, + } + with pytest.raises(ValidationError) as exc_info: + CheckMetadata(**data) + assert "RelatedUrl must be empty" in str(exc_info.value) + + def test_all_external_providers_bypass(self): + for provider in ("image", "iac", "llm"): + data = { + **self.EXTERNAL_METADATA_BASE, + "Provider": provider, + "Description": "D" * 500, + "Risk": "R" * 500, + "CheckTitle": "T" * 200, + "Categories": ["vulnerability"], + "RelatedUrl": "https://example.com/vuln", + } + metadata = CheckMetadata(**data) + assert metadata.Provider == provider diff --git a/tests/providers/image/image_provider_test.py b/tests/providers/image/image_provider_test.py index ab9358b002..2db7a8fe48 100644 --- a/tests/providers/image/image_provider_test.py +++ b/tests/providers/image/image_provider_test.py @@ -143,7 +143,7 @@ class TestImageProvider: assert report.image_sha == "c1aabb73d233" assert report.resource_details == "alpine:3.18 (alpine 3.18.0)" assert report.region == "container" - assert report.check_metadata.Categories == ["vulnerability"] + assert report.check_metadata.Categories == ["vulnerabilities"] assert report.check_metadata.RelatedUrl == "" def test_process_finding_secret(self): From f5f1f1ab2d83851c4cc939f4758df00c6e6cef8c Mon Sep 17 00:00:00 2001 From: Josema Camacho Date: Wed, 18 Mar 2026 09:49:45 +0100 Subject: [PATCH 026/366] fix(attack-paths): recover graph_data_ready when scan fails during graph swap (#10354) --- api/CHANGELOG.md | 4 + api/src/backend/api/attack_paths/database.py | 35 +- .../api/tests/test_attack_paths_database.py | 75 ++ .../tasks/jobs/attack_paths/db_utils.py | 43 +- .../backend/tasks/jobs/attack_paths/scan.py | 40 +- .../tasks/tests/test_attack_paths_scan.py | 682 +++++++++++++++++- 6 files changed, 843 insertions(+), 36 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 8c5a47ad6b..c7ff2bf99e 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -13,6 +13,10 @@ All notable changes to the **Prowler API** are documented in this file. - Attack Paths: Complete migration to private graph labels and properties, removing deprecated dual-write support [(#10268)](https://github.com/prowler-cloud/prowler/pull/10268) - Attack Paths: Added tenant and provider related labels to the nodes so they can be easily filtered on custom queries [(#10308)](https://github.com/prowler-cloud/prowler/pull/10308) +### 🐞 Fixed + +- Attack Paths: Recover `graph_data_ready` flag when scan fails during graph swap, preventing query endpoints from staying blocked until the next successful scan [(#10354)](https://github.com/prowler-cloud/prowler/pull/10354) + ### 🔐 Security - Use `psycopg2.sql` to safely compose DDL in `PostgresEnumMigration`, preventing SQL injection via f-string interpolation [(#10166)](https://github.com/prowler-cloud/prowler/pull/10166) diff --git a/api/src/backend/api/attack_paths/database.py b/api/src/backend/api/attack_paths/database.py index 25172152e9..02083991ac 100644 --- a/api/src/backend/api/attack_paths/database.py +++ b/api/src/backend/api/attack_paths/database.py @@ -1,26 +1,22 @@ import atexit import logging import threading - -from typing import Any - from contextlib import contextmanager -from typing import Iterator +from typing import Any, Iterator from uuid import UUID import neo4j import neo4j.exceptions - -from django.conf import settings - -from api.attack_paths.retryable_session import RetryableSession from config.env import env +from django.conf import settings from tasks.jobs.attack_paths.config import ( BATCH_SIZE, PROVIDER_ID_PROPERTY, PROVIDER_RESOURCE_LABEL, ) +from api.attack_paths.retryable_session import RetryableSession + # Without this Celery goes crazy with Neo4j logging logging.getLogger("neo4j").setLevel(logging.ERROR) logging.getLogger("neo4j").propagate = False @@ -197,6 +193,29 @@ def drop_subgraph(database: str, provider_id: str) -> int: return deleted_nodes +def has_provider_data(database: str, provider_id: str) -> bool: + """ + Check if any ProviderResource node exists for this provider. + + Returns `False` if the database doesn't exist. + """ + query = ( + f"MATCH (n:{PROVIDER_RESOURCE_LABEL} " + f"{{{PROVIDER_ID_PROPERTY}: $provider_id}}) " + "RETURN 1 LIMIT 1" + ) + + try: + with get_session(database, default_access_mode=neo4j.READ_ACCESS) as session: + result = session.run(query, {"provider_id": provider_id}) + return result.single() is not None + + except GraphDatabaseQueryException as exc: + if exc.code == "Neo.ClientError.Database.DatabaseNotFound": + return False + raise + + def clear_cache(database: str) -> None: query = "CALL db.clearQueryCaches()" diff --git a/api/src/backend/api/tests/test_attack_paths_database.py b/api/src/backend/api/tests/test_attack_paths_database.py index 8b458cb7b7..7e07792a69 100644 --- a/api/src/backend/api/tests/test_attack_paths_database.py +++ b/api/src/backend/api/tests/test_attack_paths_database.py @@ -442,3 +442,78 @@ class TestThreadSafety: # All threads got the same driver instance assert all(r is mock_driver for r in results) assert len(results) == 10 + + +class TestHasProviderData: + """Test has_provider_data helper for checking provider nodes in Neo4j.""" + + def test_returns_true_when_nodes_exist(self): + import api.attack_paths.database as db_module + + mock_session = MagicMock() + mock_result = MagicMock() + mock_result.single.return_value = MagicMock() # non-None record + mock_session.run.return_value = mock_result + + session_ctx = MagicMock() + session_ctx.__enter__.return_value = mock_session + session_ctx.__exit__.return_value = False + + with patch( + "api.attack_paths.database.get_session", + return_value=session_ctx, + ): + assert db_module.has_provider_data("db-tenant-abc", "provider-123") is True + + mock_session.run.assert_called_once() + + def test_returns_false_when_no_nodes(self): + import api.attack_paths.database as db_module + + mock_session = MagicMock() + mock_result = MagicMock() + mock_result.single.return_value = None + mock_session.run.return_value = mock_result + + session_ctx = MagicMock() + session_ctx.__enter__.return_value = mock_session + session_ctx.__exit__.return_value = False + + with patch( + "api.attack_paths.database.get_session", + return_value=session_ctx, + ): + assert db_module.has_provider_data("db-tenant-abc", "provider-123") is False + + def test_returns_false_when_database_not_found(self): + import api.attack_paths.database as db_module + + session_ctx = MagicMock() + session_ctx.__enter__.side_effect = db_module.GraphDatabaseQueryException( + message="Database does not exist", + code="Neo.ClientError.Database.DatabaseNotFound", + ) + + with patch( + "api.attack_paths.database.get_session", + return_value=session_ctx, + ): + assert ( + db_module.has_provider_data("db-tenant-gone", "provider-123") is False + ) + + def test_raises_on_other_errors(self): + import api.attack_paths.database as db_module + + session_ctx = MagicMock() + session_ctx.__enter__.side_effect = db_module.GraphDatabaseQueryException( + message="Connection refused", + code="Neo.TransientError.General.UnknownError", + ) + + with patch( + "api.attack_paths.database.get_session", + return_value=session_ctx, + ): + with pytest.raises(db_module.GraphDatabaseQueryException): + db_module.has_provider_data("db-tenant-abc", "provider-123") diff --git a/api/src/backend/tasks/jobs/attack_paths/db_utils.py b/api/src/backend/tasks/jobs/attack_paths/db_utils.py index 9fb52b0ead..7d17ec07eb 100644 --- a/api/src/backend/tasks/jobs/attack_paths/db_utils.py +++ b/api/src/backend/tasks/jobs/attack_paths/db_utils.py @@ -3,15 +3,13 @@ from typing import Any from cartography.config import Config as CartographyConfig from celery.utils.log import get_task_logger +from tasks.jobs.attack_paths.config import is_provider_available from api.attack_paths import database as graph_database from api.db_utils import rls_transaction -from api.models import ( - AttackPathsScan as ProwlerAPIAttackPathsScan, - Provider as ProwlerAPIProvider, - StateChoices, -) -from tasks.jobs.attack_paths.config import is_provider_available +from api.models import AttackPathsScan as ProwlerAPIAttackPathsScan +from api.models import Provider as ProwlerAPIProvider +from api.models import StateChoices logger = get_task_logger(__name__) @@ -155,6 +153,37 @@ def set_provider_graph_data_ready( attack_paths_scan.refresh_from_db(fields=["graph_data_ready"]) +def recover_graph_data_ready( + attack_paths_scan: ProwlerAPIAttackPathsScan, +) -> None: + """ + Best-effort recovery of `graph_data_ready` after a scan failure. + + Queries Neo4j to check if the provider still has data in the tenant + database. If data exists, restores `graph_data_ready=True` for all scans + of this provider. Never raises. + + Trade-off: if the worker crashed mid-sync, partial data may exist and + this will re-enable queries against it. We accept that because leaving + `graph_data_ready=False` permanently (blocking all queries until the + next successful scan) is a worse outcome for the user. + """ + try: + tenant_db = graph_database.get_database_name(attack_paths_scan.tenant_id) + if graph_database.has_provider_data( + tenant_db, str(attack_paths_scan.provider_id) + ): + set_provider_graph_data_ready(attack_paths_scan, True) + logger.info( + f"Recovered `graph_data_ready` for provider {attack_paths_scan.provider_id}" + ) + + except Exception: + logger.exception( + f"Failed to recover `graph_data_ready` for provider {attack_paths_scan.provider_id}" + ) + + def fail_attack_paths_scan( tenant_id: str, scan_id: str, @@ -185,3 +214,5 @@ def fail_attack_paths_scan( StateChoices.FAILED, {"global_error": error}, ) + + recover_graph_data_ready(attack_paths_scan) diff --git a/api/src/backend/tasks/jobs/attack_paths/scan.py b/api/src/backend/tasks/jobs/attack_paths/scan.py index 6624680a5e..20dba74da8 100644 --- a/api/src/backend/tasks/jobs/attack_paths/scan.py +++ b/api/src/backend/tasks/jobs/attack_paths/scan.py @@ -55,7 +55,6 @@ exception propagates to Celery. import logging import time - from typing import Any from cartography.config import Config as CartographyConfig @@ -63,16 +62,14 @@ from cartography.intel import analysis as cartography_analysis from cartography.intel import create_indexes as cartography_create_indexes from cartography.intel import ontology as cartography_ontology from celery.utils.log import get_task_logger +from tasks.jobs.attack_paths import db_utils, findings, internet, sync, utils +from tasks.jobs.attack_paths.config import get_cartography_ingestion_function from api.attack_paths import database as graph_database from api.db_utils import rls_transaction -from api.models import ( - Provider as ProwlerAPIProvider, - StateChoices, -) +from api.models import Provider as ProwlerAPIProvider +from api.models import StateChoices from api.utils import initialize_prowler_provider -from tasks.jobs.attack_paths import db_utils, findings, internet, sync, utils -from tasks.jobs.attack_paths.config import get_cartography_ingestion_function # Without this Celery goes crazy with Cartography logging logging.getLogger("cartography").setLevel(logging.ERROR) @@ -147,6 +144,10 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]: attack_paths_scan, task_id, tenant_cartography_config ) + subgraph_dropped = False + sync_completed = False + provider_gated = False + try: logger.info( f"Creating Neo4j database {tmp_cartography_config.neo4j_database} for tenant {prowler_api_provider.tenant_id}" @@ -225,10 +226,12 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]: logger.info(f"Deleting existing provider graph in {tenant_database_name}") db_utils.set_provider_graph_data_ready(attack_paths_scan, False) + provider_gated = True graph_database.drop_subgraph( database=tenant_database_name, provider_id=str(prowler_api_provider.id), ) + subgraph_dropped = True db_utils.update_attack_paths_scan_progress(attack_paths_scan, 98) logger.info( @@ -240,6 +243,7 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]: tenant_id=str(prowler_api_provider.tenant_id), provider_id=str(prowler_api_provider.id), ) + sync_completed = True db_utils.set_graph_data_ready(attack_paths_scan, True) db_utils.update_attack_paths_scan_progress(attack_paths_scan, 99) @@ -264,23 +268,39 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]: logger.exception(exception_message) ingestion_exceptions["global_error"] = exception_message - # Handling databases changes + # Recover graph_data_ready based on how far the swap got. + # Partial drop (mid-batch failure) may leave `subgraph_dropped=False` + # with data partially deleted, so we prefer that over permanently blocked queries. + try: + if sync_completed: + db_utils.set_graph_data_ready(attack_paths_scan, True) + elif provider_gated and not subgraph_dropped: + db_utils.set_provider_graph_data_ready(attack_paths_scan, True) + + except Exception: + logger.error( + f"Failed to recover `graph_data_ready` for provider {attack_paths_scan.provider_id}", + exc_info=True, + ) + + # Dropping the temporary database if it still exists try: graph_database.drop_database(tmp_cartography_config.neo4j_database) except Exception as e: logger.error( - f"Failed to drop temporary Neo4j database {tmp_cartography_config.neo4j_database} during cleanup: {e}", + f"Failed to drop temporary Neo4j database `{tmp_cartography_config.neo4j_database}` during cleanup: {e}", exc_info=True, ) + # Set Attack Paths scan state to FAILED try: db_utils.finish_attack_paths_scan( attack_paths_scan, StateChoices.FAILED, ingestion_exceptions ) except Exception as e: logger.error( - f"Could not mark attack paths scan {attack_paths_scan.id} as FAILED (row may have been deleted): {e}", + f"Could not mark Attack Paths scan {attack_paths_scan.id} as `FAILED` (row may have been deleted): {e}", exc_info=True, ) diff --git a/api/src/backend/tasks/tests/test_attack_paths_scan.py b/api/src/backend/tasks/tests/test_attack_paths_scan.py index cd4dfcffd9..43a2420321 100644 --- a/api/src/backend/tasks/tests/test_attack_paths_scan.py +++ b/api/src/backend/tasks/tests/test_attack_paths_scan.py @@ -269,6 +269,106 @@ class TestAttackPathsRun: assert failure_args[1] == StateChoices.FAILED assert failure_args[2] == {"global_error": "Cartography failed: ingestion boom"} + @patch( + "tasks.jobs.attack_paths.scan.utils.stringify_exception", + return_value="Cartography failed: ingestion boom", + ) + @patch( + "tasks.jobs.attack_paths.scan.utils.call_within_event_loop", + side_effect=lambda fn, *a, **kw: fn(*a, **kw), + ) + @patch("tasks.jobs.attack_paths.scan.graph_database.drop_database") + @patch("tasks.jobs.attack_paths.scan.db_utils.finish_attack_paths_scan") + @patch("tasks.jobs.attack_paths.scan.db_utils.set_graph_data_ready") + @patch("tasks.jobs.attack_paths.scan.db_utils.set_provider_graph_data_ready") + @patch("tasks.jobs.attack_paths.scan.db_utils.update_attack_paths_scan_progress") + @patch("tasks.jobs.attack_paths.scan.db_utils.starting_attack_paths_scan") + @patch("tasks.jobs.attack_paths.scan.findings.analysis") + @patch("tasks.jobs.attack_paths.scan.internet.analysis") + @patch("tasks.jobs.attack_paths.scan.findings.create_findings_indexes") + @patch("tasks.jobs.attack_paths.scan.cartography_analysis.run") + @patch("tasks.jobs.attack_paths.scan.cartography_create_indexes.run") + @patch("tasks.jobs.attack_paths.scan.graph_database.create_database") + @patch( + "tasks.jobs.attack_paths.scan.graph_database.get_database_name", + return_value="db-scan-id", + ) + @patch("tasks.jobs.attack_paths.scan.graph_database.get_uri") + @patch( + "tasks.jobs.attack_paths.scan.initialize_prowler_provider", + return_value=MagicMock(_enabled_regions=["us-east-1"]), + ) + @patch( + "tasks.jobs.attack_paths.scan.rls_transaction", + new=lambda *args, **kwargs: nullcontext(), + ) + def test_failure_before_gate_does_not_flip_graph_data_ready_true( + self, + mock_init_provider, + mock_get_uri, + mock_get_db_name, + mock_create_db, + mock_cartography_indexes, + mock_cartography_analysis, + mock_findings_indexes, + mock_internet_analysis, + mock_findings_analysis, + mock_starting, + mock_update_progress, + mock_set_provider_graph_data_ready, + mock_set_graph_data_ready, + mock_finish, + mock_drop_db, + mock_event_loop, + mock_stringify, + tenants_fixture, + providers_fixture, + scans_fixture, + ): + """Failure during ingestion (before set_provider_graph_data_ready(False)) + must NOT flip graph_data_ready to True for providers that never had data.""" + tenant = tenants_fixture[0] + provider = providers_fixture[0] + provider.provider = Provider.ProviderChoices.AWS + provider.save() + scan = scans_fixture[0] + scan.provider = provider + scan.save() + + attack_paths_scan = AttackPathsScan.objects.create( + tenant_id=tenant.id, + provider=provider, + scan=scan, + state=StateChoices.SCHEDULED, + ) + + mock_session = MagicMock() + session_ctx = MagicMock() + session_ctx.__enter__.return_value = mock_session + session_ctx.__exit__.return_value = False + ingestion_fn = MagicMock(side_effect=RuntimeError("ingestion boom")) + + with ( + patch( + "tasks.jobs.attack_paths.scan.graph_database.get_session", + return_value=session_ctx, + ), + patch( + "tasks.jobs.attack_paths.scan.db_utils.retrieve_attack_paths_scan", + return_value=attack_paths_scan, + ), + patch( + "tasks.jobs.attack_paths.scan.get_cartography_ingestion_function", + return_value=ingestion_fn, + ), + ): + with pytest.raises(RuntimeError, match="ingestion boom"): + attack_paths_run(str(tenant.id), str(scan.id), "task-456") + + # Gate was never applied, so recovery must not flip anything to True + mock_set_provider_graph_data_ready.assert_not_called() + mock_set_graph_data_ready.assert_not_called() + @patch( "tasks.jobs.attack_paths.scan.utils.stringify_exception", return_value="Cartography failed: ingestion boom", @@ -371,6 +471,465 @@ class TestAttackPathsRun: assert failure_args[1] == StateChoices.FAILED assert failure_args[2] == {"global_error": "Cartography failed: ingestion boom"} + @patch( + "tasks.jobs.attack_paths.scan.utils.stringify_exception", + return_value="Attack Paths scan failed: drop failed", + ) + @patch( + "tasks.jobs.attack_paths.scan.utils.call_within_event_loop", + side_effect=lambda fn, *a, **kw: fn(*a, **kw), + ) + @patch("tasks.jobs.attack_paths.scan.graph_database.drop_database") + @patch("tasks.jobs.attack_paths.scan.db_utils.finish_attack_paths_scan") + @patch("tasks.jobs.attack_paths.scan.db_utils.set_graph_data_ready") + @patch("tasks.jobs.attack_paths.scan.db_utils.set_provider_graph_data_ready") + @patch("tasks.jobs.attack_paths.scan.db_utils.update_attack_paths_scan_progress") + @patch("tasks.jobs.attack_paths.scan.db_utils.starting_attack_paths_scan") + @patch("tasks.jobs.attack_paths.scan.sync.sync_graph") + @patch( + "tasks.jobs.attack_paths.scan.graph_database.drop_subgraph", + side_effect=RuntimeError("drop failed"), + ) + @patch("tasks.jobs.attack_paths.scan.sync.create_sync_indexes") + @patch("tasks.jobs.attack_paths.scan.internet.analysis") + @patch("tasks.jobs.attack_paths.scan.findings.analysis") + @patch("tasks.jobs.attack_paths.scan.findings.create_findings_indexes") + @patch("tasks.jobs.attack_paths.scan.cartography_ontology.run") + @patch("tasks.jobs.attack_paths.scan.cartography_analysis.run") + @patch("tasks.jobs.attack_paths.scan.cartography_create_indexes.run") + @patch("tasks.jobs.attack_paths.scan.graph_database.clear_cache") + @patch("tasks.jobs.attack_paths.scan.graph_database.create_database") + @patch( + "tasks.jobs.attack_paths.scan.graph_database.get_uri", + return_value="bolt://neo4j", + ) + @patch( + "tasks.jobs.attack_paths.scan.initialize_prowler_provider", + return_value=MagicMock(_enabled_regions=["us-east-1"]), + ) + @patch( + "tasks.jobs.attack_paths.scan.rls_transaction", + new=lambda *args, **kwargs: nullcontext(), + ) + def test_failure_after_gate_before_drop_restores_graph_data_ready( + self, + mock_init_provider, + mock_get_uri, + mock_create_db, + mock_clear_cache, + mock_cartography_indexes, + mock_cartography_analysis, + mock_cartography_ontology, + mock_findings_indexes, + mock_findings_analysis, + mock_internet_analysis, + mock_sync_indexes, + mock_drop_subgraph, + mock_sync, + mock_starting, + mock_update_progress, + mock_set_provider_graph_data_ready, + mock_set_graph_data_ready, + mock_finish, + mock_drop_db, + mock_event_loop, + mock_stringify, + tenants_fixture, + providers_fixture, + scans_fixture, + ): + tenant = tenants_fixture[0] + provider = providers_fixture[0] + provider.provider = Provider.ProviderChoices.AWS + provider.save() + scan = scans_fixture[0] + scan.provider = provider + scan.save() + + attack_paths_scan = AttackPathsScan.objects.create( + tenant_id=tenant.id, + provider=provider, + scan=scan, + state=StateChoices.SCHEDULED, + ) + + mock_session = MagicMock() + session_ctx = MagicMock() + session_ctx.__enter__.return_value = mock_session + session_ctx.__exit__.return_value = False + + with ( + patch( + "tasks.jobs.attack_paths.scan.graph_database.get_database_name", + side_effect=["db-scan-id", "tenant-db"], + ), + patch( + "tasks.jobs.attack_paths.scan.graph_database.get_session", + return_value=session_ctx, + ), + patch( + "tasks.jobs.attack_paths.scan.db_utils.retrieve_attack_paths_scan", + return_value=attack_paths_scan, + ), + patch( + "tasks.jobs.attack_paths.scan.get_cartography_ingestion_function", + return_value=MagicMock(return_value={}), + ), + ): + with pytest.raises(RuntimeError, match="drop failed"): + attack_paths_run(str(tenant.id), str(scan.id), "task-456") + + assert mock_set_provider_graph_data_ready.call_args_list == [ + call(attack_paths_scan, False), + call(attack_paths_scan, True), + ] + + @patch( + "tasks.jobs.attack_paths.scan.utils.stringify_exception", + return_value="Attack Paths scan failed: sync failed", + ) + @patch( + "tasks.jobs.attack_paths.scan.utils.call_within_event_loop", + side_effect=lambda fn, *a, **kw: fn(*a, **kw), + ) + @patch("tasks.jobs.attack_paths.scan.graph_database.drop_database") + @patch("tasks.jobs.attack_paths.scan.db_utils.finish_attack_paths_scan") + @patch("tasks.jobs.attack_paths.scan.db_utils.set_graph_data_ready") + @patch("tasks.jobs.attack_paths.scan.db_utils.set_provider_graph_data_ready") + @patch("tasks.jobs.attack_paths.scan.db_utils.update_attack_paths_scan_progress") + @patch("tasks.jobs.attack_paths.scan.db_utils.starting_attack_paths_scan") + @patch( + "tasks.jobs.attack_paths.scan.sync.sync_graph", + side_effect=RuntimeError("sync failed"), + ) + @patch("tasks.jobs.attack_paths.scan.graph_database.drop_subgraph") + @patch("tasks.jobs.attack_paths.scan.sync.create_sync_indexes") + @patch("tasks.jobs.attack_paths.scan.internet.analysis") + @patch("tasks.jobs.attack_paths.scan.findings.analysis") + @patch("tasks.jobs.attack_paths.scan.findings.create_findings_indexes") + @patch("tasks.jobs.attack_paths.scan.cartography_ontology.run") + @patch("tasks.jobs.attack_paths.scan.cartography_analysis.run") + @patch("tasks.jobs.attack_paths.scan.cartography_create_indexes.run") + @patch("tasks.jobs.attack_paths.scan.graph_database.clear_cache") + @patch("tasks.jobs.attack_paths.scan.graph_database.create_database") + @patch( + "tasks.jobs.attack_paths.scan.graph_database.get_uri", + return_value="bolt://neo4j", + ) + @patch( + "tasks.jobs.attack_paths.scan.initialize_prowler_provider", + return_value=MagicMock(_enabled_regions=["us-east-1"]), + ) + @patch( + "tasks.jobs.attack_paths.scan.rls_transaction", + new=lambda *args, **kwargs: nullcontext(), + ) + def test_failure_after_drop_before_sync_leaves_graph_data_ready_false( + self, + mock_init_provider, + mock_get_uri, + mock_create_db, + mock_clear_cache, + mock_cartography_indexes, + mock_cartography_analysis, + mock_cartography_ontology, + mock_findings_indexes, + mock_findings_analysis, + mock_internet_analysis, + mock_sync_indexes, + mock_drop_subgraph, + mock_sync, + mock_starting, + mock_update_progress, + mock_set_provider_graph_data_ready, + mock_set_graph_data_ready, + mock_finish, + mock_drop_db, + mock_event_loop, + mock_stringify, + tenants_fixture, + providers_fixture, + scans_fixture, + ): + tenant = tenants_fixture[0] + provider = providers_fixture[0] + provider.provider = Provider.ProviderChoices.AWS + provider.save() + scan = scans_fixture[0] + scan.provider = provider + scan.save() + + attack_paths_scan = AttackPathsScan.objects.create( + tenant_id=tenant.id, + provider=provider, + scan=scan, + state=StateChoices.SCHEDULED, + ) + + mock_session = MagicMock() + session_ctx = MagicMock() + session_ctx.__enter__.return_value = mock_session + session_ctx.__exit__.return_value = False + + with ( + patch( + "tasks.jobs.attack_paths.scan.graph_database.get_database_name", + side_effect=["db-scan-id", "tenant-db"], + ), + patch( + "tasks.jobs.attack_paths.scan.graph_database.get_session", + return_value=session_ctx, + ), + patch( + "tasks.jobs.attack_paths.scan.db_utils.retrieve_attack_paths_scan", + return_value=attack_paths_scan, + ), + patch( + "tasks.jobs.attack_paths.scan.get_cartography_ingestion_function", + return_value=MagicMock(return_value={}), + ), + ): + with pytest.raises(RuntimeError, match="sync failed"): + attack_paths_run(str(tenant.id), str(scan.id), "task-456") + + # Only called with False (gate), never with True (no recovery for partial data) + mock_set_provider_graph_data_ready.assert_called_once_with( + attack_paths_scan, False + ) + + @patch( + "tasks.jobs.attack_paths.scan.utils.stringify_exception", + return_value="Attack Paths scan failed: flag failed", + ) + @patch( + "tasks.jobs.attack_paths.scan.utils.call_within_event_loop", + side_effect=lambda fn, *a, **kw: fn(*a, **kw), + ) + @patch("tasks.jobs.attack_paths.scan.graph_database.drop_database") + @patch("tasks.jobs.attack_paths.scan.db_utils.finish_attack_paths_scan") + @patch( + "tasks.jobs.attack_paths.scan.db_utils.set_graph_data_ready", + side_effect=[RuntimeError("flag failed"), None], + ) + @patch("tasks.jobs.attack_paths.scan.db_utils.set_provider_graph_data_ready") + @patch("tasks.jobs.attack_paths.scan.db_utils.update_attack_paths_scan_progress") + @patch("tasks.jobs.attack_paths.scan.db_utils.starting_attack_paths_scan") + @patch("tasks.jobs.attack_paths.scan.sync.sync_graph") + @patch("tasks.jobs.attack_paths.scan.graph_database.drop_subgraph") + @patch("tasks.jobs.attack_paths.scan.sync.create_sync_indexes") + @patch("tasks.jobs.attack_paths.scan.internet.analysis") + @patch("tasks.jobs.attack_paths.scan.findings.analysis") + @patch("tasks.jobs.attack_paths.scan.findings.create_findings_indexes") + @patch("tasks.jobs.attack_paths.scan.cartography_ontology.run") + @patch("tasks.jobs.attack_paths.scan.cartography_analysis.run") + @patch("tasks.jobs.attack_paths.scan.cartography_create_indexes.run") + @patch("tasks.jobs.attack_paths.scan.graph_database.clear_cache") + @patch("tasks.jobs.attack_paths.scan.graph_database.create_database") + @patch( + "tasks.jobs.attack_paths.scan.graph_database.get_uri", + return_value="bolt://neo4j", + ) + @patch( + "tasks.jobs.attack_paths.scan.initialize_prowler_provider", + return_value=MagicMock(_enabled_regions=["us-east-1"]), + ) + @patch( + "tasks.jobs.attack_paths.scan.rls_transaction", + new=lambda *args, **kwargs: nullcontext(), + ) + def test_failure_after_sync_restores_graph_data_ready( + self, + mock_init_provider, + mock_get_uri, + mock_create_db, + mock_clear_cache, + mock_cartography_indexes, + mock_cartography_analysis, + mock_cartography_ontology, + mock_findings_indexes, + mock_findings_analysis, + mock_internet_analysis, + mock_sync_indexes, + mock_drop_subgraph, + mock_sync, + mock_starting, + mock_update_progress, + mock_set_provider_graph_data_ready, + mock_set_graph_data_ready, + mock_finish, + mock_drop_db, + mock_event_loop, + mock_stringify, + tenants_fixture, + providers_fixture, + scans_fixture, + ): + tenant = tenants_fixture[0] + provider = providers_fixture[0] + provider.provider = Provider.ProviderChoices.AWS + provider.save() + scan = scans_fixture[0] + scan.provider = provider + scan.save() + + attack_paths_scan = AttackPathsScan.objects.create( + tenant_id=tenant.id, + provider=provider, + scan=scan, + state=StateChoices.SCHEDULED, + ) + + mock_session = MagicMock() + session_ctx = MagicMock() + session_ctx.__enter__.return_value = mock_session + session_ctx.__exit__.return_value = False + + with ( + patch( + "tasks.jobs.attack_paths.scan.graph_database.get_database_name", + side_effect=["db-scan-id", "tenant-db"], + ), + patch( + "tasks.jobs.attack_paths.scan.graph_database.get_session", + return_value=session_ctx, + ), + patch( + "tasks.jobs.attack_paths.scan.db_utils.retrieve_attack_paths_scan", + return_value=attack_paths_scan, + ), + patch( + "tasks.jobs.attack_paths.scan.get_cartography_ingestion_function", + return_value=MagicMock(return_value={}), + ), + ): + with pytest.raises(RuntimeError, match="flag failed"): + attack_paths_run(str(tenant.id), str(scan.id), "task-456") + + # sync completed: first call (normal path) raised, recovery retried and succeeded + assert mock_set_graph_data_ready.call_args_list == [ + call(attack_paths_scan, True), + call(attack_paths_scan, True), + ] + # set_provider_graph_data_ready only called once with False (the gate) + mock_set_provider_graph_data_ready.assert_called_once_with( + attack_paths_scan, False + ) + + @patch( + "tasks.jobs.attack_paths.scan.utils.stringify_exception", + return_value="Attack Paths scan failed: drop failed", + ) + @patch( + "tasks.jobs.attack_paths.scan.utils.call_within_event_loop", + side_effect=lambda fn, *a, **kw: fn(*a, **kw), + ) + @patch("tasks.jobs.attack_paths.scan.graph_database.drop_database") + @patch("tasks.jobs.attack_paths.scan.db_utils.finish_attack_paths_scan") + @patch("tasks.jobs.attack_paths.scan.db_utils.set_graph_data_ready") + @patch("tasks.jobs.attack_paths.scan.db_utils.set_provider_graph_data_ready") + @patch("tasks.jobs.attack_paths.scan.db_utils.update_attack_paths_scan_progress") + @patch("tasks.jobs.attack_paths.scan.db_utils.starting_attack_paths_scan") + @patch("tasks.jobs.attack_paths.scan.sync.sync_graph") + @patch( + "tasks.jobs.attack_paths.scan.graph_database.drop_subgraph", + side_effect=RuntimeError("drop failed"), + ) + @patch("tasks.jobs.attack_paths.scan.sync.create_sync_indexes") + @patch("tasks.jobs.attack_paths.scan.internet.analysis") + @patch("tasks.jobs.attack_paths.scan.findings.analysis") + @patch("tasks.jobs.attack_paths.scan.findings.create_findings_indexes") + @patch("tasks.jobs.attack_paths.scan.cartography_ontology.run") + @patch("tasks.jobs.attack_paths.scan.cartography_analysis.run") + @patch("tasks.jobs.attack_paths.scan.cartography_create_indexes.run") + @patch("tasks.jobs.attack_paths.scan.graph_database.clear_cache") + @patch("tasks.jobs.attack_paths.scan.graph_database.create_database") + @patch( + "tasks.jobs.attack_paths.scan.graph_database.get_uri", + return_value="bolt://neo4j", + ) + @patch( + "tasks.jobs.attack_paths.scan.initialize_prowler_provider", + return_value=MagicMock(_enabled_regions=["us-east-1"]), + ) + @patch( + "tasks.jobs.attack_paths.scan.rls_transaction", + new=lambda *args, **kwargs: nullcontext(), + ) + def test_recovery_failure_does_not_suppress_original_exception( + self, + mock_init_provider, + mock_get_uri, + mock_create_db, + mock_clear_cache, + mock_cartography_indexes, + mock_cartography_analysis, + mock_cartography_ontology, + mock_findings_indexes, + mock_findings_analysis, + mock_internet_analysis, + mock_sync_indexes, + mock_drop_subgraph, + mock_sync, + mock_starting, + mock_update_progress, + mock_set_provider_graph_data_ready, + mock_set_graph_data_ready, + mock_finish, + mock_drop_db, + mock_event_loop, + mock_stringify, + tenants_fixture, + providers_fixture, + scans_fixture, + ): + tenant = tenants_fixture[0] + provider = providers_fixture[0] + provider.provider = Provider.ProviderChoices.AWS + provider.save() + scan = scans_fixture[0] + scan.provider = provider + scan.save() + + attack_paths_scan = AttackPathsScan.objects.create( + tenant_id=tenant.id, + provider=provider, + scan=scan, + state=StateChoices.SCHEDULED, + ) + + # Recovery itself fails on the second call (True) + mock_set_provider_graph_data_ready.side_effect = [ + None, + RuntimeError("recovery boom"), + ] + + mock_session = MagicMock() + session_ctx = MagicMock() + session_ctx.__enter__.return_value = mock_session + session_ctx.__exit__.return_value = False + + with ( + patch( + "tasks.jobs.attack_paths.scan.graph_database.get_database_name", + side_effect=["db-scan-id", "tenant-db"], + ), + patch( + "tasks.jobs.attack_paths.scan.graph_database.get_session", + return_value=session_ctx, + ), + patch( + "tasks.jobs.attack_paths.scan.db_utils.retrieve_attack_paths_scan", + return_value=attack_paths_scan, + ), + patch( + "tasks.jobs.attack_paths.scan.get_cartography_ingestion_function", + return_value=MagicMock(return_value={}), + ), + ): + # Original exception propagates despite recovery failure + with pytest.raises(RuntimeError, match="drop failed"): + attack_paths_run(str(tenant.id), str(scan.id), "task-456") + def test_run_returns_early_for_unsupported_provider(self, tenants_fixture): tenant = tenants_fixture[0] provider = Provider.objects.create( @@ -419,9 +978,7 @@ class TestFailAttackPathsScan: def test_marks_executing_scan_as_failed( self, tenants_fixture, providers_fixture, scans_fixture ): - from tasks.jobs.attack_paths.db_utils import ( - fail_attack_paths_scan, - ) + from tasks.jobs.attack_paths.db_utils import fail_attack_paths_scan tenant = tenants_fixture[0] provider = providers_fixture[0] @@ -449,6 +1006,7 @@ class TestFailAttackPathsScan: patch( "tasks.jobs.attack_paths.db_utils.finish_attack_paths_scan" ) as mock_finish, + patch("tasks.jobs.attack_paths.db_utils.recover_graph_data_ready"), ): fail_attack_paths_scan(str(tenant.id), str(scan.id), "setup exploded") @@ -464,9 +1022,7 @@ class TestFailAttackPathsScan: def test_drops_temp_database_even_when_drop_fails( self, tenants_fixture, providers_fixture, scans_fixture ): - from tasks.jobs.attack_paths.db_utils import ( - fail_attack_paths_scan, - ) + from tasks.jobs.attack_paths.db_utils import fail_attack_paths_scan tenant = tenants_fixture[0] provider = providers_fixture[0] @@ -495,6 +1051,7 @@ class TestFailAttackPathsScan: patch( "tasks.jobs.attack_paths.db_utils.finish_attack_paths_scan" ) as mock_finish, + patch("tasks.jobs.attack_paths.db_utils.recover_graph_data_ready"), ): fail_attack_paths_scan(str(tenant.id), str(scan.id), "setup exploded") @@ -507,9 +1064,7 @@ class TestFailAttackPathsScan: def test_skips_already_failed_scan( self, tenants_fixture, providers_fixture, scans_fixture ): - from tasks.jobs.attack_paths.db_utils import ( - fail_attack_paths_scan, - ) + from tasks.jobs.attack_paths.db_utils import fail_attack_paths_scan tenant = tenants_fixture[0] provider = providers_fixture[0] @@ -544,9 +1099,7 @@ class TestFailAttackPathsScan: mock_finish.assert_not_called() def test_skips_when_no_scan_found(self, tenants_fixture): - from tasks.jobs.attack_paths.db_utils import ( - fail_attack_paths_scan, - ) + from tasks.jobs.attack_paths.db_utils import fail_attack_paths_scan tenant = tenants_fixture[0] @@ -563,6 +1116,111 @@ class TestFailAttackPathsScan: mock_finish.assert_not_called() + def test_fail_recovers_graph_data_ready_when_data_exists( + self, tenants_fixture, providers_fixture, scans_fixture + ): + from tasks.jobs.attack_paths.db_utils import fail_attack_paths_scan + + tenant = tenants_fixture[0] + provider = providers_fixture[0] + provider.provider = Provider.ProviderChoices.AWS + provider.save() + scan = scans_fixture[0] + scan.provider = provider + scan.save() + + attack_paths_scan = AttackPathsScan.objects.create( + tenant_id=tenant.id, + provider=provider, + scan=scan, + state=StateChoices.EXECUTING, + ) + + with ( + patch( + "tasks.jobs.attack_paths.db_utils.retrieve_attack_paths_scan", + return_value=attack_paths_scan, + ), + patch("tasks.jobs.attack_paths.db_utils.graph_database.drop_database"), + patch("tasks.jobs.attack_paths.db_utils.finish_attack_paths_scan"), + patch( + "tasks.jobs.attack_paths.db_utils.graph_database.has_provider_data", + return_value=True, + ), + patch( + "tasks.jobs.attack_paths.db_utils.set_provider_graph_data_ready" + ) as mock_set_ready, + ): + fail_attack_paths_scan(str(tenant.id), str(scan.id), "worker died") + + mock_set_ready.assert_called_once_with(attack_paths_scan, True) + + def test_fail_leaves_graph_data_ready_false_when_no_data( + self, tenants_fixture, providers_fixture, scans_fixture + ): + from tasks.jobs.attack_paths.db_utils import fail_attack_paths_scan + + tenant = tenants_fixture[0] + provider = providers_fixture[0] + provider.provider = Provider.ProviderChoices.AWS + provider.save() + scan = scans_fixture[0] + scan.provider = provider + scan.save() + + attack_paths_scan = AttackPathsScan.objects.create( + tenant_id=tenant.id, + provider=provider, + scan=scan, + state=StateChoices.EXECUTING, + ) + + with ( + patch( + "tasks.jobs.attack_paths.db_utils.retrieve_attack_paths_scan", + return_value=attack_paths_scan, + ), + patch("tasks.jobs.attack_paths.db_utils.graph_database.drop_database"), + patch("tasks.jobs.attack_paths.db_utils.finish_attack_paths_scan"), + patch( + "tasks.jobs.attack_paths.db_utils.graph_database.has_provider_data", + return_value=False, + ), + patch( + "tasks.jobs.attack_paths.db_utils.set_provider_graph_data_ready" + ) as mock_set_ready, + ): + fail_attack_paths_scan(str(tenant.id), str(scan.id), "worker died") + + mock_set_ready.assert_not_called() + + def test_recover_graph_data_ready_never_raises( + self, tenants_fixture, providers_fixture, scans_fixture + ): + from tasks.jobs.attack_paths.db_utils import recover_graph_data_ready + + tenant = tenants_fixture[0] + provider = providers_fixture[0] + provider.provider = Provider.ProviderChoices.AWS + provider.save() + scan = scans_fixture[0] + scan.provider = provider + scan.save() + + attack_paths_scan = AttackPathsScan.objects.create( + tenant_id=tenant.id, + provider=provider, + scan=scan, + state=StateChoices.EXECUTING, + ) + + with patch( + "tasks.jobs.attack_paths.db_utils.graph_database.has_provider_data", + side_effect=Exception("Neo4j unreachable"), + ): + # Should not raise + recover_graph_data_ready(attack_paths_scan) + class TestAttackPathsScanRLSTaskOnFailure: def test_on_failure_delegates_to_fail_attack_paths_scan(self): From e8aaf5266abeb7a81a7290b2de1d5ff168edb838 Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:58:40 +0100 Subject: [PATCH 027/366] chore(sdk): bump pygithub from 2.5.0 to 2.8.0 (#10353) --- poetry.lock | 39 ++++++------------------ prowler/CHANGELOG.md | 1 + prowler/providers/iac/iac_provider.py | 1 + pyproject.toml | 2 +- tests/providers/iac/iac_provider_test.py | 4 ++- 5 files changed, 16 insertions(+), 31 deletions(-) diff --git a/poetry.lock b/poetry.lock index ee225d3bf4..553a67322f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "about-time" @@ -2151,24 +2151,6 @@ files = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] -[[package]] -name = "deprecated" -version = "1.2.18" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -groups = ["main"] -files = [ - {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, - {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] - [[package]] name = "detect-secrets" version = "1.5.0" @@ -3972,14 +3954,14 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} [[package]] name = "multipart" -version = "1.3.1" +version = "1.3.0" description = "Parser for multipart/form-data" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "multipart-1.3.1-py3-none-any.whl", hash = "sha256:a82b59e1befe74d3d30b3d3f70efd5a2eba4d938f845dcff9faace968888ff29"}, - {file = "multipart-1.3.1.tar.gz", hash = "sha256:211d7cfc1a7a43e75c4d24ee0e8e0f4f61d522f1a21575303ae85333dea687bf"}, + {file = "multipart-1.3.0-py3-none-any.whl", hash = "sha256:439bf4b00fd7cb2dbff08ae13f49f4f49798931ecd8d496372c63537fa19f304"}, + {file = "multipart-1.3.0.tar.gz", hash = "sha256:a46bd6b0eb4c1ba865beb88ddd886012a3da709b6e7b86084fc37e99087e5cf1"}, ] [package.extras] @@ -5050,22 +5032,21 @@ files = [ [[package]] name = "pygithub" -version = "2.5.0" +version = "2.8.0" description = "Use the full Github API v3" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "PyGithub-2.5.0-py3-none-any.whl", hash = "sha256:b0b635999a658ab8e08720bdd3318893ff20e2275f6446fcf35bf3f44f2c0fd2"}, - {file = "pygithub-2.5.0.tar.gz", hash = "sha256:e1613ac508a9be710920d26eb18b1905ebd9926aa49398e88151c1b526aad3cf"}, + {file = "pygithub-2.8.0-py3-none-any.whl", hash = "sha256:11a3473c1c2f1c39c525d0ee8c559f369c6d46c272cb7321c9b0cabc7aa1ce7d"}, + {file = "pygithub-2.8.0.tar.gz", hash = "sha256:72f5f2677d86bc3a8843aa720c6ce4c1c42fb7500243b136e3d5e14ddb5c3386"}, ] [package.dependencies] -Deprecated = "*" pyjwt = {version = ">=2.4.0", extras = ["crypto"]} pynacl = ">=1.4.0" requests = ">=2.14.0" -typing-extensions = ">=4.0.0" +typing-extensions = ">=4.5.0" urllib3 = ">=1.26.0" [[package]] @@ -6519,7 +6500,7 @@ version = "1.17.2" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["dev"] files = [ {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, @@ -6905,4 +6886,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">3.9.1,<3.13" -content-hash = "386f6cf2bed49290cc4661aa2093ceb018aa6cdaf6864bdfab36f6c2c50e241e" +content-hash = "fa67f98ae1b75ec5a54d1d6a1c33c5412d888ec60cf35fc407606dc48329c0bf" diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index b8cc8b8f04..d33b1c0b50 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. ### 🔄 Changed +- Bump `pygithub` from 2.5.0 to 2.8.0 to use native Organization properties - Update M365 SharePoint service metadata to new format [(#9684)](https://github.com/prowler-cloud/prowler/pull/9684) - Update M365 Exchange service metadata to new format [(#9683)](https://github.com/prowler-cloud/prowler/pull/9683) - Update M365 Teams service metadata to new format [(#9685)](https://github.com/prowler-cloud/prowler/pull/9685) diff --git a/prowler/providers/iac/iac_provider.py b/prowler/providers/iac/iac_provider.py index e7b83995a7..5b3d898fb3 100644 --- a/prowler/providers/iac/iac_provider.py +++ b/prowler/providers/iac/iac_provider.py @@ -224,6 +224,7 @@ class IacProvider(Provider): }, }, "Categories": [], + "AdditionalURLs": [], "DependsOn": [], "RelatedTo": [], "Notes": "", diff --git a/pyproject.toml b/pyproject.toml index 4ffee0685f..d24f1692e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ dependencies = [ "pandas==2.2.3", "py-ocsf-models==0.8.1", "pydantic (>=2.0,<3.0)", - "pygithub==2.5.0", + "pygithub==2.8.0", "python-dateutil (>=2.9.0.post0,<3.0.0)", "pytz==2025.1", "schema==0.7.5", diff --git a/tests/providers/iac/iac_provider_test.py b/tests/providers/iac/iac_provider_test.py index 318ae549bd..fc98c097d8 100644 --- a/tests/providers/iac/iac_provider_test.py +++ b/tests/providers/iac/iac_provider_test.py @@ -61,7 +61,9 @@ class TestIacProvider: assert report.check_metadata.CheckID == SAMPLE_FAILED_CHECK["ID"] assert report.check_metadata.CheckTitle == SAMPLE_FAILED_CHECK["Title"] assert report.check_metadata.Severity == "low" - assert report.check_metadata.RelatedUrl == SAMPLE_FAILED_CHECK["PrimaryURL"] + assert report.check_metadata.RelatedUrl == SAMPLE_FAILED_CHECK.get( + "PrimaryURL", "" + ) def test_iac_provider_process_finding_passed(self): """Test processing a passed finding""" From 1da10611e7eadac24a9a6619c232a35811965a92 Mon Sep 17 00:00:00 2001 From: Josema Camacho Date: Wed, 18 Mar 2026 10:08:30 +0100 Subject: [PATCH 028/366] perf(attack-paths): reduce sync and findings memory usage with smaller batches and cursor iteration (#10359) --- api/CHANGELOG.md | 1 + .../backend/tasks/jobs/attack_paths/config.py | 8 +- .../tasks/jobs/attack_paths/findings.py | 138 ++++------ .../backend/tasks/jobs/attack_paths/sync.py | 140 +++++----- .../tasks/tests/test_attack_paths_scan.py | 253 +++++++++++++++--- 5 files changed, 351 insertions(+), 189 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index c7ff2bf99e..15702e342d 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to the **Prowler API** are documented in this file. - Attack Paths: Complete migration to private graph labels and properties, removing deprecated dual-write support [(#10268)](https://github.com/prowler-cloud/prowler/pull/10268) - Attack Paths: Added tenant and provider related labels to the nodes so they can be easily filtered on custom queries [(#10308)](https://github.com/prowler-cloud/prowler/pull/10308) +- Attack Paths: Reduce sync and findings memory usage with smaller batches, cursor iteration, and sequential sessions [(#10359)](https://github.com/prowler-cloud/prowler/pull/10359) ### 🐞 Fixed diff --git a/api/src/backend/tasks/jobs/attack_paths/config.py b/api/src/backend/tasks/jobs/attack_paths/config.py index be9305d8a5..315d2ecd24 100644 --- a/api/src/backend/tasks/jobs/attack_paths/config.py +++ b/api/src/backend/tasks/jobs/attack_paths/config.py @@ -3,12 +3,14 @@ from typing import Callable from uuid import UUID from config.env import env - from tasks.jobs.attack_paths import aws - -# Batch size for Neo4j operations +# Batch size for Neo4j write operations (resource labeling, cleanup) BATCH_SIZE = env.int("ATTACK_PATHS_BATCH_SIZE", 1000) +# Batch size for Postgres findings fetch (keyset pagination page size) +FINDINGS_BATCH_SIZE = env.int("ATTACK_PATHS_FINDINGS_BATCH_SIZE", 500) +# Batch size for temp-to-tenant graph sync (nodes and relationships per cursor page) +SYNC_BATCH_SIZE = env.int("ATTACK_PATHS_SYNC_BATCH_SIZE", 250) # Neo4j internal labels (Prowler-specific, not provider-specific) # - `Internet`: Singleton node representing external internet access for exposed-resource queries diff --git a/api/src/backend/tasks/jobs/attack_paths/findings.py b/api/src/backend/tasks/jobs/attack_paths/findings.py index b4534fb8de..9a9f365911 100644 --- a/api/src/backend/tasks/jobs/attack_paths/findings.py +++ b/api/src/backend/tasks/jobs/attack_paths/findings.py @@ -9,22 +9,15 @@ This module handles: """ from collections import defaultdict -from dataclasses import asdict, dataclass, fields from typing import Any, Generator from uuid import UUID import neo4j - from cartography.config import Config as CartographyConfig from celery.utils.log import get_task_logger - -from api.db_router import READ_REPLICA_ALIAS -from api.db_utils import rls_transaction -from api.models import Finding as FindingModel -from api.models import Provider, ResourceFindingMapping -from prowler.config import config as ProwlerConfig from tasks.jobs.attack_paths.config import ( BATCH_SIZE, + FINDINGS_BATCH_SIZE, get_node_uid_field, get_provider_resource_label, get_root_node_label, @@ -37,75 +30,54 @@ from tasks.jobs.attack_paths.queries import ( render_cypher_template, ) +from api.db_router import READ_REPLICA_ALIAS +from api.db_utils import rls_transaction +from api.models import Finding as FindingModel +from api.models import Provider, ResourceFindingMapping +from prowler.config import config as ProwlerConfig + logger = get_task_logger(__name__) -# Type Definitions -# ----------------- - -# Maps dataclass field names to Django ORM query field names -_DB_FIELD_MAP: dict[str, str] = { - "check_title": "check_metadata__checktitle", -} +# Django ORM field names for `.values()` queries +# Most map 1:1 to Neo4j property names, exceptions are remapped in `_to_neo4j_dict` +_DB_QUERY_FIELDS = [ + "id", + "uid", + "inserted_at", + "updated_at", + "first_seen_at", + "scan_id", + "delta", + "status", + "status_extended", + "severity", + "check_id", + "check_metadata__checktitle", + "muted", + "muted_reason", +] -@dataclass(slots=True) -class Finding: - """ - Finding data for Neo4j ingestion. - - Can be created from a Django .values() query result using from_db_record(). - """ - - id: str - uid: str - inserted_at: str - updated_at: str - first_seen_at: str - scan_id: str - delta: str - status: str - status_extended: str - severity: str - check_id: str - check_title: str - muted: bool - muted_reason: str | None - resource_uid: str | None = None - - @classmethod - def get_db_query_fields(cls) -> tuple[str, ...]: - """Get field names for Django .values() query.""" - return tuple( - _DB_FIELD_MAP.get(f.name, f.name) - for f in fields(cls) - if f.name != "resource_uid" - ) - - @classmethod - def from_db_record(cls, record: dict[str, Any], resource_uid: str) -> "Finding": - """Create a Finding from a Django .values() query result.""" - return cls( - id=str(record["id"]), - uid=record["uid"], - inserted_at=record["inserted_at"], - updated_at=record["updated_at"], - first_seen_at=record["first_seen_at"], - scan_id=str(record["scan_id"]), - delta=record["delta"], - status=record["status"], - status_extended=record["status_extended"], - severity=record["severity"], - check_id=str(record["check_id"]), - check_title=record["check_metadata__checktitle"], - muted=record["muted"], - muted_reason=record["muted_reason"], - resource_uid=resource_uid, - ) - - def to_dict(self) -> dict[str, Any]: - """Convert to dict for Neo4j ingestion.""" - return asdict(self) +def _to_neo4j_dict(record: dict[str, Any], resource_uid: str) -> dict[str, Any]: + """Transform a Django `.values()` record into a `dict` ready for Neo4j ingestion.""" + return { + "id": str(record["id"]), + "uid": record["uid"], + "inserted_at": record["inserted_at"], + "updated_at": record["updated_at"], + "first_seen_at": record["first_seen_at"], + "scan_id": str(record["scan_id"]), + "delta": record["delta"], + "status": record["status"], + "status_extended": record["status_extended"], + "severity": record["severity"], + "check_id": str(record["check_id"]), + "check_title": record["check_metadata__checktitle"], + "muted": record["muted"], + "muted_reason": record["muted_reason"], + "resource_uid": resource_uid, + } # Public API @@ -180,7 +152,7 @@ def add_resource_label( def load_findings( neo4j_session: neo4j.Session, - findings_batches: Generator[list[Finding], None, None], + findings_batches: Generator[list[dict[str, Any]], None, None], prowler_api_provider: Provider, config: CartographyConfig, ) -> None: @@ -209,7 +181,7 @@ def load_findings( batch_size = len(batch) total_records += batch_size - parameters["findings_data"] = [f.to_dict() for f in batch] + parameters["findings_data"] = batch logger.info(f"Loading findings batch {batch_num} ({batch_size} records)") neo4j_session.run(query, parameters) @@ -247,16 +219,17 @@ def cleanup_findings( def stream_findings_with_resources( prowler_api_provider: Provider, scan_id: str, -) -> Generator[list[Finding], None, None]: +) -> Generator[list[dict[str, Any]], None, None]: """ Stream findings with their associated resources in batches. Uses keyset pagination for efficient traversal of large datasets. - Memory efficient: yields one batch at a time, never holds all findings in memory. + Memory efficient: yields one batch at a time as dicts ready for Neo4j ingestion, + never holds all findings in memory. """ logger.info( f"Starting findings stream for scan {scan_id} " - f"(tenant {prowler_api_provider.tenant_id}) with batch size {BATCH_SIZE}" + f"(tenant {prowler_api_provider.tenant_id}) with batch size {FINDINGS_BATCH_SIZE}" ) tenant_id = prowler_api_provider.tenant_id @@ -305,15 +278,14 @@ def _fetch_findings_batch( Uses read replica and RLS-scoped transaction. """ with rls_transaction(tenant_id, using=READ_REPLICA_ALIAS): - # Use all_objects to avoid the ActiveProviderManager's implicit JOIN - # through Scan -> Provider (to check is_deleted=False). - # The provider is already validated as active in this context. + # Use `all_objects` to get `Findings` even on soft-deleted `Providers` + # But even the provider is already validated as active in this context qs = FindingModel.all_objects.filter(scan_id=scan_id).order_by("id") if after_id is not None: qs = qs.filter(id__gt=after_id) - return list(qs.values(*Finding.get_db_query_fields())[:BATCH_SIZE]) + return list(qs.values(*_DB_QUERY_FIELDS)[:FINDINGS_BATCH_SIZE]) # Batch Enrichment @@ -323,7 +295,7 @@ def _fetch_findings_batch( def _enrich_batch_with_resources( findings_batch: list[dict[str, Any]], tenant_id: str, -) -> list[Finding]: +) -> list[dict[str, Any]]: """ Enrich findings with their resource UIDs. @@ -334,7 +306,7 @@ def _enrich_batch_with_resources( resource_map = _build_finding_resource_map(finding_ids, tenant_id) return [ - Finding.from_db_record(finding, resource_uid) + _to_neo4j_dict(finding, resource_uid) for finding in findings_batch for resource_uid in resource_map.get(finding["id"], []) ] diff --git a/api/src/backend/tasks/jobs/attack_paths/sync.py b/api/src/backend/tasks/jobs/attack_paths/sync.py index c0b1799b9a..870aba6fa8 100644 --- a/api/src/backend/tasks/jobs/attack_paths/sync.py +++ b/api/src/backend/tasks/jobs/attack_paths/sync.py @@ -8,13 +8,14 @@ to the tenant database, adding provider isolation labels and properties. from collections import defaultdict from typing import Any +import neo4j from celery.utils.log import get_task_logger from api.attack_paths import database as graph_database from tasks.jobs.attack_paths.config import ( - BATCH_SIZE, PROVIDER_ISOLATION_PROPERTIES, PROVIDER_RESOURCE_LABEL, + SYNC_BATCH_SIZE, get_provider_label, get_tenant_label, ) @@ -82,40 +83,32 @@ def sync_nodes( Adds `_ProviderResource` label and `_provider_id` property to all nodes. Also adds dynamic `_Tenant_{id}` and `_Provider_{id}` isolation labels. + + Source and target sessions are opened sequentially per batch to avoid + holding two Bolt connections simultaneously for the entire sync duration. """ last_id = -1 total_synced = 0 - with ( - graph_database.get_session(source_database) as source_session, - graph_database.get_session(target_database) as target_session, - ): - while True: - rows = list( - source_session.run( - NODE_FETCH_QUERY, - {"last_id": last_id, "batch_size": BATCH_SIZE}, - ) + while True: + grouped: dict[tuple[str, ...], list[dict[str, Any]]] = defaultdict(list) + batch_count = 0 + + with graph_database.get_session(source_database) as source_session: + result = source_session.run( + NODE_FETCH_QUERY, + {"last_id": last_id, "batch_size": SYNC_BATCH_SIZE}, ) + for record in result: + batch_count += 1 + last_id = record["internal_id"] + key, value = _node_to_sync_dict(record, provider_id) + grouped[key].append(value) - if not rows: - break - - last_id = rows[-1]["internal_id"] - - grouped: dict[tuple[str, ...], list[dict[str, Any]]] = defaultdict(list) - for row in rows: - labels = tuple(sorted(set(row["labels"] or []))) - props = dict(row["props"] or {}) - _strip_internal_properties(props) - provider_element_id = f"{provider_id}:{row['element_id']}" - grouped[labels].append( - { - "provider_element_id": provider_element_id, - "props": props, - } - ) + if batch_count == 0: + break + with graph_database.get_session(target_database) as target_session: for labels, batch in grouped.items(): label_set = set(labels) label_set.add(PROVIDER_RESOURCE_LABEL) @@ -134,10 +127,10 @@ def sync_nodes( }, ) - total_synced += len(rows) - logger.info( - f"Synced {total_synced} nodes from {source_database} to {target_database}" - ) + total_synced += batch_count + logger.info( + f"Synced {total_synced} nodes from {source_database} to {target_database}" + ) return total_synced @@ -151,41 +144,32 @@ def sync_relationships( Sync relationships from source to target database. Adds `_provider_id` property to all relationships. + + Source and target sessions are opened sequentially per batch to avoid + holding two Bolt connections simultaneously for the entire sync duration. """ last_id = -1 total_synced = 0 - with ( - graph_database.get_session(source_database) as source_session, - graph_database.get_session(target_database) as target_session, - ): - while True: - rows = list( - source_session.run( - RELATIONSHIPS_FETCH_QUERY, - {"last_id": last_id, "batch_size": BATCH_SIZE}, - ) + while True: + grouped: dict[str, list[dict[str, Any]]] = defaultdict(list) + batch_count = 0 + + with graph_database.get_session(source_database) as source_session: + result = source_session.run( + RELATIONSHIPS_FETCH_QUERY, + {"last_id": last_id, "batch_size": SYNC_BATCH_SIZE}, ) + for record in result: + batch_count += 1 + last_id = record["internal_id"] + key, value = _rel_to_sync_dict(record, provider_id) + grouped[key].append(value) - if not rows: - break - - last_id = rows[-1]["internal_id"] - - grouped: dict[str, list[dict[str, Any]]] = defaultdict(list) - for row in rows: - props = dict(row["props"] or {}) - _strip_internal_properties(props) - rel_type = row["rel_type"] - grouped[rel_type].append( - { - "start_element_id": f"{provider_id}:{row['start_element_id']}", - "end_element_id": f"{provider_id}:{row['end_element_id']}", - "provider_element_id": f"{provider_id}:{rel_type}:{row['internal_id']}", - "props": props, - } - ) + if batch_count == 0: + break + with graph_database.get_session(target_database) as target_session: for rel_type, batch in grouped.items(): query = render_cypher_template( RELATIONSHIP_SYNC_TEMPLATE, {"__REL_TYPE__": rel_type} @@ -198,14 +182,42 @@ def sync_relationships( }, ) - total_synced += len(rows) - logger.info( - f"Synced {total_synced} relationships from {source_database} to {target_database}" - ) + total_synced += batch_count + logger.info( + f"Synced {total_synced} relationships from {source_database} to {target_database}" + ) return total_synced +def _node_to_sync_dict( + record: neo4j.Record, provider_id: str +) -> tuple[tuple[str, ...], dict[str, Any]]: + """Transform a source node record into a (grouping_key, sync_dict) pair.""" + props = dict(record["props"] or {}) + _strip_internal_properties(props) + labels = tuple(sorted(set(record["labels"] or []))) + return labels, { + "provider_element_id": f"{provider_id}:{record['element_id']}", + "props": props, + } + + +def _rel_to_sync_dict( + record: neo4j.Record, provider_id: str +) -> tuple[str, dict[str, Any]]: + """Transform a source relationship record into a (grouping_key, sync_dict) pair.""" + props = dict(record["props"] or {}) + _strip_internal_properties(props) + rel_type = record["rel_type"] + return rel_type, { + "start_element_id": f"{provider_id}:{record['start_element_id']}", + "end_element_id": f"{provider_id}:{record['end_element_id']}", + "provider_element_id": f"{provider_id}:{rel_type}:{record['internal_id']}", + "props": props, + } + + def _strip_internal_properties(props: dict[str, Any]) -> None: """Remove provider isolation properties before the += spread in sync templates.""" for key in PROVIDER_ISOLATION_PROPERTIES: diff --git a/api/src/backend/tasks/tests/test_attack_paths_scan.py b/api/src/backend/tasks/tests/test_attack_paths_scan.py index 43a2420321..dc621f173b 100644 --- a/api/src/backend/tasks/tests/test_attack_paths_scan.py +++ b/api/src/backend/tasks/tests/test_attack_paths_scan.py @@ -1279,16 +1279,10 @@ class TestAttackPathsFindingsHelpers: provider.provider = Provider.ProviderChoices.AWS provider.save() - # Create mock Finding objects with to_dict() method - mock_finding_1 = MagicMock() - mock_finding_1.to_dict.return_value = {"id": "1", "resource_uid": "r-1"} - mock_finding_2 = MagicMock() - mock_finding_2.to_dict.return_value = {"id": "2", "resource_uid": "r-2"} - - # Create a generator that yields two batches of Finding instances + # Create a generator that yields two batches of dicts (pre-converted) def findings_generator(): - yield [mock_finding_1] - yield [mock_finding_2] + yield [{"id": "1", "resource_uid": "r-1"}] + yield [{"id": "2", "resource_uid": "r-2"}] config = SimpleNamespace(update_tag=12345) mock_session = MagicMock() @@ -1435,17 +1429,17 @@ class TestAttackPathsFindingsHelpers: assert len(findings_data) == 1 finding_result = findings_data[0] - assert finding_result.id == str(finding.id) - assert finding_result.resource_uid == resource.uid - assert finding_result.check_title == "Check title" - assert finding_result.scan_id == str(latest_scan.id) + assert finding_result["id"] == str(finding.id) + assert finding_result["resource_uid"] == resource.uid + assert finding_result["check_title"] == "Check title" + assert finding_result["scan_id"] == str(latest_scan.id) def test_enrich_batch_with_resources_single_resource( self, tenants_fixture, providers_fixture, ): - """One finding + one resource = one output Finding instance""" + """One finding + one resource = one output dict""" tenant = tenants_fixture[0] provider = providers_fixture[0] provider.provider = Provider.ProviderChoices.AWS @@ -1519,16 +1513,16 @@ class TestAttackPathsFindingsHelpers: ) assert len(result) == 1 - assert result[0].resource_uid == resource.uid - assert result[0].id == str(finding.id) - assert result[0].status == "FAIL" + assert result[0]["resource_uid"] == resource.uid + assert result[0]["id"] == str(finding.id) + assert result[0]["status"] == "FAIL" def test_enrich_batch_with_resources_multiple_resources( self, tenants_fixture, providers_fixture, ): - """One finding + three resources = three output Finding instances""" + """One finding + three resources = three output dicts""" tenant = tenants_fixture[0] provider = providers_fixture[0] provider.provider = Provider.ProviderChoices.AWS @@ -1607,13 +1601,13 @@ class TestAttackPathsFindingsHelpers: ) assert len(result) == 3 - result_resource_uids = {r.resource_uid for r in result} + result_resource_uids = {r["resource_uid"] for r in result} assert result_resource_uids == {r.uid for r in resources} # All should have same finding data for r in result: - assert r.id == str(finding.id) - assert r.status == "FAIL" + assert r["id"] == str(finding.id) + assert r["status"] == "FAIL" def test_enrich_batch_with_resources_no_resources_skips( self, @@ -1690,16 +1684,12 @@ class TestAttackPathsFindingsHelpers: provider.save() scan_id = "some-scan-id" - with ( - patch("tasks.jobs.attack_paths.findings.rls_transaction") as mock_rls, - patch("tasks.jobs.attack_paths.findings.Finding") as mock_finding, - ): + with patch("tasks.jobs.attack_paths.findings.rls_transaction") as mock_rls: # Create generator but don't iterate findings_module.stream_findings_with_resources(provider, scan_id) # Nothing should be called yet mock_rls.assert_not_called() - mock_finding.objects.filter.assert_not_called() def test_load_findings_empty_generator(self, providers_fixture): """Empty generator should not call neo4j""" @@ -1752,41 +1742,226 @@ class TestAddResourceLabel: assert "AWSResource" not in query.replace("_AWSResource", "") +def _make_session_ctx(session, call_order=None, name=None): + """Create a mock context manager wrapping a mock session.""" + ctx = MagicMock() + if call_order is not None and name is not None: + ctx.__enter__ = MagicMock( + side_effect=lambda: (call_order.append(f"{name}:enter"), session)[1] + ) + ctx.__exit__ = MagicMock( + side_effect=lambda *a: (call_order.append(f"{name}:exit"), False)[1] + ) + else: + ctx.__enter__ = MagicMock(return_value=session) + ctx.__exit__ = MagicMock(return_value=False) + return ctx + + class TestSyncNodes: def test_sync_nodes_adds_private_label(self): - mock_source_session = MagicMock() - mock_target_session = MagicMock() - row = { "internal_id": 1, "element_id": "elem-1", "labels": ["SomeLabel"], "props": {"key": "value"}, } - mock_source_session.run.side_effect = [[row], []] - source_ctx = MagicMock() - source_ctx.__enter__ = MagicMock(return_value=mock_source_session) - source_ctx.__exit__ = MagicMock(return_value=False) - - target_ctx = MagicMock() - target_ctx.__enter__ = MagicMock(return_value=mock_target_session) - target_ctx.__exit__ = MagicMock(return_value=False) + mock_source_1 = MagicMock() + mock_source_1.run.return_value = [row] + mock_target = MagicMock() + mock_source_2 = MagicMock() + mock_source_2.run.return_value = [] with patch( "tasks.jobs.attack_paths.sync.graph_database.get_session", - side_effect=[source_ctx, target_ctx], + side_effect=[ + _make_session_ctx(mock_source_1), + _make_session_ctx(mock_target), + _make_session_ctx(mock_source_2), + ], ): total = sync_module.sync_nodes( "source-db", "target-db", "tenant-1", "prov-1" ) assert total == 1 - query = mock_target_session.run.call_args.args[0] + query = mock_target.run.call_args.args[0] assert "_ProviderResource" in query assert "_Tenant_tenant1" in query assert "_Provider_prov1" in query + def test_sync_nodes_source_closes_before_target_opens(self): + row = { + "internal_id": 1, + "element_id": "elem-1", + "labels": ["SomeLabel"], + "props": {"key": "value"}, + } + + call_order = [] + + src_1 = MagicMock() + src_1.run.return_value = [row] + tgt = MagicMock() + src_2 = MagicMock() + src_2.run.return_value = [] + + with patch( + "tasks.jobs.attack_paths.sync.graph_database.get_session", + side_effect=[ + _make_session_ctx(src_1, call_order, "source1"), + _make_session_ctx(tgt, call_order, "target"), + _make_session_ctx(src_2, call_order, "source2"), + ], + ): + sync_module.sync_nodes("src-db", "tgt-db", "t-1", "p-1") + + assert call_order.index("source1:exit") < call_order.index("target:enter") + + def test_sync_nodes_pagination_with_batch_size_1(self): + row_a = { + "internal_id": 1, + "element_id": "elem-1", + "labels": ["LabelA"], + "props": {"a": 1}, + } + row_b = { + "internal_id": 2, + "element_id": "elem-2", + "labels": ["LabelB"], + "props": {"b": 2}, + } + + src_1 = MagicMock() + src_1.run.return_value = [row_a] + src_2 = MagicMock() + src_2.run.return_value = [row_b] + src_3 = MagicMock() + src_3.run.return_value = [] + tgt_1 = MagicMock() + tgt_2 = MagicMock() + + with ( + patch( + "tasks.jobs.attack_paths.sync.graph_database.get_session", + side_effect=[ + _make_session_ctx(src_1), + _make_session_ctx(tgt_1), + _make_session_ctx(src_2), + _make_session_ctx(tgt_2), + _make_session_ctx(src_3), + ], + ), + patch("tasks.jobs.attack_paths.sync.SYNC_BATCH_SIZE", 1), + ): + total = sync_module.sync_nodes("src", "tgt", "t-1", "p-1") + + assert total == 2 + assert src_1.run.call_args.args[1]["last_id"] == -1 + assert src_2.run.call_args.args[1]["last_id"] == 1 + + def test_sync_nodes_empty_source_returns_zero(self): + src = MagicMock() + src.run.return_value = [] + + with patch( + "tasks.jobs.attack_paths.sync.graph_database.get_session", + side_effect=[_make_session_ctx(src)], + ) as mock_get_session: + total = sync_module.sync_nodes("src", "tgt", "t-1", "p-1") + + assert total == 0 + assert mock_get_session.call_count == 1 + + +class TestSyncRelationships: + def test_sync_relationships_source_closes_before_target_opens(self): + row = { + "internal_id": 1, + "rel_type": "HAS", + "start_element_id": "s-1", + "end_element_id": "e-1", + "props": {}, + } + + call_order = [] + + src_1 = MagicMock() + src_1.run.return_value = [row] + tgt = MagicMock() + src_2 = MagicMock() + src_2.run.return_value = [] + + with patch( + "tasks.jobs.attack_paths.sync.graph_database.get_session", + side_effect=[ + _make_session_ctx(src_1, call_order, "source1"), + _make_session_ctx(tgt, call_order, "target"), + _make_session_ctx(src_2, call_order, "source2"), + ], + ): + sync_module.sync_relationships("src", "tgt", "p-1") + + assert call_order.index("source1:exit") < call_order.index("target:enter") + + def test_sync_relationships_pagination_with_batch_size_1(self): + row_a = { + "internal_id": 1, + "rel_type": "HAS", + "start_element_id": "s-1", + "end_element_id": "e-1", + "props": {"a": 1}, + } + row_b = { + "internal_id": 2, + "rel_type": "CONNECTS", + "start_element_id": "s-2", + "end_element_id": "e-2", + "props": {"b": 2}, + } + + src_1 = MagicMock() + src_1.run.return_value = [row_a] + src_2 = MagicMock() + src_2.run.return_value = [row_b] + src_3 = MagicMock() + src_3.run.return_value = [] + tgt_1 = MagicMock() + tgt_2 = MagicMock() + + with ( + patch( + "tasks.jobs.attack_paths.sync.graph_database.get_session", + side_effect=[ + _make_session_ctx(src_1), + _make_session_ctx(tgt_1), + _make_session_ctx(src_2), + _make_session_ctx(tgt_2), + _make_session_ctx(src_3), + ], + ), + patch("tasks.jobs.attack_paths.sync.SYNC_BATCH_SIZE", 1), + ): + total = sync_module.sync_relationships("src", "tgt", "p-1") + + assert total == 2 + assert src_1.run.call_args.args[1]["last_id"] == -1 + assert src_2.run.call_args.args[1]["last_id"] == 1 + + def test_sync_relationships_empty_source_returns_zero(self): + src = MagicMock() + src.run.return_value = [] + + with patch( + "tasks.jobs.attack_paths.sync.graph_database.get_session", + side_effect=[_make_session_ctx(src)], + ) as mock_get_session: + total = sync_module.sync_relationships("src", "tgt", "p-1") + + assert total == 0 + assert mock_get_session.call_count == 1 + class TestInternetAnalysis: def _make_provider_and_config(self): From 75c4f114752ce6194b63129f8326ac329fe2b5a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Wed, 18 Mar 2026 10:28:46 +0100 Subject: [PATCH 029/366] feat(ui): add skills system infrastructure to Lighthouse AI (#10322) Co-authored-by: alejandrobailo --- ui/CHANGELOG.md | 4 ++ ui/components/lighthouse/chat-utils.ts | 16 ++++- ui/lib/lighthouse/analyst-stream.ts | 70 +++++++++++++++------- ui/lib/lighthouse/constants.ts | 3 + ui/lib/lighthouse/skills/index.ts | 8 +++ ui/lib/lighthouse/skills/registry.ts | 21 +++++++ ui/lib/lighthouse/skills/types.ts | 10 ++++ ui/lib/lighthouse/system-prompt.ts | 31 +++++++++- ui/lib/lighthouse/tools/load-skill.ts | 82 ++++++++++++++++++++++++++ ui/lib/lighthouse/workflow.ts | 9 ++- 10 files changed, 228 insertions(+), 26 deletions(-) create mode 100644 ui/lib/lighthouse/skills/index.ts create mode 100644 ui/lib/lighthouse/skills/registry.ts create mode 100644 ui/lib/lighthouse/skills/types.ts create mode 100644 ui/lib/lighthouse/tools/load-skill.ts diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index dc451861c5..f30f068463 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -6,6 +6,10 @@ All notable changes to the **Prowler UI** are documented in this file. ### 🚀 Added +- Add skill system to Lighthouse AI [(#10322)](https://github.com/prowler-cloud/prowler/pull/10322) + +### 🔄 Changed + - Google Workspace provider support [(#10333)](https://github.com/prowler-cloud/prowler/pull/10333) - Image (Container Registry) provider support in UI: badge icon, credentials form, and provider-type filtering [(#10167)](https://github.com/prowler-cloud/prowler/pull/10167) - Organization and organizational unit row actions (Edit Name, Update Credentials, Test Connections, Delete) in providers table dropdown [(#10317)](https://github.com/prowler-cloud/prowler/pull/10317) diff --git a/ui/components/lighthouse/chat-utils.ts b/ui/components/lighthouse/chat-utils.ts index 8af28b83cb..70ac9e0b05 100644 --- a/ui/components/lighthouse/chat-utils.ts +++ b/ui/components/lighthouse/chat-utils.ts @@ -9,6 +9,7 @@ import { MESSAGE_ROLES, MESSAGE_STATUS, META_TOOLS, + SKILL_PREFIX, } from "@/lib/lighthouse/constants"; import type { ChainOfThoughtData, Message } from "@/lib/lighthouse/types"; @@ -70,17 +71,28 @@ export function getChainOfThoughtStepLabel( return `Executing ${tool}`; } + if (metaTool === META_TOOLS.LOAD_SKILL && tool) { + const skillId = tool.startsWith(SKILL_PREFIX) + ? tool.slice(SKILL_PREFIX.length) + : tool; + return `Loading ${skillId} skill`; + } + return tool || "Completed"; } /** - * Determines if a meta-tool is a wrapper tool (describe_tool or execute_tool) + * Determines if a tool name is a meta-tool (describe_tool, execute_tool, or load_skill) * * @param metaTool - The meta-tool name to check * @returns True if it's a meta-tool, false otherwise */ export function isMetaTool(metaTool: string): boolean { - return metaTool === META_TOOLS.DESCRIBE || metaTool === META_TOOLS.EXECUTE; + return ( + metaTool === META_TOOLS.DESCRIBE || + metaTool === META_TOOLS.EXECUTE || + metaTool === META_TOOLS.LOAD_SKILL + ); } /** diff --git a/ui/lib/lighthouse/analyst-stream.ts b/ui/lib/lighthouse/analyst-stream.ts index 95050b1001..13d5648ba0 100644 --- a/ui/lib/lighthouse/analyst-stream.ts +++ b/ui/lib/lighthouse/analyst-stream.ts @@ -9,6 +9,7 @@ import { ERROR_PREFIX, LIGHTHOUSE_AGENT_TAG, META_TOOLS, + SKILL_PREFIX, STREAM_MESSAGE_ID, } from "@/lib/lighthouse/constants"; import type { ChainOfThoughtData, StreamEvent } from "@/lib/lighthouse/types"; @@ -16,10 +17,35 @@ import type { ChainOfThoughtData, StreamEvent } from "@/lib/lighthouse/types"; // Re-export for convenience export { CHAIN_OF_THOUGHT_ACTIONS, ERROR_PREFIX, STREAM_MESSAGE_ID }; +/** + * Safely parses the JSON string nested inside a meta-tool's input wrapper. + * In tool stream events, meta-tools receive their arguments as `{ input: "" }`. + * Note: In chat_model_end events, args are pre-parsed by LangChain (see handleChatModelEndEvent). + * + * @returns The parsed object, or null if parsing fails + */ +function parseMetaToolInput( + toolInput: unknown, +): Record | null { + try { + if ( + toolInput && + typeof toolInput === "object" && + "input" in toolInput && + typeof toolInput.input === "string" + ) { + return JSON.parse(toolInput.input) as Record; + } + } catch { + // Failed to parse + } + return null; +} + /** * Extracts the actual tool name from meta-tool input. * - * Meta-tools (describe_tool, execute_tool) wrap actual tool calls. + * Meta-tools (describe_tool, execute_tool, load_skill) wrap actual tool calls. * This function parses the input to extract the real tool name. * * @param metaToolName - The name of the meta-tool or actual tool @@ -30,26 +56,19 @@ export function extractActualToolName( metaToolName: string, toolInput: unknown, ): string | null { - // Check if this is a meta-tool if ( metaToolName === META_TOOLS.DESCRIBE || metaToolName === META_TOOLS.EXECUTE ) { - // Meta-tool: Parse the JSON string in input.input - try { - if ( - toolInput && - typeof toolInput === "object" && - "input" in toolInput && - typeof toolInput.input === "string" - ) { - const parsedInput = JSON.parse(toolInput.input); - return parsedInput.toolName || null; - } - } catch { - // Failed to parse, return null - return null; - } + const parsed = parseMetaToolInput(toolInput); + return (parsed?.toolName as string) || null; + } + + if (metaToolName === META_TOOLS.LOAD_SKILL) { + const parsed = parseMetaToolInput(toolInput); + return parsed?.skillId + ? `${SKILL_PREFIX}${parsed.skillId as string}` + : null; } // Actual tool execution: use the name directly @@ -172,11 +191,18 @@ export function handleChatModelEndEvent( const metaToolName = toolCall.name; const toolArgs = toolCall.args; - // Extract actual tool name from toolArgs.toolName (camelCase) - const actualToolName = - toolArgs && typeof toolArgs === "object" && "toolName" in toolArgs - ? (toolArgs.toolName as string) - : null; + // Extract actual tool name from toolArgs + let actualToolName: string | null = null; + if (toolArgs && typeof toolArgs === "object") { + if ("toolName" in toolArgs) { + actualToolName = toolArgs.toolName as string; + } else if ( + metaToolName === META_TOOLS.LOAD_SKILL && + "skillId" in toolArgs + ) { + actualToolName = `${SKILL_PREFIX}${toolArgs.skillId as string}`; + } + } controller.enqueue( createChainOfThoughtEvent({ diff --git a/ui/lib/lighthouse/constants.ts b/ui/lib/lighthouse/constants.ts index 6fbb30947f..cea67eabb4 100644 --- a/ui/lib/lighthouse/constants.ts +++ b/ui/lib/lighthouse/constants.ts @@ -6,6 +6,7 @@ export const META_TOOLS = { DESCRIBE: "describe_tool", EXECUTE: "execute_tool", + LOAD_SKILL: "load_skill", } as const; export type MetaTool = (typeof META_TOOLS)[keyof typeof META_TOOLS]; @@ -68,5 +69,7 @@ export const STREAM_MESSAGE_ID = "msg-1"; export const ERROR_PREFIX = "[LIGHTHOUSE_ANALYST_ERROR]:"; +export const SKILL_PREFIX = "skill:"; + export const TOOLS_UNAVAILABLE_MESSAGE = "\nProwler tools are unavailable. You cannot access cloud accounts or security scan data. If asked about security status or scan results, inform the user that this data is currently inaccessible.\n"; diff --git a/ui/lib/lighthouse/skills/index.ts b/ui/lib/lighthouse/skills/index.ts new file mode 100644 index 0000000000..2ec8d586d7 --- /dev/null +++ b/ui/lib/lighthouse/skills/index.ts @@ -0,0 +1,8 @@ +// Re-export registry functions and types +export { + getAllSkillMetadata, + getRegisteredSkillIds, + getSkillById, + registerSkill, +} from "./registry"; +export type { SkillDefinition, SkillMetadata } from "./types"; diff --git a/ui/lib/lighthouse/skills/registry.ts b/ui/lib/lighthouse/skills/registry.ts new file mode 100644 index 0000000000..bd23c3f61c --- /dev/null +++ b/ui/lib/lighthouse/skills/registry.ts @@ -0,0 +1,21 @@ +import "server-only"; + +import type { SkillDefinition, SkillMetadata } from "./types"; + +const skillRegistry = new Map(); + +export function registerSkill(skill: SkillDefinition): void { + skillRegistry.set(skill.metadata.id, skill); +} + +export function getAllSkillMetadata(): SkillMetadata[] { + return Array.from(skillRegistry.values()).map((skill) => skill.metadata); +} + +export function getSkillById(id: string): SkillDefinition | undefined { + return skillRegistry.get(id); +} + +export function getRegisteredSkillIds(): string[] { + return Array.from(skillRegistry.keys()); +} diff --git a/ui/lib/lighthouse/skills/types.ts b/ui/lib/lighthouse/skills/types.ts new file mode 100644 index 0000000000..dfb7104be6 --- /dev/null +++ b/ui/lib/lighthouse/skills/types.ts @@ -0,0 +1,10 @@ +export interface SkillMetadata { + id: string; + name: string; + description: string; +} + +export interface SkillDefinition { + metadata: SkillMetadata; + instructions: string; +} diff --git a/ui/lib/lighthouse/system-prompt.ts b/ui/lib/lighthouse/system-prompt.ts index c91ec56d9c..be3ec099e3 100644 --- a/ui/lib/lighthouse/system-prompt.ts +++ b/ui/lib/lighthouse/system-prompt.ts @@ -3,6 +3,8 @@ * * {{TOOL_LISTING}} placeholder will be replaced with dynamically generated tool list */ +import type { SkillMetadata } from "@/lib/lighthouse/skills/types"; + export const LIGHTHOUSE_SYSTEM_PROMPT_TEMPLATE = ` ## Introduction @@ -45,7 +47,7 @@ You have access to tools from multiple sources: ## Tool Usage -You have access to TWO meta-tools to interact with the available tools: +You have access to THREE meta-tools to interact with the available tools and skills: 1. **describe_tool** - Get detailed schema for a specific tool - Use exact tool name from the list above @@ -59,6 +61,13 @@ You have access to TWO meta-tools to interact with the available tools: - Example: execute_tool({ "toolName": "prowler_hub_list_providers", "toolInput": {} }) - Example: execute_tool({ "toolName": "prowler_app_search_security_findings", "toolInput": { "severity": ["critical", "high"], "status": ["FAIL"] } }) +3. **load_skill** - Load specialized instructions for a complex task + - Use when you identify a matching skill from the skill catalog below + - Returns detailed workflows, schema knowledge, and examples + - Example: load_skill({ "skillId": "" }) + +{{SKILL_CATALOG}} + ## General Instructions - **DON'T ASSUME**. Base your answers on the system prompt or tool outputs before responding to the user. @@ -229,6 +238,26 @@ When providing proactive recommendations to secure users' cloud accounts, follow - Prowler Documentation: https://docs.prowler.com/ `; +/** + * Generates the skill catalog section for the system prompt. + * Lists all registered skills with their metadata so the LLM can match user requests. + */ +export function generateSkillCatalog(skills: SkillMetadata[]): string { + if (skills.length === 0) { + return ""; + } + + let catalog = "## Skill Catalog\n\n"; + catalog += + "When a user request matches a skill below, use load_skill to get detailed instructions before proceeding.\n\n"; + + for (const skill of skills) { + catalog += `- **${skill.id}**: ${skill.name} - ${skill.description}\n`; + } + + return catalog; +} + /** * Generates the user-provided data section with security boundary */ diff --git a/ui/lib/lighthouse/tools/load-skill.ts b/ui/lib/lighthouse/tools/load-skill.ts new file mode 100644 index 0000000000..6c57d03ede --- /dev/null +++ b/ui/lib/lighthouse/tools/load-skill.ts @@ -0,0 +1,82 @@ +import "server-only"; + +import { tool } from "@langchain/core/tools"; +import { addBreadcrumb } from "@sentry/nextjs"; +import { z } from "zod"; + +import { + getRegisteredSkillIds, + getSkillById, +} from "@/lib/lighthouse/skills/index"; + +interface SkillLoadedResult { + found: true; + skillId: string; + name: string; + instructions: string; +} + +interface SkillNotFoundResult { + found: false; + skillId: string; + message: string; + availableSkills: string[]; +} + +type LoadSkillResult = SkillLoadedResult | SkillNotFoundResult; + +export const loadSkill = tool( + async ({ skillId }: { skillId: string }): Promise => { + addBreadcrumb({ + category: "skill", + message: `load_skill called for: ${skillId}`, + level: "info", + data: { skillId }, + }); + + const skill = getSkillById(skillId); + + if (!skill) { + const availableSkills = getRegisteredSkillIds(); + + addBreadcrumb({ + category: "skill", + message: `Skill not found: ${skillId}`, + level: "warning", + data: { skillId, availableSkills }, + }); + + return { + found: false, + skillId, + message: `Skill '${skillId}' not found.`, + availableSkills, + }; + } + + return { + found: true, + skillId: skill.metadata.id, + name: skill.metadata.name, + instructions: skill.instructions, + }; + }, + { + name: "load_skill", + description: `Load detailed instructions for a specialized skill. + +Skills provide domain-specific guidance, workflows, and schema knowledge for complex tasks. +Use this when you identify a relevant skill from the skill catalog in your system prompt. + +Returns: +- Skill metadata (id, name) +- Full skill instructions with workflows and examples`, + schema: z.object({ + skillId: z + .string() + .describe( + "The ID of the skill to load (from the skill catalog in your system prompt)", + ), + }), + }, +); diff --git a/ui/lib/lighthouse/workflow.ts b/ui/lib/lighthouse/workflow.ts index bc27b7fe4d..c94adaf8db 100644 --- a/ui/lib/lighthouse/workflow.ts +++ b/ui/lib/lighthouse/workflow.ts @@ -12,10 +12,13 @@ import { initializeMCPClient, isMCPAvailable, } from "@/lib/lighthouse/mcp-client"; +import { getAllSkillMetadata } from "@/lib/lighthouse/skills/index"; import { + generateSkillCatalog, generateUserDataSection, LIGHTHOUSE_SYSTEM_PROMPT_TEMPLATE, } from "@/lib/lighthouse/system-prompt"; +import { loadSkill } from "@/lib/lighthouse/tools/load-skill"; import { describeTool, executeTool } from "@/lib/lighthouse/tools/meta-tool"; import { getModelParams } from "@/lib/lighthouse/utils"; @@ -136,6 +139,10 @@ export async function initLighthouseWorkflow(runtimeConfig?: RuntimeConfig) { toolListing, ); + // Generate and inject skill catalog + const skillCatalog = generateSkillCatalog(getAllSkillMetadata()); + systemPrompt = systemPrompt.replace("{{SKILL_CATALOG}}", skillCatalog); + // Add user-provided data section if available const userDataSection = generateUserDataSection( runtimeConfig?.businessContext, @@ -177,7 +184,7 @@ export async function initLighthouseWorkflow(runtimeConfig?: RuntimeConfig) { const agent = createAgent({ model: llm, - tools: [describeTool, executeTool], + tools: [describeTool, executeTool, loadSkill], systemPrompt, }); From 8c1e69b5420635eb629a06c8fd83e9ae40b3fd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Wed, 18 Mar 2026 10:39:04 +0100 Subject: [PATCH 030/366] feat(mcp): add cartography schema tool for attack paths (#10321) --- mcp_server/CHANGELOG.md | 6 +++ .../prowler_app/models/attack_paths.py | 45 ++++++++++++++++ .../prowler_app/tools/attack_paths.py | 51 +++++++++++++++++++ .../prowler_app/utils/api_client.py | 45 ++++++++++++++++ 4 files changed, 147 insertions(+) diff --git a/mcp_server/CHANGELOG.md b/mcp_server/CHANGELOG.md index 88f3b72a75..0b2589fb7c 100644 --- a/mcp_server/CHANGELOG.md +++ b/mcp_server/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to the **Prowler MCP Server** are documented in this file. +## [0.5.0] (Prowler v5.20.0 UNRELEASED) + +### 🚀 Added + +- Attack Path tool to get Neo4j DB schema [(#10321)](https://github.com/prowler-cloud/prowler/pull/10321) + ## [0.4.0] (Prowler v5.19.0) ### 🚀 Added diff --git a/mcp_server/prowler_mcp_server/prowler_app/models/attack_paths.py b/mcp_server/prowler_mcp_server/prowler_app/models/attack_paths.py index bc048918aa..cfd704eeca 100644 --- a/mcp_server/prowler_mcp_server/prowler_app/models/attack_paths.py +++ b/mcp_server/prowler_mcp_server/prowler_app/models/attack_paths.py @@ -118,6 +118,51 @@ class AttackPathScansListResponse(BaseModel): ) +class AttackPathCartographySchema(MinimalSerializerMixin, BaseModel): + """Cartography graph schema metadata for a completed attack paths scan. + + Contains the schema URL and provider info needed to fetch the full + Cartography schema markdown for openCypher query generation. + """ + + model_config = ConfigDict(frozen=True) + + id: str = Field(description="Unique identifier for the schema resource") + provider: str = Field(description="Cloud provider type (aws, azure, gcp, etc.)") + cartography_version: str = Field(description="Version of the Cartography schema") + schema_url: str = Field(description="URL to the Cartography schema page on GitHub") + raw_schema_url: str = Field( + description="Raw URL to fetch the Cartography schema markdown content" + ) + schema_content: str | None = Field( + default=None, + description="Full Cartography schema markdown content (populated after fetch)", + ) + + @classmethod + def from_api_response( + cls, response: dict[str, Any] + ) -> "AttackPathCartographySchema": + """Transform JSON:API schema response to model. + + Args: + response: Full API response with data and attributes + + Returns: + AttackPathCartographySchema instance + """ + data = response.get("data", {}) + attributes = data.get("attributes", {}) + + return cls( + id=data["id"], + provider=attributes["provider"], + cartography_version=attributes["cartography_version"], + schema_url=attributes["schema_url"], + raw_schema_url=attributes["raw_schema_url"], + ) + + class AttackPathQueryParameter(MinimalSerializerMixin, BaseModel): """Parameter definition for an attack paths query. diff --git a/mcp_server/prowler_mcp_server/prowler_app/tools/attack_paths.py b/mcp_server/prowler_mcp_server/prowler_app/tools/attack_paths.py index 8d7119b9cc..ff9b8045a4 100644 --- a/mcp_server/prowler_mcp_server/prowler_app/tools/attack_paths.py +++ b/mcp_server/prowler_mcp_server/prowler_app/tools/attack_paths.py @@ -8,6 +8,7 @@ through cloud infrastructure relationships. from typing import Any, Literal from prowler_mcp_server.prowler_app.models.attack_paths import ( + AttackPathCartographySchema, AttackPathQuery, AttackPathQueryResult, AttackPathScansListResponse, @@ -225,3 +226,53 @@ class AttackPathsTools(BaseTool): f"Failed to run attack paths query '{query_id}' on scan {scan_id}: {e}" ) return {"error": f"Failed to run attack paths query '{query_id}': {str(e)}"} + + async def get_attack_paths_cartography_schema( + self, + scan_id: str = Field( + description="UUID of a COMPLETED attack paths scan. Use `prowler_app_list_attack_paths_scans` with state=['completed'] to find scan IDs" + ), + ) -> dict[str, Any]: + """Retrieve the Cartography graph schema for a completed attack paths scan. + + This tool fetches the full Cartography schema (node labels, relationships, + and properties) so the LLM can write accurate custom openCypher queries + for attack paths analysis. + + Two-step flow: + 1. Calls the Prowler API to get schema metadata (provider, version, URLs) + 2. Fetches the raw Cartography schema markdown from GitHub + + Returns: + - id: Schema resource identifier + - provider: Cloud provider type + - cartography_version: Schema version + - schema_url: GitHub page URL for reference + - raw_schema_url: Raw markdown URL + - schema_content: Full Cartography schema markdown with node/relationship definitions + + Workflow: + 1. Use prowler_app_list_attack_paths_scans to find a completed scan + 2. Use this tool to get the schema for the scan's provider + 3. Use the schema to craft custom openCypher queries + 4. Execute queries with prowler_app_run_attack_paths_query + """ + try: + api_response = await self.api_client.get( + f"/attack-paths-scans/{scan_id}/schema" + ) + + schema = AttackPathCartographySchema.from_api_response(api_response) + + schema_content = await self.api_client.fetch_external_url( + schema.raw_schema_url + ) + + return schema.model_copy( + update={"schema_content": schema_content} + ).model_dump() + except Exception as e: + self.logger.error( + f"Failed to get cartography schema for scan {scan_id}: {e}" + ) + return {"error": f"Failed to get cartography schema: {str(e)}"} diff --git a/mcp_server/prowler_mcp_server/prowler_app/utils/api_client.py b/mcp_server/prowler_mcp_server/prowler_app/utils/api_client.py index 43a7e59a42..4b6d0e77f5 100644 --- a/mcp_server/prowler_mcp_server/prowler_app/utils/api_client.py +++ b/mcp_server/prowler_mcp_server/prowler_app/utils/api_client.py @@ -4,11 +4,15 @@ import asyncio from datetime import datetime, timedelta from enum import Enum from typing import Any, Dict +from urllib.parse import urlparse import httpx +from prowler_mcp_server import __version__ from prowler_mcp_server.lib.logger import logger from prowler_mcp_server.prowler_app.utils.auth import ProwlerAppAuth +ALLOWED_EXTERNAL_DOMAINS: frozenset[str] = frozenset({"raw.githubusercontent.com"}) + class HTTPMethod(str, Enum): """HTTP methods enum.""" @@ -187,6 +191,47 @@ class ProwlerAPIClient(metaclass=SingletonMeta): """ return await self._make_request(HTTPMethod.DELETE, path, params=params) + async def fetch_external_url(self, url: str) -> str: + """Fetch content from an allowed external URL (unauthenticated). + + Uses the existing singleton httpx client with a domain allowlist + to prevent SSRF attacks. + + Args: + url: The external URL to fetch content from + + Returns: + Raw text content from the URL + + Raises: + ValueError: If the URL domain is not in the allowlist + Exception: If the HTTP request fails + """ + parsed = urlparse(url) + if parsed.scheme != "https": + raise ValueError(f"Only HTTPS URLs are allowed, got '{parsed.scheme}'") + if parsed.hostname not in ALLOWED_EXTERNAL_DOMAINS: + raise ValueError( + f"Domain '{parsed.hostname}' is not allowed. " + f"Allowed domains: {', '.join(sorted(ALLOWED_EXTERNAL_DOMAINS))}" + ) + + try: + response = await self.client.get( + url, + headers={"User-Agent": f"prowler-mcp-server/{__version__}"}, + ) + response.raise_for_status() + return response.text + except httpx.HTTPStatusError as e: + logger.error(f"HTTP error fetching external URL {url}: {e}") + raise Exception( + f"Failed to fetch external URL: {e.response.status_code}" + ) from e + except Exception as e: + logger.error(f"Error fetching external URL {url}: {e}") + raise + async def poll_task_until_complete( self, task_id: str, From bc43eed7361f7f4d922a395aa82c8d1c07cb2baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 18 Mar 2026 10:40:58 +0100 Subject: [PATCH 031/366] feat(compliance): add SecNumCloud 3.2 for AlibabaCloud (#10370) --- .../secnumcloud_3_2_alibabacloud.py | 24 + prowler/CHANGELOG.md | 1 + .../secnumcloud_3.2_alibabacloud.json | 1432 +++++++++++++++++ 3 files changed, 1457 insertions(+) create mode 100644 dashboard/compliance/secnumcloud_3_2_alibabacloud.py create mode 100644 prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json diff --git a/dashboard/compliance/secnumcloud_3_2_alibabacloud.py b/dashboard/compliance/secnumcloud_3_2_alibabacloud.py new file mode 100644 index 0000000000..2d5517aed6 --- /dev/null +++ b/dashboard/compliance/secnumcloud_3_2_alibabacloud.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_format3 + +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_format3( + aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID" + ) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index d33b1c0b50..387f1fae88 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. - `organization_repository_deletion_limited` check for GitHub provider [(#10185)](https://github.com/prowler-cloud/prowler/pull/10185) - SecNumCloud 3.2 compliance framework for the GCP provider [(#10364)](https://github.com/prowler-cloud/prowler/pull/10364) - SecNumCloud 3.2 for the Azure provider [(#10358)](https://github.com/prowler-cloud/prowler/pull/10358) +- SecNumCloud 3.2 for the Alibaba Cloud provider [(#10370)](https://github.com/prowler-cloud/prowler/pull/10370) ### 🔄 Changed diff --git a/prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json b/prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json new file mode 100644 index 0000000000..e71dc8b869 --- /dev/null +++ b/prowler/compliance/alibabacloud/secnumcloud_3.2_alibabacloud.json @@ -0,0 +1,1432 @@ +{ + "Framework": "SecNumCloud", + "Name": "SecNumCloud Referentiel d'Exigences v3.2", + "Version": "3.2", + "Provider": "AlibabaCloud", + "Description": "The SecNumCloud framework is published by ANSSI (Agence Nationale de la Securite des Systemes d'Information) to qualify cloud service providers operating in France. Version 3.2, dated March 8, 2022, covers IaaS, CaaS, PaaS, and SaaS services with requirements spanning information security policies, access control, cryptography, physical security, operational security, communications security, and data sovereignty protections against extra-European law.", + "Requirements": [ + { + "Id": "5.1", + "Description": "Le prestataire doit definir et appliquer des principes de securite de l'information adaptes a ses activites de fourniture de services cloud.", + "Name": "Principes", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit operer la prestation a l'etat de l'art pour le type d'activite retenu : utiliser des logiciels stables beneficiant d'un suivi des correctifs de securite et parametres de facon a obtenir un niveau de securite optimal. b) Le prestataire doit appliquer le guide d'hygiene informatique de l'ANSSI [HYGIENE], niveau renforce, au systeme d'information du service." + } + ], + "Checks": [] + }, + { + "Id": "5.2", + "Description": "Le prestataire doit definir, faire approuver par la direction, publier et communiquer aux salaries et aux tiers concernes un ensemble de politiques de securite de l'information.", + "Name": "Politique de securite de l'information", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de securite de l'information relative au service. b) La politique de securite de l'information doit identifier les engagements du prestataire quant au respect de la legislation et reglementation nationale en vigueur selon la nature des informations qui pourraient etre confiees par le commanditaire au prestataire ; il revient en revanche in fine au commanditaire de s'assurer du respect des contraintes legales et reglementaires applicables aux donnees qu'il confie effectivement au prestataire. c) La politique de securite de l'information doit notamment couvrir les themes abordes aux chapitres 6 a 19 du present referentiel. d) La direction du prestataire doit approuver formellement la politique de securite de l'information. e) Le prestataire doit reviser annuellement la politique de securite de l'information et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "5.3", + "Description": "Le prestataire doit definir et appliquer un processus d'appreciation des risques de securite de l'information.", + "Name": "Appreciation des risques", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter une appreciation des risques couvrant l'ensemble du perimetre du service. b) Le prestataire doit realiser son appreciation de risques en utilisant une methode documentee garantissant la reproductibilite et comparabilite de la demarche. c) Le prestataire doit prendre en compte dans l'appreciation des risques : la gestion d'informations du commanditaire ayant des besoins de securite differents ; les risques ayant des impacts sur les droits et libertes des personnes concernees en cas d'acces non autorise, de modification non desiree et de disparition de donnees a caractere personnel ; les risques de defaillance des mecanismes de cloisonnement des ressources de l'infrastructure technique (memoire, calcul, stockage, reseau) partagees entre les commanditaires ; les risques lies a l'effacement incomplet ou non securise des donnees stockees sur les espaces de memoire ou de stockage partages entre commanditaires, en particulier lors des reallocations des espaces de memoire et de stockage ; les risques lies a l'exposition des interfaces d'administration sur un reseau public ; les risques d'atteinte a la confidentialite des donnees des commanditaires par des tiers impliques dans la fourniture du service (fournisseurs, sous-traitants, etc.) ; les risques lies aux evenements naturels et sinistres physiques ; les risques lies a la separation des taches (voir 6.2.a) ; les risques lies aux environnements de developpement (voir 14.4.b). d) Le prestataire doit lister, dans un document specifique, les risques residuels lies a l'existence de lois extra-europeennes ayant pour objectif la collecte de donnees ou metadonnees des commanditaires sans leur consentement prealable. e) Le prestataire doit mettre a la disposition du commanditaire, sur demande de celui-ci, les elements d'appreciation des risques lies a la soumission des donnees du commanditaire au droit d'un etat non-membre de l'Union Europeenne. f) Lorsqu'il existe des exigences legales, reglementaires ou sectorielles specifiques liees aux types d'informations confiees par le commanditaire au prestataire, ce dernier doit les prendre en compte dans son appreciation des risques en s'assurant de respecter l'ensemble des exigences du present referentiel d'une part et de ne pas abaisser le niveau de securite etabli par le respect des exigences du present referentiel d'autre part. g) La direction du prestataire doit accepter formellement les risques residuels identifies dans l'appreciation des risques. h) Le prestataire doit reviser annuellement l'appreciation des risques et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "6.1", + "Description": "Le prestataire doit definir et attribuer toutes les responsabilites en matiere de securite de l'information.", + "Name": "Fonctions et responsabilites liees a la securite de l'information", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une organisation interne de la securite pour assurer la definition, la mise en place et le suivi du fonctionnement operationnel de la securite de l'information au sein de son organisation. b) Le prestataire doit designer un responsable de la securite des systemes d'information et un responsable de la securite physique. c) Le prestataire doit definir et attribuer les responsabilites en matiere de securite de l'information pour le personnel implique dans la fourniture du service. d) Le prestataire doit s'assurer apres tout changement majeur pouvant avoir un impact sur le service que l'attribution des responsabilites en matiere de securite de l'information est toujours pertinente. e) Le prestataire doit definir et attribuer les responsabilites en matiere de protection de donnees a caractere personnel, en coherence avec son role dans les traitements de donnees a caractere personnel (responsable de traitement, sous-traitant ou co-responsable). f) Le prestataire doit, lorsqu'il traite un grand nombre de donnees parmi lesquelles figurent des categories particulieres de donnees a caractere personnel telles que definies dans [RGPD], designer un delegue a la protection des donnees. g) Il est recommande que le prestataire, quel que soit le volume de donnees a caractere personnel qu'il traite, designe un delegue a la protection des donnees. h) Le prestataire doit realiser ou contribuer a la realisation d'une analyse d'impact relative a la protection des donnees a caractere personnel lorsque le traitement est susceptible d'engendrer un risque eleve pour les droits et libertes des personnes concernees (traitement de categories particulieres de donnees a caractere personnel telles que definies dans [RGPD], traitement de donnees a grande echelle, etc.). Cette analyse doit comporter une evaluation juridique du respect des principes et droits fondamentaux, ainsi qu'une etude plus technique des mesures techniques mises en oeuvre pour proteger les personnes des risques pour leur vie privee." + } + ], + "Checks": [] + }, + { + "Id": "6.2", + "Description": "Le prestataire doit separer les taches et les domaines de responsabilite incompatibles afin de reduire les possibilites de modification non autorisee ou de mauvais usage des actifs.", + "Name": "Separation des taches", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les risques associes a des cumuls de responsabilites ou de taches, les prendre en compte dans l'appreciation des risques et mettre en oeuvre des mesures de reduction de ces risques." + } + ], + "Checks": [] + }, + { + "Id": "6.3", + "Description": "Le prestataire doit etablir et maintenir des relations appropriees avec les autorites competentes.", + "Name": "Relations avec les autorites", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire mette en place des relations appropriees avec les autorites competentes en matiere de securite de l'information et de donnees a caractere personnel et, le cas echeant, avec les autorites sectorielles selon la nature des informations confiees par le commanditaire au prestataire." + } + ], + "Checks": [] + }, + { + "Id": "6.4", + "Description": "Le prestataire doit etablir et maintenir des relations appropriees avec des groupes de travail specialises, des associations professionnelles ou des forums traitant de la securite.", + "Name": "Relations avec les groupes de travail specialises", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire entretienne des contacts appropries avec des groupes de specialistes ou des sources reconnues, notamment pour prendre en compte de nouvelles menaces et les mesures de securite appropriees pour les contrer." + } + ], + "Checks": [] + }, + { + "Id": "6.5", + "Description": "Le prestataire doit integrer la securite de l'information dans la gestion de projet, quel que soit le type de projet.", + "Name": "La securite de l'information dans la gestion de projet", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter une estimation des risques prealablement a tout projet pouvant avoir un impact sur le service, et ce quelle que soit la nature du projet. b) Dans la mesure ou un projet affecte ou est susceptible d'affecter le niveau de securite du service, le prestataire doit avertir le commanditaire et l'informer par ecrit des impacts potentiels, des mesures mises en place pour reduire ces impacts ainsi que des risques residuels le concernant." + } + ], + "Checks": [] + }, + { + "Id": "7.1", + "Description": "Le prestataire doit s'assurer que les candidats a l'embauche font l'objet de verifications proportionnees aux exigences metier, a la classification des informations accessibles et aux risques identifies.", + "Name": "Selection des candidats", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de verification des informations concernant son personnel conforme aux lois et reglements en vigueur. Ces verifications s'appliquent a toute personne impliquee dans la fourniture du service et doivent etre proportionnelles a la sensibilite ou a la specificite des informations du commanditaire confiees au prestataire ainsi qu'aux risques identifies. b) Pour les personnels disposant de privileges d'administration eleves sur les composants logiciels et materiels de l'infrastructure, le prestataire doit renforcer les verifications destinees a verifier que les antecedents de ceux-ci ne sont pas incompatibles avec l'exercice de leurs fonctions. Il est entendu par des privileges d'administration eleves, des actions permettant l'elevation de privileges ou la possibilite de realiser des actions sans traces techniques ou de desactiver, alterer les traces techniques." + } + ], + "Checks": [] + }, + { + "Id": "7.2", + "Description": "Les accords contractuels avec les salaries et les sous-traitants doivent preciser leurs responsabilites et celles du prestataire en matiere de securite de l'information.", + "Name": "Conditions d'embauche", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit disposer d'une charte d'ethique integree au reglement interieur, prevoyant notamment que : les prestations sont realisees avec loyaute, discretion et impartialite et dans des conditions de confidentialite des informations traitees ; les personnels ne recourent qu'aux methodes, outils et techniques valides par le prestataire ; les personnels s'engagent a ne pas divulguer d'informations a un tiers, meme anonymisees et decontextualisees, obtenues ou generees dans le cadre de la prestation sauf autorisation formelle et ecrite du commanditaire ; les personnels s'engagent a signaler au prestataire tout contenu manifestement illicite decouvert pendant la prestation ; les personnels s'engagent a respecter la legislation et la reglementation nationale en vigueur et les bonnes pratiques liees a leurs activites. b) Le prestataire doit faire signer la charte d'ethique a l'ensemble des personnes impliquees dans la fourniture du service. c) Le prestataire doit introduire, dans le contrat de travail des personnels disposant de privileges d'administration eleves sur les composants et materiels de l'infrastructure du service, un engagement de responsabilite avec un renvoi aux clauses du code du travail sur la protection du secret des affaires et de la propriete intellectuelle. Il est entendu par des privileges d'administration eleves, des actions permettant l'elevation de privileges ou la possibilite de realiser des actions sans traces techniques ou de desactiver, alterer les traces techniques. d) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible le reglement interieur et la charte d'ethique." + } + ], + "Checks": [] + }, + { + "Id": "7.3", + "Description": "Les salaries du prestataire et, le cas echeant, les sous-traitants doivent suivre un programme de sensibilisation et de formation adapte et regulier concernant la securite de l'information.", + "Name": "Sensibilisation, apprentissage et formations a la securite de l'information", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit sensibiliser a la securite de l'information et aux risques lies a la protection des donnees l'ensemble des personnes impliquees dans la fourniture du service. Il doit leur communiquer les mises a jour des politiques et procedures pertinentes dans le cadre de leurs missions. b) Le prestataire doit documenter et mettre en oeuvre un plan de formation concernant la securite de l'information adapte au service et aux missions des personnels. c) Le responsable de la securite des systemes d'information du prestataire doit valider formellement le plan de formation concernant la securite de l'information." + } + ], + "Checks": [] + }, + { + "Id": "7.4", + "Description": "Le prestataire doit mettre en place un processus disciplinaire formel et communique pour prendre des mesures a l'encontre des salaries ayant enfreint les regles de securite de l'information.", + "Name": "Processus disciplinaire", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus disciplinaire applicable a l'ensemble des personnes impliquees dans la fourniture du service ayant enfreint la politique de securite. b) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible les sanctions encourues en cas d'infraction a la politique de securite." + } + ], + "Checks": [] + }, + { + "Id": "7.5", + "Description": "Les responsabilites et les obligations en matiere de securite de l'information qui restent valables apres un changement ou une rupture du contrat de travail doivent etre definies, communiquees au salarie ou au sous-traitant et appliquees.", + "Name": "Rupture, terme ou modification du contrat de travail", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit definir et attribuer les roles et les responsabilites relatives a la rupture, au terme ou a la modification de tout contrat avec une personne impliquee dans la fourniture du service." + } + ], + "Checks": [] + }, + { + "Id": "8.1", + "Description": "Le prestataire doit identifier les actifs associes a l'information et aux moyens de traitement de l'information et doit etablir et tenir a jour un inventaire de ces actifs.", + "Name": "Inventaire et propriete des actifs", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "securitycenter", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit tenir a jour l'inventaire de l'ensemble des equipements mettant en oeuvre le service. Cet inventaire doit preciser pour chaque equipement : les informations d'identification de l'equipement (noms, adresses IP, adresses MAC, etc.) ; la fonction de l'equipement ; le modele de l'equipement ; la localisation de l'equipement ; le proprietaire de l'equipement ; le besoin de securite des informations (au sens du chapitre 8.3). b) Le prestataire doit tenir a jour l'inventaire de l'ensemble des logiciels mettant en oeuvre le service. Cet inventaire doit identifier pour chaque logiciel, sa version et les equipements sur lesquels le logiciel est installe. c) Le prestataire doit s'assurer de la validite des licences des logiciels tout au long de la prestation." + } + ], + "Checks": [ + "securitycenter_all_assets_agent_installed" + ] + }, + { + "Id": "8.2", + "Description": "Les salaries et les utilisateurs de tiers doivent restituer tous les actifs du prestataire en leur possession au terme de la periode d'emploi, du contrat ou de l'accord.", + "Name": "Restitution des actifs", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de restitution des actifs permettant de s'assurer que chaque personne impliquee dans la fourniture du service restitue l'ensemble des actifs en sa possession a la fin de sa periode d'emploi ou de son contrat." + } + ], + "Checks": [] + }, + { + "Id": "8.3", + "Description": "Les besoins de protection de la confidentialite, de l'integrite et de la disponibilite de l'information doivent etre identifies.", + "Name": "Identification des besoins de securite de l'information", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les differents besoins de securite des informations relatives au service. b) Lorsque le commanditaire confie au prestataire des donnees soumises a des contraintes legales, reglementaires ou sectorielles specifiques, le prestataire doit identifier les besoins de securite specifiques associes a ces contraintes." + } + ], + "Checks": [] + }, + { + "Id": "8.4", + "Description": "Un ensemble de procedures appropriees pour le marquage et la manipulation de l'information doit etre elabore et mis en oeuvre conformement au plan de classification adopte par le prestataire.", + "Name": "Marquage et manipulation de l'information", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire documente et mette en oeuvre une procedure pour le marquage et la manipulation de toutes les informations participant a la delivrance du service, conformement a son besoin de securite defini au chapitre 8.3." + } + ], + "Checks": [] + }, + { + "Id": "8.5", + "Description": "Des procedures de gestion des supports amovibles doivent etre mises en oeuvre conformement au plan de classification adopte par le prestataire.", + "Name": "Gestion des supports amovibles", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure pour la gestion des supports amovibles, conformement au besoin de securite defini au chapitre 8.3. Lorsque des supports amovibles sont utilises sur l'infrastructure technique ou pour des taches d'administration, ces supports doivent etre dedies a un usage." + } + ], + "Checks": [] + }, + { + "Id": "9.1", + "Description": "Une politique de controle d'acces doit etre etablie, documentee et revue en se basant sur les exigences metier et les exigences de securite de l'information. Les regles de controle d'acces et les droits pour chaque utilisateur ou groupe d'utilisateurs doivent etre clairement definis.", + "Name": "Politiques et controle d'acces", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "ram", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de controle d'acces sur la base du resultat de son appreciation des risques et du partage des responsabilites. b) Le prestataire doit reviser annuellement la politique de controle d'acces et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [ + "ram_policy_no_administrative_privileges", + "ram_policy_attached_only_to_group_or_roles" + ] + }, + { + "Id": "9.2", + "Description": "Un processus formel d'enregistrement et de desinscription des utilisateurs doit etre mis en oeuvre pour permettre l'attribution des droits d'acces.", + "Name": "Enregistrement et desinscription des utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "ram", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure d'enregistrement et de desinscription des utilisateurs s'appuyant sur une interface de gestion des comptes et des droits d'acces. Cette procedure doit indiquer quelles donnees doivent etre supprimees au depart d'un utilisateur. b) Le prestataire doit attribuer des comptes nominatifs lors de l'enregistrement des utilisateurs places sous sa responsabilite. c) Le prestataire doit mettre en oeuvre des moyens permettant de s'assurer que la desinscription d'un utilisateur entraine la suppression de tous ses acces aux ressources du systeme d'information du service, ainsi que la suppression de ses donnees conformement a la procedure d'enregistrement et de desinscription (voir exigence 9.2 a))." + } + ], + "Checks": [ + "ram_user_console_access_unused" + ] + }, + { + "Id": "9.3", + "Description": "Un processus formel de gestion des droits d'acces doit etre mis en oeuvre pour controler l'attribution des droits d'acces a tous les types d'utilisateurs et a tous les systemes et services.", + "Name": "Gestion des droits d'acces", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "ram", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'assurer l'attribution, la modification et le retrait de droits d'acces aux ressources du systeme d'information du service. b) Le prestataire doit mettre a la disposition de ses commanditaires les outils et les moyens qui permettent une differenciation des roles des utilisateurs du service, par exemple suivant leur role fonctionnel. c) Le prestataire doit tenir a jour l'inventaire des utilisateurs sous sa responsabilite disposant de droits d'administration sur les ressources du systeme d'information du service. d) Le prestataire doit etre en mesure de fournir, pour une ressource donnee mettant en oeuvre le service, la liste de tous les utilisateurs y ayant acces, qu'ils soient sous la responsabilite du prestataire ou du commanditaire ainsi que les droits d'acces qui leurs ont ete attribues. e) Le prestataire doit etre en mesure de fournir, pour un utilisateur donne, qu'ils soient sous la responsabilite du prestataire ou du commanditaire, la liste de tous ses droits d'acces sur les differents elements du systeme d'information du service. f) Le prestataire doit definir une liste de droits d'acces incompatibles entre eux. Il doit s'assurer, lors de l'attribution de droits d'acces a un utilisateur qu'il ne possede pas de droits d'acces incompatibles entre eux au titre de la liste precedemment etablie. g) Le prestataire doit inclure dans la procedure de gestion des droits d'acces les actions de revocation ou de suspension des droits de tout utilisateur." + } + ], + "Checks": [ + "ram_policy_no_administrative_privileges", + "ram_policy_attached_only_to_group_or_roles", + "ram_no_root_access_key" + ] + }, + { + "Id": "9.4", + "Description": "Les proprietaires d'actifs doivent verifier les droits d'acces des utilisateurs a intervalles reguliers.", + "Name": "Revue des droits d'acces utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "ram", + "Type": "Automated", + "Comment": "a) Le prestataire doit reviser annuellement les droits d'acces des utilisateurs sur son perimetre de responsabilite. b) Le prestataire doit mettre a disposition du commanditaire un outil facilitant la revue des droits d'acces des utilisateurs places sous la responsabilite de ce dernier. c) Le prestataire doit reviser trimestriellement la liste des utilisateurs sur son perimetre de responsabilite pouvant utiliser les comptes techniques mentionnes dans l'exigence 9.2 b)." + } + ], + "Checks": [ + "ram_rotate_access_key_90_days", + "ram_user_console_access_unused" + ] + }, + { + "Id": "9.5", + "Description": "L'attribution et l'utilisation des informations secretes d'authentification doivent etre gerees dans le cadre d'un processus de gestion formel incluant une politique de mot de passe robuste et l'utilisation de l'authentification multi-facteur.", + "Name": "Gestion des authentifications des utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "ram", + "Type": "Automated", + "Comment": "a) Le prestataire doit formaliser et mettre en oeuvre des procedures de gestion de l'authentification des utilisateurs. En accord avec les exigences du chapitre 10, celles-ci doivent notamment porter sur : la gestion des moyens d'authentification (emission et reinitialisation de mot de passe, mise a jour des CRL et import des certificats racines en cas d'utilisation de certificats, etc.) ; la mise en place des moyens permettant une authentification a multiples facteurs afin de repondre aux differents cas d'usage du referentiel ; les systemes qui generent des mots de passe ou verifient leur robustesse, lorsqu'une authentification par mot de passe est utilisee. Ils doivent suivre les recommandations de [G_AUTH]. b) Tout mecanisme d'authentification doit prevoir le blocage d'un compte apres un nombre limite de tentatives infructueuses. c) Dans le cadre d'un service SaaS, le prestataire doit proposer a ses commanditaires des moyens d'authentification a multiples facteurs pour l'acces des utilisateurs finaux. d) Lorsque des comptes techniques, non nominatifs, sont necessaires, le prestataire doit mettre en place des mesures obligeant les utilisateurs a s'authentifier avec leur compte nominatif avant de pouvoir acceder a ces comptes techniques." + } + ], + "Checks": [ + "ram_password_policy_minimum_length", + "ram_password_policy_lowercase", + "ram_password_policy_uppercase", + "ram_password_policy_number", + "ram_password_policy_symbol", + "ram_password_policy_max_password_age", + "ram_password_policy_password_reuse_prevention", + "ram_password_policy_max_login_attempts", + "ram_user_mfa_enabled_console_access" + ] + }, + { + "Id": "9.6", + "Description": "L'acces aux interfaces d'administration du service cloud doit etre restreint et protege par des mecanismes d'authentification forte, incluant l'utilisation de dispositifs MFA materiels pour les comptes a privileges.", + "Name": "Acces aux interfaces d'administration", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "ram", + "Type": "Automated", + "Comment": "a) Les comptes d'administration sous la responsabilite du prestataire doivent etre geres a l'aide d'outils et d'annuaires distincts de ceux utilises pour la gestion des comptes utilisateurs places sous la responsabilite du commanditaire. b) Les interfaces d'administration mises a disposition des commanditaires doivent etre distinctes des interfaces d'administration utilisees par le prestataire. c) Les interfaces d'administration mises a disposition des commanditaires ne doivent permettre aucune connexion avec des comptes d'administrateurs sous la responsabilite du prestataire. d) Les interfaces d'administration utilisees par le prestataire ne doivent pas etre accessibles a partir d'un reseau public et ainsi ne doivent permettre aucune connexion des utilisateurs sous la responsabilite du commanditaire. e) Si des interfaces d'administration sont mises a disposition des commanditaires avec un acces via un reseau public, les flux d'administration doivent etre authentifies et chiffres avec des moyens en accord avec les exigences du chapitre 10.2. f) Le prestataire doit mettre en place un systeme d'authentification multifacteur fort pour l'acces : aux interfaces d'administration utilisees par le prestataire ; aux interfaces d'administration dediees aux commanditaires. g) Dans le cadre d'un service SaaS, les interfaces d'administration mises a disposition des commanditaires doivent etre differenciees des interfaces permettant l'acces des utilisateurs finaux. h) Des lors qu'une interface d'administration est accessible depuis un reseau public, le processus d'authentification doit avoir lieu avant toute interaction entre l'utilisateur et l'interface en question. i) Lorsque le prestataire utilise un service de type IaaS comme socle d'un autre type de service (CaaS, PaaS ou SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service IaaS. j) Lorsque le prestataire utilise un service de type CaaS comme socle d'un autre type de service (PaaS ou SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service CaaS. k) Lorsque le prestataire utilise un service de type PaaS comme socle d'un autre type de service (typiquement SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service PaaS." + } + ], + "Checks": [ + "ram_no_root_access_key", + "ram_user_mfa_enabled_console_access", + "ecs_securitygroup_restrict_rdp_internet", + "ecs_securitygroup_restrict_ssh_internet" + ] + }, + { + "Id": "9.7", + "Description": "L'acces a l'information et aux fonctions d'application des systemes doit etre restreint conformement a la politique de controle d'acces. Les ressources doivent etre protegees contre tout acces public non autorise.", + "Name": "Restriction des acces a l'information", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "ecs", + "Type": "Automated", + "Comment": "a) Le prestataire doit mettre en oeuvre des mesures de cloisonnement appropriees entre ses commanditaires. b) Le prestataire doit mettre en oeuvre des mesures de cloisonnement appropriees entre le systeme d'information du service et ses autres systemes d'information (bureautique, informatique de gestion, gestion technique du batiment, controle d'acces physique, etc.). c) Le prestataire doit concevoir, developper, configurer et deployer le systeme d'information du service en assurant au moins un cloisonnement entre d'une part l'infrastructure technique et d'autre part les equipements necessaires a l'administration des services et des ressources qu'elle heberge. d) Dans le cadre du support technique, si les actions necessaires au diagnostic et a la resolution d'un probleme rencontre par un commanditaire necessitent un acces aux donnees du commanditaire, alors le prestataire doit : n'autoriser l'acces aux donnees du commanditaire qu'apres consentement explicite du commanditaire ; verifier que la personne a qui l'acces doit etre autorise a satisfait aux verifications de l'exigence 7.1.b ; dans le cas d'une intervention realisee a distance par une personne localisee hors de l'Union Europeenne, mettre en oeuvre une passerelle securisee (poste de rebond) par laquelle la personne devra se connecter et permettant une supervision (autorisation ou interdiction des actions, demandes d'explications, etc.) en temps reel, par une personne ayant elle-meme satisfait aux verifications de l'exigence 7.1.b ; considerer les actions menees, une fois l'acces autorise, comme des actions d'administration et les journaliser comme telles ; supprimer l'autorisation d'acces aux donnees du commanditaire au terme de ces actions." + } + ], + "Checks": [ + "ecs_securitygroup_restrict_rdp_internet", + "ecs_securitygroup_restrict_ssh_internet", + "oss_bucket_not_publicly_accessible", + "ecs_instance_no_legacy_network", + "rds_instance_no_public_access_whitelist" + ] + }, + { + "Id": "10.1", + "Description": "Les donnees stockees dans le cadre du service cloud doivent etre chiffrees au repos en utilisant des algorithmes et des longueurs de cle conformes a l'etat de l'art.", + "Name": "Chiffrement des donnees stockees", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "ecs", + "Type": "Automated", + "Comment": "a) Le prestataire doit definir et mettre en oeuvre un mecanisme de chiffrement empechant la recuperation des donnees des commanditaires en cas de reallocation d'une ressource ou de recuperation du support physique. Dans le cas d'un service IaaS ou CaaS, cet objectif pourra par exemple etre atteint par un chiffrement du disque ou du systeme de fichier, lorsque le protocole d'acces en mode fichiers garantit que seuls des blocs vides peuvent etre alloues, ou par un chiffrement par volume dans le cas d'un acces en mode bloc, avec au moins une cle par commanditaire. Dans le cas d'un service PaaS ou SaaS, cet objectif pourra etre atteint en utilisant un chiffrement applicatif dans le perimetre du prestataire, avec au moins une cle par commanditaire. b) Le prestataire doit utiliser une methode de chiffrement des donnees respectant les regles de [CRYPTO_B1]. c) Il est recommande d'utiliser une methode de chiffrement des donnees respectant les recommandations de [CRYPTO_B1]. d) Le prestataire doit mettre en place un chiffrement des donnees sur les supports amovibles et les supports de sauvegarde amenes a quitter le perimetre de securite physique du systeme d'information du service (au sens du chapitre 10), en fonction du besoin de securite des donnees (voir chapitre 8.3)." + } + ], + "Checks": [ + "ecs_attached_disk_encrypted", + "ecs_unattached_disk_encrypted", + "rds_instance_tde_enabled", + "rds_instance_tde_key_custom" + ] + }, + { + "Id": "10.2", + "Description": "Les flux de donnees entre les composants du service cloud et entre le service et les commanditaires doivent etre chiffres en transit en utilisant des protocoles et des algorithmes conformes a l'etat de l'art.", + "Name": "Chiffrement des flux", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "oss", + "Type": "Automated", + "Comment": "a) Lorsque le prestataire met en oeuvre un mecanisme de chiffrement des flux reseau, celui-ci doit respecter les regles de [CRYPTO_B1]. b) Lorsque le prestataire met en oeuvre un mecanisme de chiffrement des flux reseau, il est recommande que celui-ci respecte les recommandations de [CRYPTO_B1]. c) Si le protocole TLS est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_TLS]. d) Si le protocole IPsec est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_IPSEC]. e) Si le protocole SSH est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_SSH]." + } + ], + "Checks": [ + "oss_bucket_secure_transport_enabled", + "rds_instance_ssl_enabled" + ] + }, + { + "Id": "10.3", + "Description": "Les mots de passe doivent etre stockes sous forme hachee en utilisant des algorithmes robustes conformes a l'etat de l'art et les politiques de mot de passe doivent imposer des exigences de complexite adequates.", + "Name": "Hachage des mots de passe", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "ram", + "Type": "Partially Automated", + "Comment": "a) Le prestataire ne doit stocker que l'empreinte des mots de passe des utilisateurs et des comptes techniques. b) Le prestataire doit mettre en oeuvre une fonction de hachage respectant les regles de [CRYPTO_B1]. c) Il est recommande que le prestataire mette en oeuvre une fonction de hachage respectant les recommandations de [CRYPTO_B1]. d) Le prestataire doit generer les empreintes des mots de passe avec une fonction de hachage associee a l'utilisation d'un sel cryptographique respectant les regles de [CRYPTO_B1]." + } + ], + "Checks": [ + "ram_password_policy_minimum_length", + "ram_password_policy_symbol", + "ram_password_policy_number" + ] + }, + { + "Id": "10.4", + "Description": "Des mecanismes de non-repudiation doivent etre mis en oeuvre pour assurer la tracabilite des actions effectuees sur le service cloud, incluant la validation de l'integrite des journaux.", + "Name": "Non repudiation", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "actiontrail", + "Type": "Partially Automated", + "Comment": "a) Lorsque le prestataire met en oeuvre un mecanisme de signature electronique, celui-ci doit respecter les regles de [CRYPTO_B1]. b) Lorsque le prestataire met en oeuvre un mecanisme de signature electronique, il est recommande que celui-ci respecte les recommandations de [CRYPTO_B1]." + } + ], + "Checks": [ + "actiontrail_oss_bucket_not_publicly_accessible" + ] + }, + { + "Id": "10.5", + "Description": "Les secrets cryptographiques (cles, certificats, mots de passe) doivent etre geres de maniere securisee tout au long de leur cycle de vie, incluant la generation, le stockage, la distribution, la rotation et la destruction.", + "Name": "Gestion des secrets", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "ram", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit mettre en oeuvre des cles cryptographiques respectant les regles de [CRYPTO_B2]. b) Il est recommande que le prestataire mette en oeuvre des cles cryptographiques respectant les recommandations de [CRYPTO_B2]. c) Le prestataire doit proteger l'acces aux cles cryptographiques et autres secrets utilises pour le chiffrement des donnees par un moyen adapte : conteneur de securite (logiciel ou materiel) ou support disjoint. d) Le prestataire doit proteger l'acces aux cles cryptographiques et autres secrets utilises pour les taches d'administration par un conteneur de securite adapte, logiciel ou materiel." + } + ], + "Checks": [ + "sls_customer_created_cmk_changes_alert_enabled", + "ram_rotate_access_key_90_days" + ] + }, + { + "Id": "10.6", + "Description": "Les racines de confiance (certificats racine, autorites de certification) utilisees dans le cadre du service cloud doivent etre gerees de maniere securisee. Les certificats doivent etre valides et utiliser des algorithmes de cle robustes.", + "Name": "Racines de confiance", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "general", + "Type": "Manual", + "Comment": "a) Sur l'infrastructure technique, le prestataire doit utiliser exclusivement des certificats de cle publique issus d'une autorite de certification d'un Etat membre de l'Union Europeenne (les ceremonies de generation des cles maitresses doivent avoir lieu dans un pays membre de l'Union Europeenne et en presence du prestataire)." + } + ], + "Checks": [] + }, + { + "Id": "11.1", + "Description": "Des perimetres de securite doivent etre definis et utilises pour proteger les zones contenant des informations sensibles ou critiques et les moyens de traitement de l'information.", + "Name": "Perimetres de securite physique", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des perimetres de securite, incluant le marquage des zones et les differents moyens de limitation et de controle des acces. b) Le prestataire doit distinguer des zones publiques, des zones privees et des zones sensibles. 11.1.1. Zones publiques : a) Les zones publiques sont accessibles a tous dans les limites de la propriete du prestataire. Le prestataire ne doit heberger aucune ressource devolue au service ou permettant d'acceder a des composantes de celui-ci dans les zones publiques. 11.1.2. Zones privees : a) Les zones privees peuvent heberger : les plateformes et moyens de developpement du service ; les postes d'administration, d'exploitation et de supervision ; les locaux a partir desquels le prestataire opere. 11.1.3. Zones sensibles : a) Les zones sensibles sont reservees a l'hebergement du systeme d'information de production du service hors postes d'administration, d'exploitation et de supervision." + } + ], + "Checks": [] + }, + { + "Id": "11.2", + "Description": "Les zones securisees doivent etre protegees par des controles d'acces physiques adequats pour s'assurer que seul le personnel autorise est admis.", + "Name": "Controle d'acces physique", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "11.2.1. Zones privees : a) Le prestataire doit proteger les zones privees contre les acces non autorises. Pour ce faire, il doit mettre en oeuvre un controle d'acces physique reposant au moins sur un facteur personnel : la connaissance d'un secret, la detention d'un objet ou la biometrie. b) Il est recommande que le prestataire respecte les recommandations de [G_CVAP] pour mettre en oeuvre du controle d'acces physique. c) Le prestataire doit definir et documenter des mesures d'acces physique derogatoires en cas d'urgence. d) Le prestataire doit afficher a l'entree des zones privees un avertissement relatif aux limites et conditions d'acces a ces zones. e) Le prestataire doit definir et documenter les plages horaires et conditions d'acces aux zones privees en fonction des profils des intervenants. f) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de s'assurer que les visiteurs sont systematiquement accompagnes par le prestataire lors de leurs acces et sejours en zone privee. Le prestataire doit conserver une trace de l'identite des visiteurs conformement a la legislation et reglementation en vigueur. g) En cas d'intervention (actions de diagnostic, de maintenance, ou d'administration) en zone privee par un tiers visiteur, le prestataire doit faire superviser (suivre, autoriser, interdire, questionner) les actions par un personnel ayant satisfait aux verifications de l'exigence 7.1.b. h) Le prestataire doit documenter et mettre en oeuvre des mecanismes de surveillance et de detection des acces non autorises aux zones privees. 11.2.2. Zones sensibles : a) Le prestataire doit proteger les zones sensibles contre les acces non autorises. Pour ce faire, il doit mettre en oeuvre un controle d'acces physique reposant au moins sur deux facteurs personnels : la connaissance d'un secret, la detention d'un objet ou la biometrie. b) Il est recommande que le prestataire respecte les recommandations de [G_CVAP] pour la mise en oeuvre du controle d'acces physique. c) Le prestataire doit definir et documenter des mesures d'acces physique derogatoires en cas d'urgence. d) Le prestataire doit afficher a l'entree des zones sensibles un avertissement relatif aux limites et conditions d'acces a ces zones. e) Le prestataire doit definir et documenter les plages horaires et conditions d'acces aux zones sensibles en fonction des profils des intervenants. f) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de s'assurer que les visiteurs sont systematiquement accompagnes par le prestataire lors de leurs acces et sejours en zone sensible. Le prestataire doit conserver une trace de l'identite des visiteurs conformement a la legislation et reglementation en vigueur. g) En cas d'intervention (actions de diagnostic, de maintenance, ou d'administration) en zone sensible par un tiers visiteur, le prestataire doit faire superviser (suivre, autoriser, interdire, questionner) les actions par un personnel ayant satisfait aux verifications de l'exigence 7.1.b. h) Le prestataire doit documenter et mettre en oeuvre des mecanismes de surveillance et de detection des acces non autorises aux zones sensibles. i) Le prestataire doit mettre en place une journalisation des acces physiques aux zones sensibles. Il doit effectuer une revue de ces journaux au moins mensuellement. j) Le prestataire doit mettre en oeuvre les moyens garantissant qu'aucun acces direct n'existe entre une zone publique et une zone sensible." + } + ], + "Checks": [] + }, + { + "Id": "11.3", + "Description": "Des mesures de protection contre les menaces exterieures et environnementales, telles que les catastrophes naturelles, les attaques malveillantes ou les accidents, doivent etre concues et appliquees.", + "Name": "Protection contre les menaces exterieures et environnementales", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de minimiser les risques inherents aux sinistres physiques (incendie, degat des eaux, etc.) et naturels (risques climatiques, inondations, seismes, etc.). b) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de limiter les risques de depart et de propagation de feu ainsi que les risques de degat des eaux. c) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de prevenir et limiter les consequences d'une coupure d'alimentation electrique et permettre une reprise du service conformement aux exigences de disponibilite du service definies dans la convention de service. d) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de maintenir des conditions de temperature et d'humidite adaptees aux equipements. De plus, il doit mettre en oeuvre des mesures permettant de prevenir les pannes de climatisation et d'en limiter les consequences. e) Le prestataire doit documenter et mettre en oeuvre des controles et tests reguliers des equipements de detection et de protection physique." + } + ], + "Checks": [] + }, + { + "Id": "11.4", + "Description": "Des mesures de securite physique pour le travail dans les zones privees et sensibles doivent etre concues et appliquees.", + "Name": "Travail dans les zones privees et sensibles", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit integrer les elements de securite physique dans la politique de securite et l'appreciation des risques conformement au niveau de securite requis par la categorie de la zone. b) Le prestataire doit documenter et mettre en oeuvre des procedures relatives au travail en zones privees et sensibles. Il doit communiquer ces procedures aux intervenants concernes." + } + ], + "Checks": [] + }, + { + "Id": "11.5", + "Description": "Les points d'acces tels que les zones de livraison et de chargement et les autres points par lesquels des personnes non autorisees peuvent penetrer dans les locaux doivent etre controles.", + "Name": "Zones de livraison et de chargement", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Les zones de livraison et de chargement et les autres points par lesquels des personnes non autorisees peuvent penetrer dans les locaux sans etre accompagnees sont considerees comme des zones publiques. b) Le prestataire doit isoler les points d'acces de ces zones vers les zones privees et sensibles, de facon a eviter les acces non autorises, ou a defaut, implementer des mesures compensatoires permettant d'assurer le meme niveau de securite." + } + ], + "Checks": [] + }, + { + "Id": "11.6", + "Description": "Le cablage electrique et de telecommunications transportant des donnees ou supportant des services d'information doit etre protege contre les interceptions, les interferences ou les dommages.", + "Name": "Securite du cablage", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de proteger le cablage electrique et de telecommunication des dommages physiques et des possibilites d'interception. b) Le prestataire doit etablir et tenir a jour un plan de cablage. c) Il est recommande que le prestataire mette en oeuvre des mesures permettant d'identifier les cables (par exemple code couleur, etiquette, etc.) afin d'en faciliter l'exploitation et limiter les erreurs de manipulation." + } + ], + "Checks": [] + }, + { + "Id": "11.7", + "Description": "Les materiels doivent etre entretenus correctement pour garantir leur disponibilite permanente et leur integrite.", + "Name": "Maintenance des materiels", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de s'assurer que les conditions d'installation, de maintenance et d'entretien des equipements du systeme d'information du service heberges en zones privees et sensibles sont compatibles avec les exigences de confidentialite et de disponibilite du service definies dans la convention de service. b) Le prestataire doit souscrire des contrats de maintenance permettant de disposer des mises a jour de securite des logiciels installes sur les equipements du systeme d'information du service. c) Le prestataire doit s'assurer que les supports ne peuvent etre retournes a un tiers que si les donnees du commanditaire y sont stockees chiffrees conformement au chapitre 10.1 ou ont prealablement ete detruites a l'aide d'un mecanisme d'effacement securise par reecriture de motifs aleatoires. d) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de s'assurer que les conditions d'installation, de maintenance et d'entretien des equipements techniques annexes (alimentation electrique, climatisation, incendie, etc.) sont compatibles avec les exigences de disponibilite du service definies dans la convention de service." + } + ], + "Checks": [] + }, + { + "Id": "11.8", + "Description": "Les materiels, les informations ou les logiciels ne doivent pas etre sortis des locaux du prestataire sans autorisation prealable.", + "Name": "Sortie des actifs", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de transfert hors site de donnees du commanditaire, equipements et logiciels. Cette procedure doit necessiter que la direction du prestataire donne son autorisation ecrite. Dans tous les cas, le prestataire doit mettre en oeuvre les moyens permettant de garantir que le niveau de protection en confidentialite et en integrite des actifs durant leur transport est equivalent a celui sur site." + } + ], + "Checks": [] + }, + { + "Id": "11.9", + "Description": "Tous les composants des equipements contenant des supports de stockage doivent etre verifies pour s'assurer que toute donnee sensible et tout logiciel sous licence ont ete supprimes ou ecrases de facon securisee avant leur mise au rebut ou leur reutilisation.", + "Name": "Recyclage securise du materiel", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des moyens permettant d'effacer de maniere securisee par reecriture de motifs aleatoires tout support de donnees mis a disposition d'un commanditaire. Si l'espace de stockage est chiffre dans le cadre de l'exigence 10.1.a), l'effacement peut etre realise par un effacement securise de la cle de chiffrement." + } + ], + "Checks": [] + }, + { + "Id": "11.10", + "Description": "Le materiel en attente d'utilisation doit etre protege de maniere adequate.", + "Name": "Materiel en attente d'utilisation", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de protection du materiel en attente d'utilisation." + } + ], + "Checks": [] + }, + { + "Id": "12.1", + "Description": "Les procedures d'exploitation doivent etre documentees et mises a disposition de tous les utilisateurs concernes.", + "Name": "Procedures d'exploitation documentees", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter les procedures d'exploitation, les tenir a jour et les rendre accessibles au personnel concerne." + } + ], + "Checks": [] + }, + { + "Id": "12.2", + "Description": "Les changements apportes au systeme d'information du prestataire, aux processus metier, aux moyens de traitement de l'information et aux systemes qui ont une incidence sur la securite de l'information doivent etre geres.", + "Name": "Gestion des changements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "actiontrail", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de gestion des changements apportes aux systemes et moyens de traitement de l'information. b) Le prestataire doit documenter et mettre en oeuvre une procedure permettant, en cas d'operations realisees par le prestataire et pouvant avoir un impact sur la securite ou la disponibilite du service, de communiquer au plus tot a l'ensemble de ses commanditaires les informations suivantes : la date et l'heure programmees du debut et de la fin des operations ; la nature des operations ; les impacts sur la securite ou la disponibilite du service ; le contact au sein du prestataire. c) Dans le cadre d'un service PaaS, le prestataire doit informer au plus tot le commanditaire de toute modification a venir sur des elements logiciels sous sa responsabilite des lors que la compatibilite complete ne peut etre assuree. d) Le prestataire doit informer au plus tot le commanditaire de toute modification a venir sur les elements du service des lors qu'elle est susceptible d'occasionner une perte de fonctionnalite pour le commanditaire." + } + ], + "Checks": [ + "actiontrail_multi_region_enabled", + "sls_logstore_retention_period" + ] + }, + { + "Id": "12.3", + "Description": "Les environnements de developpement, de test et d'exploitation doivent etre separes pour reduire les risques d'acces non autorise ou de changements non souhaites dans l'environnement d'exploitation.", + "Name": "Separation des environnements de developpement, de test et d'exploitation", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de separer physiquement les environnements lies a la production du service des autres environnements, dont les environnements de developpement." + } + ], + "Checks": [] + }, + { + "Id": "12.4", + "Description": "Des mesures de detection, de prevention et de recuperation conjuguees a une sensibilisation des utilisateurs doivent etre mises en oeuvre pour proteger le systeme d'information contre les codes malveillants.", + "Name": "Mesures contre les codes malveillants", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "securitycenter", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures de detection, de prevention et de restauration pour se proteger des codes malveillants. Le perimetre d'application de cette exigence sur le systeme d'information du service doit necessairement contenir les postes utilisateurs sous la responsabilite du prestataire et les flux entrants sur ce meme systeme d'information. b) Le prestataire doit documenter et mettre en oeuvre une sensibilisation de ses employes aux risques lies aux codes malveillants et aux bonnes pratiques pour reduire l'impact d'une infection." + } + ], + "Checks": [ + "securitycenter_advanced_or_enterprise_edition", + "securitycenter_all_assets_agent_installed", + "securitycenter_vulnerability_scan_enabled", + "securitycenter_notification_enabled_high_risk", + "ecs_instance_endpoint_protection_installed" + ] + }, + { + "Id": "12.5", + "Description": "Des copies de sauvegarde des informations, des logiciels et des images systeme doivent etre effectuees et testees regulierement conformement a une politique de sauvegarde convenue.", + "Name": "Sauvegarde des informations", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de sauvegarde et de restauration des donnees sous sa responsabilite dans le cadre du service. Cette politique doit prevoir une sauvegarde quotidienne de l'ensemble des donnees (informations, logiciels, configurations, etc.) sous la responsabilite du prestataire dans le cadre du service. b) Le prestataire doit documenter et mettre en oeuvre des mesures de protection des sauvegardes conformement a la politique de controle d'acces (voir chapitre 9). Cette politique doit prevoir une revue mensuelle des traces d'acces aux sauvegardes. c) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester regulierement la restauration des sauvegardes. d) Le prestataire doit localiser les sauvegardes a une distance suffisante des equipements principaux en coherence avec les resultats de l'appreciation de risques et permettant de faire face a des sinistres majeurs. Les sauvegardes sont assujetties aux memes exigences de localisation que les donnees operationnelles. Le ou les sites de sauvegarde sont assujettis aux memes exigences de securite que le site principal, en particulier celles listees aux chapitres 8 et 11. Les communications entre site principal et site de sauvegarde doivent etre protegees par chiffrement, conformement aux exigences du chapitre 10." + } + ], + "Checks": [] + }, + { + "Id": "12.6", + "Description": "Des journaux d'evenements enregistrant les activites des utilisateurs, les exceptions, les defaillances et les evenements de securite de l'information doivent etre crees, tenus a jour et regulierement revus.", + "Name": "Journalisation des evenements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "actiontrail", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de journalisation incluant au minimum les elements suivants : la liste des sources de collecte ; la liste des evenements a journaliser par source ; l'objet de la journalisation par evenement ; la frequence de la collecte et base de temps utilisee ; la duree de retention locale et centralisee ; les mesures de protection des journaux (dont chiffrement et duplication) ; la localisation des journaux. b) Le prestataire doit generer et collecter les evenements suivants : les activites des utilisateurs liees a la securite de l'information ; la modification des droits d'acces dans le perimetre de sa responsabilite ; les evenements issus des mecanismes de lutte contre les codes malveillants (voir chapitre 12.4) ; les exceptions ; les defaillances ; tout autre evenement lie a la securite de l'information. c) Le prestataire doit conserver les evenements issus de la journalisation pendant une duree minimale de six mois sous reserve du respect des exigences legales et reglementaires. d) Le prestataire doit fournir, sur demande d'un commanditaire, l'ensemble des evenements le concernant. e) Il est recommande que le systeme de journalisation mis en place par le prestataire respecte les recommandations de [NT_JOURNAL]." + } + ], + "Checks": [ + "actiontrail_multi_region_enabled", + "oss_bucket_logging_enabled", + "rds_instance_sql_audit_enabled", + "rds_instance_sql_audit_retention", + "sls_logstore_retention_period" + ] + }, + { + "Id": "12.7", + "Description": "Les moyens de journalisation et les informations journalisees doivent etre proteges contre les risques de falsification et les acces non autorises.", + "Name": "Protection de l'information journalisee", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "actiontrail", + "Type": "Automated", + "Comment": "a) Le prestataire doit proteger les equipements de journalisation et les evenements journalises contre les atteintes a leur disponibilite, integrite ou confidentialite, conformement au chapitre 3.2 de [NT_JOURNAL]. b) Le prestataire doit gerer le dimensionnement de l'espace de stockage de l'ensemble des equipements hebergeant une ou plusieurs sources de collecte afin de permettre la conservation locale des evenements journalises prevue par la politique de journalisation des evenements. Cette gestion du dimensionnement doit prendre en compte les evolutions du systeme d'information. c) Le prestataire doit transferer les evenements journalises en assurant leur protection en confidentialite et en integrite, sur un ou plusieurs serveurs centraux dedies et doit les stocker sur une machine physique distincte de celle qui les a generes. d) Le prestataire doit mettre en place une sauvegarde des evenements collectes suivant une politique adaptee. e) Le prestataire doit executer les processus de journalisation et de collecte des evenements avec des comptes disposant de privileges necessaires et suffisants et doit limiter l'acces aux evenements journalises conformement a la politique de controle d'acces (voir chapitre 9.1)." + } + ], + "Checks": [ + "actiontrail_oss_bucket_not_publicly_accessible", + "oss_bucket_not_publicly_accessible", + "sls_logstore_retention_period" + ] + }, + { + "Id": "12.8", + "Description": "Les horloges de tous les systemes de traitement de l'information pertinents d'un organisme ou d'un domaine de securite doivent etre synchronisees sur une source de reference temporelle unique.", + "Name": "Synchronisation des horloges", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une synchronisation des horloges de l'ensemble des equipements sur une ou plusieurs sources de temps internes coherentes entre elles. Ces sources pourront elles-memes etre synchronisees sur plusieurs sources fiables externes, sauf pour les reseaux isoles. b) Le prestataire doit mettre en place l'horodatage de chaque evenement journalise." + } + ], + "Checks": [] + }, + { + "Id": "12.9", + "Description": "Les evenements de securite doivent etre analyses et correles afin de detecter les incidents de securite. Des systemes de detection et de correlation doivent etre mis en oeuvre.", + "Name": "Analyse et correlation des evenements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "sls", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une infrastructure permettant l'analyse et la correlation des evenements enregistres par le systeme de journalisation afin de detecter les evenements susceptibles d'affecter la securite du systeme d'information du service, en temps reel ou a posteriori pour des evenements remontant jusqu'a six mois. b) Il est recommande de s'appuyer sur le referentiel d'exigences des prestataires de detection d'incidents de securite [PDIS] pour la mise en place et l'exploitation de l'infrastructure d'analyse et de correlation des evenements. c) Le prestataire doit acquitter les alarmes remontees par l'infrastructure d'analyse et de correlation des evenements au moins quotidiennement." + } + ], + "Checks": [ + "sls_root_account_usage_alert_enabled", + "sls_unauthorized_api_calls_alert_enabled", + "sls_management_console_authentication_failures_alert_enabled", + "sls_management_console_signin_without_mfa_alert_enabled", + "sls_security_group_changes_alert_enabled", + "sls_vpc_changes_alert_enabled", + "sls_vpc_network_route_changes_alert_enabled", + "sls_cloud_firewall_changes_alert_enabled", + "sls_oss_bucket_policy_changes_alert_enabled", + "sls_oss_permission_changes_alert_enabled", + "sls_ram_role_changes_alert_enabled", + "sls_rds_instance_configuration_changes_alert_enabled", + "sls_customer_created_cmk_changes_alert_enabled", + "securitycenter_advanced_or_enterprise_edition" + ] + }, + { + "Id": "12.10", + "Description": "Des regles regissant l'installation de logiciels par les utilisateurs doivent etre etablies et mises en oeuvre. Les systemes doivent etre geres de maniere centralisee et les correctifs appliques regulierement.", + "Name": "Installation de logiciels sur des systemes en exploitation", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "ecs", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de controler l'installation de logiciels sur les equipements du systeme d'information du service. b) Le prestataire doit documenter et mettre en oeuvre une procedure de gestion de la configuration des environnements logiciels mis a la disposition du commanditaire, notamment pour leur maintien en condition de securite. c) Le prestataire doit fournir une capacite d'inspection et de suppression, si necessaire, des entrants (controle de l'authenticite et de l'innocuite des mises a jour, controle de l'innocuite des outils fournis, etc.) relatifs au perimetre de l'infrastructure technique : cette capacite d'inspection et de suppression doit generer des journaux d'activite et doit pouvoir faire l'objet d'un audit de code ; les entrants doivent etre traites sur des dispositifs specifiques operes et maintenus par le prestataire et heberges dans une zone cloisonnee du reste de l'infrastructure (du type zone demilitarisee telle que definie dans [G_INT])." + } + ], + "Checks": [ + "ecs_instance_latest_os_patches_applied", + "ecs_instance_endpoint_protection_installed" + ] + }, + { + "Id": "12.11", + "Description": "Les informations sur les vulnerabilites techniques des systemes d'information utilises doivent etre obtenues en temps voulu, l'exposition du prestataire a ces vulnerabilites doit etre evaluee et les mesures appropriees doivent etre prises pour traiter le risque associe.", + "Name": "Gestion des vulnerabilites techniques", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "securitycenter", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus de veille permettant de gerer les vulnerabilites techniques des logiciels et des systemes utilises dans le systeme d'information du service. b) Le prestataire doit evaluer son exposition a ces vulnerabilites en les incluant dans l'appreciation des risques et appliquer les mesures de traitement du risque adaptees." + } + ], + "Checks": [ + "securitycenter_vulnerability_scan_enabled", + "securitycenter_advanced_or_enterprise_edition", + "ecs_instance_latest_os_patches_applied" + ] + }, + { + "Id": "12.12", + "Description": "L'administration des systemes d'information du service cloud doit etre effectuee de maniere securisee via des canaux dedies et des protocoles securises.", + "Name": "Administration", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "ecs", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure obligeant les administrateurs sous sa responsabilite a utiliser des terminaux dedies pour la realisation exclusive des taches d'administration, en accord avec le chapitre 4.1 intitule 'poste et reseau d'administration' de [NT_ADMIN]. Il doit les maitriser et les maintenir a jour. b) Le prestataire doit mettre en place des mesures de durcissement de la configuration des terminaux utilises pour les taches d'administration, notamment celles du chapitre 4.2 intitule 'securisation du socle' de [NT_ADMIN]. c) Lorsque le prestataire autorise une situation de mobilite pour les administrateurs sous sa responsabilite, il doit l'encadrer par une politique documentee. La solution mise en oeuvre doit assurer que le niveau de securite de cette situation de mobilite est au moins equivalent au niveau de securite hors situation de mobilite (voir chapitres 9.6 et 9.7). Cette solution doit notamment inclure : l'utilisation d'un tunnel chiffre, non debrayable et non contournable, pour l'ensemble des flux (voir chapitre 10.2) ; le chiffrement integral du disque (voir chapitre 10.1)." + } + ], + "Checks": [ + "cs_kubernetes_private_cluster_enabled", + "ram_user_mfa_enabled_console_access" + ] + }, + { + "Id": "12.13", + "Description": "Le telediagnostic et la telemaintenance des composants de l'infrastructure doivent etre encadres par des procedures de securite specifiques.", + "Name": "Telediagnostic et telemaintenance des composants de l'infrastructure", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Dans le cadre du telediagnostic ou de la telemaintenance de composants de l'infrastructure, considerant les risques d'atteinte a la confidentialite des donnees des commanditaires, le prestataire doit : verifier que la personne a qui l'acces doit etre autorise a satisfait aux verifications de l'exigence 7.1.b ; dans le cas d'une intervention realisee par une personne n'ayant pas satisfait aux verifications de l'exigence 7.1.b, mettre en oeuvre une passerelle securisee (poste de rebond) par laquelle la personne devra se connecter et permettant une supervision des actions (autorisation ou interdiction des actions, demande d'explications, etc.) en temps reel, par une personne ayant elle-meme satisfait aux verifications de l'exigence 7.1.b. La passerelle securisee devra repondre aux objectifs de securite specifies dans [G_EXT] ; considerer les actions menees, une fois l'acces autorise, comme des actions d'administration et les journaliser comme telles ; supprimer l'autorisation d'acces a l'issue de l'intervention." + } + ], + "Checks": [] + }, + { + "Id": "12.14", + "Description": "Les flux sortants de l'infrastructure du service cloud doivent etre surveilles afin de detecter et de prevenir les exfiltrations de donnees et les communications non autorisees.", + "Name": "Surveillance des flux sortants de l'infrastructure", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "vpc", + "Type": "Automated", + "Comment": "a) Le prestataire doit fournir une capacite d'inspection et de suppression des sortants de l'infrastructure technique relatifs au perimetre du service (informations de facturation, les eventuels journaux necessaires au traitement d'incidents, etc.) : les sortants doivent pouvoir etre expurges des donnees pouvant porter atteinte a la confidentialite des donnees des commanditaires ; cette capacite d'inspection et de suppression doit generer des journaux d'activite et doit pouvoir faire l'objet d'un audit de code ; les sortants sont traites sur des dispositifs specifiques operes et maintenus par le prestataire, et heberges dans une zone cloisonnee du reste de l'infrastructure (du type zone demilitarisee telle que definie dans [G_INT])." + } + ], + "Checks": [ + "vpc_flow_logs_enabled" + ] + }, + { + "Id": "13.1", + "Description": "Le prestataire doit etablir et maintenir une cartographie complete et a jour de son systeme d'information, incluant les reseaux, les flux et les composants.", + "Name": "Cartographie du systeme d'information", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "actiontrail", + "Type": "Automated", + "Comment": "a) Le prestataire doit etablir et tenir a jour une cartographie du systeme d'information du service, en lien avec l'inventaire des actifs (voir chapitre 8.1), comprenant au minimum les elements suivants : la liste des ressources materielles ou virtualisees ; les noms et fonctions des applications, supportant le service ; le schema d'architecture reseau au niveau 3 du modele OSI sur lequel les points nevralgiques sont identifies : les points d'interconnexions, notamment avec les reseaux tiers et publics ; les reseaux, sous-reseaux, notamment les reseaux d'administration ; les equipements assurant des fonctions de securite (filtrage, authentification, chiffrement, etc.) ; les serveurs hebergeant des donnees ou assurant des fonctions sensibles ; la matrice des flux reseau autorises en precisant : leur description technique (services, protocoles et ports) ; la justification metier ou d'infrastructure technique ; le cas echeant, lorsque des services, protocoles ou ports reputes non surs sont utilises, les mesures compensatoires mises en place, dans la logique de defense en profondeur. b) Le prestataire doit reviser au moins annuellement la cartographie." + } + ], + "Checks": [ + "actiontrail_multi_region_enabled", + "vpc_flow_logs_enabled", + "securitycenter_all_assets_agent_installed" + ] + }, + { + "Id": "13.2", + "Description": "Les reseaux doivent etre cloisonnes et les flux entre les segments doivent etre filtres selon le principe du moindre privilege. Les groupes de securite et les listes de controle d'acces reseau doivent etre configures de maniere restrictive.", + "Name": "Cloisonnement des reseaux", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "ecs", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre, pour le systeme d'information du service, les mesures de cloisonnement (logique, physique ou par chiffrement) pour separer les flux reseau selon : la sensibilite des informations transmises ; la nature des flux (production, administration, supervision, etc.) ; le domaine d'appartenance des flux (des commanditaires - avec distinction par commanditaire ou ensemble de commanditaires, du prestataire, des tiers, etc.) ; le domaine technique (traitement, stockage, etc.). b) Le prestataire doit cloisonner, physiquement ou par chiffrement, tous les flux de donnees internes au systeme d'information du service vis-a-vis de tout autre systeme d'information. Lorsque ce cloisonnement est realise par chiffrement, il est realise en accord avec les exigences du chapitre 10.2. c) Dans le cas ou le reseau d'administration de l'infrastructure technique ne fait pas l'objet d'un cloisonnement physique, les flux d'administration doivent transiter dans un tunnel chiffre, en accord avec les exigences du chapitre 10.2. d) Le prestataire doit mettre en place et configurer un pare-feu applicatif pour proteger les interfaces d'administration destinees a ses commanditaires et exposees sur un reseau public. e) Le prestataire doit mettre en oeuvre sur l'ensemble des interfaces d'administration et de supervision de l'infrastructure technique du service un mecanisme de filtrage n'autorisant que les connexions legitimes identifiees dans la matrice des flux autorises." + } + ], + "Checks": [ + "ecs_securitygroup_restrict_rdp_internet", + "ecs_securitygroup_restrict_ssh_internet", + "ecs_instance_no_legacy_network", + "cs_kubernetes_network_policy_enabled", + "cs_kubernetes_private_cluster_enabled", + "rds_instance_no_public_access_whitelist" + ] + }, + { + "Id": "13.3", + "Description": "Les reseaux doivent etre surveilles de maniere continue afin de detecter les activites anormales ou malveillantes.", + "Name": "Surveillance des reseaux", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "securitycenter", + "Type": "Automated", + "Comment": "a) Le prestataire doit disposer une ou plusieurs sondes de detection d'incidents de securite sur le systeme d'information du service. Ces sondes doivent notamment permettre la supervision de chacune des interconnexions du systeme d'information du service avec des systemes d'information tiers et des reseaux publics. Ces sondes doivent etre des sources de collecte pour l'infrastructure d'analyse et de correlation des evenements (voir chapitre 12.9)." + } + ], + "Checks": [ + "securitycenter_advanced_or_enterprise_edition", + "vpc_flow_logs_enabled" + ] + }, + { + "Id": "14.1", + "Description": "Des regles de developpement securise des logiciels et des systemes doivent etre etablies et appliquees au sein du prestataire.", + "Name": "Politique de developpement securise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des regles de developpement securise des logiciels et des systemes, et les appliquer aux developpements internes. b) Le prestataire doit documenter et mettre en oeuvre une formation adaptee en developpement securise aux employes concernes." + } + ], + "Checks": [] + }, + { + "Id": "14.2", + "Description": "Les changements apportes aux systemes dans le cycle de developpement doivent etre geres a l'aide de procedures formelles de controle des changements.", + "Name": "Procedures de controle des changements de systeme", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "actiontrail", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de controle des changements apportes au systeme d'information du service. b) Le prestataire doit documenter et mettre en oeuvre une procedure de validation des changements apportes au systeme d'information du service sur un environnement de pre-production avant leur mise en production. c) Le prestataire doit conserver un historique des versions des logiciels et des systemes (developpements internes ou externes, produits commerciaux) mis en oeuvre pour permettre de reconstituer, le cas echeant dans un environnement de test, un environnement complet tel qu'il etait mis en oeuvre a une date donnee. La duree de conservation de cet historique doit etre en accord avec celle des sauvegardes (voir chapitre 12.5)." + } + ], + "Checks": [ + "actiontrail_multi_region_enabled" + ] + }, + { + "Id": "14.3", + "Description": "Lorsque les plateformes d'exploitation sont modifiees, les applications critiques metier doivent etre revues et testees afin de verifier qu'il n'y a pas d'effet indesirable sur l'activite ou la securite du prestataire.", + "Name": "Revue technique des applications apres changement apporte a la plateforme d'exploitation", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester, prealablement a leur mise en production, l'ensemble des applications afin de verifier l'absence de tout effet indesirable sur l'activite ou sur la securite du service." + } + ], + "Checks": [] + }, + { + "Id": "14.4", + "Description": "Les environnements de developpement doivent etre securises et isoles des environnements de production.", + "Name": "Environnement de developpement securise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit mettre en oeuvre un environnement securise de developpement permettant de gerer l'integralite du cycle de developpement du systeme d'information du service. b) Le prestataire doit prendre en compte les environnements de developpement dans l'appreciation des risques et en assurer la protection conformement au present referentiel." + } + ], + "Checks": [] + }, + { + "Id": "14.5", + "Description": "Le prestataire doit superviser et surveiller l'activite de developpement externalise du systeme.", + "Name": "Developpement externalise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de superviser et de controler l'activite de developpement externalise des logiciels et des systemes. Cette procedure doit s'assurer que l'activite de developpement externalise soit conforme a la politique de developpement securise du prestataire et permette d'atteindre un niveau de securite du developpement externe equivalent a celui d'un developpement interne (voir exigence 14.1 a))." + } + ], + "Checks": [] + }, + { + "Id": "14.6", + "Description": "Des tests de securite et de conformite doivent etre effectues tout au long du cycle de developpement et apres chaque changement significatif.", + "Name": "Test de la securite et conformite du systeme", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "securitycenter", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit soumettre les systemes d'information, nouveaux ou mis a jour, a des tests de conformite et de fonctionnalite de securite pendant le developpement. Il doit documenter et mettre en oeuvre une procedure de test qui identifie : les taches a realiser ; les donnees d'entree ; les resultats attendus en sortie." + } + ], + "Checks": [ + "securitycenter_vulnerability_scan_enabled", + "cs_kubernetes_cluster_check_weekly" + ] + }, + { + "Id": "14.7", + "Description": "Les donnees de test doivent etre soigneusement selectionnees, protegees et controlees.", + "Name": "Protection des donnees de test", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'assurer l'integrite des donnees de tests utilises en pre-production. b) Si le prestataire souhaite utiliser des donnees du commanditaire issues de la production pour realiser des tests, le prestataire doit prealablement obtenir l'accord du commanditaire et les anonymiser. Le prestataire doit assurer la confidentialite des donnees lors de leur anonymisation." + } + ], + "Checks": [] + }, + { + "Id": "15.1", + "Description": "Le prestataire doit identifier les tiers ayant acces a l'information ou aux moyens de traitement de l'information et evaluer les risques associes.", + "Name": "Identification des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit tenir a jour une liste exhaustive des tiers participant a la mise en oeuvre du service (hebergeur, developpeur, integrateur, archiveur, sous-traitant operant sur site ou a distance, fournisseurs de climatisation, etc.). Cette liste doit preciser la contribution du tiers au service et au traitement des donnees a caractere personnel. Elle doit tenir compte des cas de sous-traitance a plusieurs niveaux. b) Le prestataire doit tenir a disposition du commanditaire la liste de l'ensemble des tiers qui peuvent acceder aux donnees et l'informer de tout changement de sous-traitants au sens de l'article 28 du [RGPD] afin que le commanditaire puisse emettre des objections a cet egard." + } + ], + "Checks": [] + }, + { + "Id": "15.2", + "Description": "Tous les aspects pertinents de la securite de l'information doivent etre traites dans les accords conclus avec les tiers.", + "Name": "La securite dans les accords conclus avec les tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit exiger des tiers participant a la mise en oeuvre du service, dans leur contribution au service, un niveau de securite au moins equivalent a celui qu'il s'engage a maintenir dans sa propre politique de securite. Il doit le faire au travers d'exigences, adaptees a chaque tiers et a sa contribution au service, dans les cahiers des charges ou dans les clauses de securite des accords de partenariat. Le prestataire doit inclure ces exigences dans les contrats conclus avec les tiers. b) Le prestataire doit contractualiser, avec chacun des tiers participant a la mise en oeuvre du service, des clauses d'audit permettant a un organisme de qualification de verifier que ces tiers respectent les exigences du present referentiel. c) Le prestataire doit definir et attribuer les roles et les responsabilites relatives a la modification ou a la fin du contrat le liant a un tiers participant a la mise en oeuvre du service." + } + ], + "Checks": [] + }, + { + "Id": "15.3", + "Description": "Le prestataire doit surveiller, revoir et auditer a intervalles reguliers la prestation des services des tiers.", + "Name": "Surveillance et revue des services des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de controler regulierement les mesures mises en place par les tiers participant a la mise en oeuvre du service pour respecter les exigences du present referentiel, conformement au chapitre 18.3." + } + ], + "Checks": [] + }, + { + "Id": "15.4", + "Description": "Les changements dans les services des tiers, incluant le maintien et l'amelioration des politiques, procedures et mesures existantes de securite de l'information, doivent etre geres.", + "Name": "Gestion des changements apportes dans les services des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de suivi des changements apportes par les tiers participant a la mise en oeuvre du service susceptibles d'affecter le niveau de securite du systeme d'information du service. b) Dans la mesure ou un changement de tiers participant a la mise en oeuvre du service affecte le niveau de securite du service, le prestataire doit en informer l'ensemble des commanditaires sans delais conformement au chapitre 12.2 et mettre en oeuvre les mesures permettant de retablir le niveau de securite precedent." + } + ], + "Checks": [] + }, + { + "Id": "15.5", + "Description": "Les personnes intervenant dans le cadre du service cloud doivent etre soumises a des engagements de confidentialite.", + "Name": "Engagements de confidentialite", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de reviser au moins annuellement les exigences en matiere d'engagements de confidentialite ou de non-divulgation vis-a-vis des tiers participant a la mise en oeuvre du service." + } + ], + "Checks": [] + }, + { + "Id": "16.1", + "Description": "Des responsabilites et des procedures de gestion doivent etre etablies pour garantir une reponse rapide, efficace et ordonnee aux incidents lies a la securite de l'information.", + "Name": "Responsabilites et procedures", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'apporter des reponses rapides et efficaces aux incidents de securite. Ces procedures doivent definir les moyens et delais de communication des incidents de securite a l'ensemble des commanditaires concernes ainsi que le niveau de confidentialite exige pour cette communication. b) Le prestataire doit informer ses employes et l'ensemble des tiers participant a la mise en oeuvre du service de cette procedure. c) Le prestataire doit documenter toute violation de donnees a caractere personnel et en informer son commanditaire. La violation doit etre notifiee a la CNIL si elle presente un risque pour les droits et libertes des personnes concernees. Elle doit faire l'objet d'une information aupres des personnes concernees lorsque le risque pour leur vie privee est eleve." + } + ], + "Checks": [] + }, + { + "Id": "16.2", + "Description": "Les evenements lies a la securite de l'information doivent etre signales dans les meilleurs delais par les voies hierarchiques appropriees. Des mecanismes de detection et de notification automatises doivent etre mis en oeuvre.", + "Name": "Signalements lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "securitycenter", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure exigeant de ses employes et des tiers participant a la mise en oeuvre du service qu'ils lui rendent compte de tout incident de securite, avere ou suspecte ainsi que de toute faille de securite. b) Le prestataire doit documenter et mettre en oeuvre une procedure permettant a l'ensemble des commanditaires de signaler tout incident de securite, avere ou suspecte et toute faille de securite. c) Le prestataire doit communiquer sans delai aux commanditaires les incidents de securite et les preconisations associees pour en limiter les impacts. Il doit permettre au commanditaire de choisir les niveaux de gravite des incidents pour lesquels il souhaite etre informe. d) Le prestataire doit communiquer les incidents de securite aux autorites competentes conformement aux exigences legales et reglementaires en vigueur." + } + ], + "Checks": [ + "securitycenter_notification_enabled_high_risk", + "securitycenter_advanced_or_enterprise_edition", + "sls_unauthorized_api_calls_alert_enabled" + ] + }, + { + "Id": "16.3", + "Description": "Les evenements lies a la securite de l'information doivent etre apprecies et il doit etre decide s'il est necessaire de les classer comme incidents lies a la securite de l'information.", + "Name": "Appreciation des evenements et prise de decision", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "securitycenter", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit apprecier les evenements lies a la securite de l'information et decider s'il faut les qualifier en incidents de securite. Pour l'appreciation, il doit s'appuyer sur une ou plusieurs echelles (estimation, evaluation, etc.) partagees avec le commanditaire. Note : Les incidents de securite incluent les violations de donnees a caractere personnel. b) Le prestataire doit utiliser une classification permettant d'identifier clairement les incidents de securite touchant des donnees relatives aux commanditaires, conformement aux resultats de l'appreciation des risques. Cette classification doit inclure les violations de donnees a caractere personnel." + } + ], + "Checks": [ + "securitycenter_notification_enabled_high_risk" + ] + }, + { + "Id": "16.4", + "Description": "Les incidents lies a la securite de l'information doivent etre traites conformement aux procedures documentees.", + "Name": "Reponse aux incidents lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit traiter les incidents de securite jusqu'a leur resolution et doit informer les commanditaires conformement aux procedures." + } + ], + "Checks": [] + }, + { + "Id": "16.5", + "Description": "Les connaissances acquises lors de l'analyse et du traitement des incidents lies a la securite de l'information doivent etre exploitees pour reduire la probabilite ou l'impact d'incidents futurs.", + "Name": "Tirer des enseignements des incidents lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus d'amelioration continue afin de diminuer l'occurrence et l'impact de types d'incidents de securite deja traites." + } + ], + "Checks": [] + }, + { + "Id": "16.6", + "Description": "Le prestataire doit definir et appliquer des procedures pour l'identification, le recueil, l'acquisition et la preservation de preuves. Les journaux d'audit doivent etre proteges et valides.", + "Name": "Recueil de preuves", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "actiontrail", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'enregistrer les informations relatives aux incidents de securite et pouvant servir d'elements de preuve." + } + ], + "Checks": [ + "actiontrail_multi_region_enabled", + "actiontrail_oss_bucket_not_publicly_accessible" + ] + }, + { + "Id": "17.1", + "Description": "Le prestataire doit determiner ses exigences en matiere de securite de l'information et de continuite du management de la securite de l'information dans des situations defavorables, par exemple lors d'une crise ou d'un sinistre.", + "Name": "Organisation de la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre oeuvre un plan de continuite d'activite prenant en compte la securite de l'information. b) Le prestataire doit reviser annuellement le plan de continuite d'activite du service et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "17.2", + "Description": "Le prestataire doit etablir, documenter, mettre en oeuvre et maintenir des processus, des procedures et des mesures de controle pour assurer le niveau requis de continuite de la securite de l'information au cours d'une situation defavorable. Les services doivent etre deployes en multi-AZ.", + "Name": "Mise en oeuvre de la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des procedures permettant de maintenir ou de restaurer l'exploitation du service et d'assurer la disponibilite des informations au niveau et dans les delais pour lesquels le prestataire s'est engage vis-a-vis du commanditaire dans la convention de service." + } + ], + "Checks": [] + }, + { + "Id": "17.3", + "Description": "Le prestataire doit verifier a intervalles reguliers les mesures de continuite de la securite de l'information mises en oeuvre afin de s'assurer qu'elles sont valables et efficaces dans des situations defavorables.", + "Name": "Verifier, revoir et evaluer la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester le plan de continuite d'activites afin de s'assurer qu'il est pertinent et efficace en situation de crise." + } + ], + "Checks": [] + }, + { + "Id": "17.4", + "Description": "Les moyens de traitement de l'information doivent etre mis en oeuvre avec suffisamment de redondance pour repondre aux exigences de disponibilite. Les mecanismes de protection contre la suppression accidentelle doivent etre actives.", + "Name": "Disponibilite des moyens de traitement de l'information", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures qui lui permettent de repondre au besoin de disponibilite du service defini dans la convention de service (voir chapitre 19.1)." + } + ], + "Checks": [] + }, + { + "Id": "17.5", + "Description": "La configuration de l'infrastructure technique du service cloud doit etre sauvegardee regulierement afin de permettre sa restauration en cas de sinistre.", + "Name": "Sauvegarde de la configuration de l'infrastructure technique", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "actiontrail", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de sauvegarde hors-ligne de la configuration de l'infrastructure technique." + } + ], + "Checks": [ + "actiontrail_multi_region_enabled", + "securitycenter_all_assets_agent_installed" + ] + }, + { + "Id": "17.6", + "Description": "Le prestataire doit mettre a disposition du commanditaire un dispositif de sauvegarde de ses donnees, permettant la restauration en cas de sinistre.", + "Name": "Mise a disposition d'un dispositif de sauvegarde des donnees du commanditaire", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre a disposition du commanditaire un service de sauvegarde de ses donnees." + } + ], + "Checks": [] + }, + { + "Id": "18.1", + "Description": "Toutes les exigences legales, reglementaires et contractuelles en vigueur, ainsi que l'approche du prestataire pour satisfaire ces exigences, doivent etre explicitement definies, documentees et tenues a jour pour chaque systeme d'information et pour le prestataire.", + "Name": "Identification de la legislation et des exigences contractuelles applicables", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les exigences legales, reglementaires et contractuelles en vigueur applicables au service. En France, le prestataire doit considerer au minimum les textes suivants : les donnees a caractere personnel [LOI_IL], [RGPD] ; le secret professionnel [CP_ART_226_13], le cas echeant sans prejudice de l'application de l'article 40 alinea 2 du Code de procedure penale relatif au signalement a une autorite judiciaire ; l'abus de confiance [CP_ART_314-1] ; le secret des correspondances privees [CP_ART_226-15] ; l'atteinte a la vie privee [CP_ART_226-1] ; l'acces ou le maintien frauduleux a un systeme d'information [CP_ART_323-1]. b) Le prestataire doit, selon son role dans les traitements de donnees a caractere personnel (responsable de traitement, sous-traitant ou co-responsable) justifier et documenter les choix de mesures techniques et organisationnelles realises en vue de repondre aux exigences de protection des donnees a caractere personnel du present referentiel (voir partie 19.5). c) Le prestataire doit documenter et mettre en oeuvre les procedures permettant de respecter les exigences legales, reglementaires et contractuelles en vigueur applicables au service, ainsi que les besoins de securite specifiques (voir exigence 8.3b)). d) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible l'ensemble de ces procedures. e) Le prestataire doit documenter et mettre en oeuvre un processus de veille actif des exigences legales, reglementaires et contractuelles en vigueur applicables au service." + } + ], + "Checks": [] + }, + { + "Id": "18.2", + "Description": "L'approche du prestataire vis-a-vis de la gestion de la securite de l'information et sa mise en oeuvre (c'est-a-dire les objectifs de controle, les mesures, les politiques, les procedures et les processus relatifs a la securite de l'information) doivent etre revues de maniere independante a intervalles definis ou en cas de changement significatif.", + "Name": "Revue independante de la securite de l'information", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un programme d'audit sur trois ans definissant le perimetre et la frequence des audits en accord avec la gestion du changement, les politiques, et les resultats de l'appreciation des risques. Le prestataire doit inclure dans le programme d'audit un audit qualifie par an realise par un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie. L'ensemble du programme d'audit doit notamment couvrir : l'audit de la configuration de l'infrastructure technique du service (par echantillonnage et doit inclure tous types d'equipements et de serveurs presents dans le systeme d'information du service) ; le test d'intrusion des interfaces d'administration exposees sur un reseau public ; le test d'intrusion de l'interface utilisateur pour les services SaaS ; si le service beneficie de developpements internes, l'audit de code source portant sur les fonctionnalites de securite implementees (l'approche en continue doit etre privilegiee). b) Il est recommande que le prestataire mette en oeuvre des mecanismes automatises d'audit de la configuration adaptes a l'infrastructure technique du service." + } + ], + "Checks": [] + }, + { + "Id": "18.3", + "Description": "Les responsables doivent regulierement s'assurer de la conformite du traitement de l'information et des procedures au sein de leur domaine de responsabilite, au regard des politiques et des normes de securite.", + "Name": "Conformite avec les politiques et les normes de securite", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "securitycenter", + "Type": "Partially Automated", + "Comment": "a) Le prestataire via le responsable de la securite de l'information doit s'assurer regulierement de l'execution correcte de l'ensemble des procedures de securite placees sous sa responsabilite en vue de garantir leur conformite avec les politiques et normes de securite." + } + ], + "Checks": [ + "securitycenter_advanced_or_enterprise_edition", + "actiontrail_multi_region_enabled" + ] + }, + { + "Id": "18.4", + "Description": "Les systemes d'information doivent etre examines regulierement quant a leur conformite avec les politiques et les normes de securite de l'information du prestataire.", + "Name": "Examen de la conformite technique", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "securitycenter", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique permettant de verifier la conformite technique du service aux exigences du present referentiel. Cette politique doit definir les objectifs, methodes, frequences, resultats attendus et mesures correctrices." + } + ], + "Checks": [ + "securitycenter_vulnerability_scan_enabled", + "securitycenter_advanced_or_enterprise_edition" + ] + }, + { + "Id": "19.1", + "Description": "Le prestataire doit etablir une convention de service avec le commanditaire definissant les engagements de niveau de service, les responsabilites et les conditions d'utilisation du service cloud.", + "Name": "Convention de service", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit etablir une convention de service avec chacun des commanditaires du service. Toute modification de la convention de service doit etre soumise a acceptation du commanditaire. b) Le prestataire doit identifier dans la convention de service : les obligations, droits et responsabilites de chacune des parties : prestataire et tiers impliques dans la fourniture du service, commanditaires, etc. ; les elements explicitement exclus des responsabilites du prestataire dans la limite de ce que prevoient les exigences legales et reglementaires en vigueur, notamment l'article 28 du [RGPD] ; la localisation du service. La localisation du support doit etre precisee lorsqu'il est realise depuis un Etat hors l'Union Europeenne, comme le permet l'exigence 19.2.e. c) Le prestataire doit proposer une convention de service appliquant le droit d'un Etat membre de l'Union Europeenne. Le droit applicable doit etre identifie dans la convention de service. d) La convention de service doit indiquer que la collecte, la manipulation, le stockage, et plus generalement le traitement des donnees faits dans le cadre de l'avant-vente, de la mise en oeuvre, de la maintenance et l'arret du service sont realises conformement aux exigences edictees par la legislation en vigueur. e) La convention de service doit indiquer que le prestataire doit mettre a la disposition du commanditaire, sur demande de celui-ci, les elements d'appreciation des risques lies a la soumission des donnees du commanditaire au droit d'un etat non-membre de l'Union Europeenne (voir 5.3.e). f) Le prestataire doit decrire dans la convention de service les moyens techniques et organisationnels qu'il met en oeuvre pour assurer le respect du droit applicable. g) Le prestataire doit inclure dans la convention de service une clause de revision de la convention prevoyant notamment une resiliation sans penalite pour le commanditaire en cas de perte de la qualification octroyee au service. h) Le prestataire doit inclure dans la convention de service une clause de reversibilite permettant au commanditaire de recuperer l'ensemble de ses donnees (fournies directement par le commanditaire ou produites dans le cadre du service a partir des donnees ou des actions du commanditaire). i) Le prestataire doit assurer cette reversibilite via l'une des modalites techniques suivantes : la mise a disposition de fichiers suivant un ou plusieurs formats documentes et exploitables en dehors du service fourni par le prestataire ; la mise en place d'interfaces techniques permettant l'acces aux donnees suivant un schema documente et exploitable (API, format pivot, etc.). Les modalites techniques de la reversibilite figurent dans la convention de service. j) Le prestataire doit indiquer dans la convention de service le niveau de disponibilite du service. k) Le prestataire doit indiquer dans la convention de service qu'il ne peut disposer des donnees transmises et generees par le commanditaire, leur disposition etant reservee au commanditaire. l) Le prestataire doit indiquer dans la convention de service qu'il ne divulgue aucune information relative a la prestation a des tiers, sauf autorisation formelle et ecrite du commanditaire. m) Le prestataire doit indiquer dans la convention de service si les donnees du commanditaire sont automatiquement sauvegardees ou non. Dans la negative, le prestataire doit sensibiliser le commanditaire aux risques encourus et clairement indiquer les operations a mener par le commanditaire pour que ses donnees soient sauvegardees. n) Le prestataire doit indiquer dans la convention de service s'il autorise l'acces distant pour des actions d'administration ou de support au systeme d'information du service. o) Le prestataire doit preciser dans la convention de service que : le service est qualifie et inclure l'attestation de qualification ; le commanditaire peut deposer une reclamation relative au service qualifie aupres de l'ANSSI ; le commanditaire autorise l'ANSSI et l'organisme de qualification a auditer le service et son systeme d'information du service afin de verifier qu'ils respectent les exigences du present referentiel. p) Le prestataire doit preciser dans la convention de service que le commanditaire autorise, conformement au present referentiel (voir chapitre 18.2, un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie mandate par le prestataire a auditer le service et son systeme d'information dans le cadre du plan de controle. q) Le prestataire doit preciser dans la convention de service qu'il s'engage a mettre a disposition toutes les informations necessaires a la realisation d'audits de conformite aux dispositions de l'article 28 du [RGPD], menes par le commanditaire ou un tiers mandate. r) Il est recommande que le tiers mandate pour les audits soit un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie." + } + ], + "Checks": [] + }, + { + "Id": "19.2", + "Description": "Les donnees du commanditaire doivent etre stockees et traitees dans des centres de donnees situes sur le territoire de l'Union europeenne. Les politiques de restriction de region doivent etre appliquees.", + "Name": "Localisation des donnees", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et communiquer au commanditaire la localisation du stockage et du traitement des donnees de ce dernier. b) Le prestataire doit stocker et traiter les donnees du commanditaire au sein de l'Union Europeenne. c) Les operations d'administration et de supervision du service doivent etre realisees depuis le territoire de l'Union Europeenne. d) Le prestataire doit stocker et traiter les donnees techniques (identites des beneficiaires et des administrateurs de l'infrastructure technique, donnees manipulees par le Software Defined Network, journaux de l'infrastructure technique, annuaire, certificats, configuration des acces, etc.) au sein de l'Union Europeenne. e) Le prestataire peut realiser des operations de support aux commanditaires depuis un Etat hors de l'Union Europeenne. Il doit documenter la liste des operations qui peuvent etre effectuees par le support au commanditaire depuis un Etat hors de l'Union Europeenne, et les mecanismes permettant d'en assurer le controle d'acces et la supervision depuis l'Union Europeenne." + } + ], + "Checks": [] + }, + { + "Id": "19.3", + "Description": "Les services cloud qualifies SecNumCloud doivent etre operes depuis le territoire de l'Union europeenne.", + "Name": "Regionalisation", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit s'assurer que les interfaces du service accessibles au commanditaire soient au moins disponibles en langue francaise. b) Le prestataire doit fournir un support de premier niveau en langue francaise." + } + ], + "Checks": [] + }, + { + "Id": "19.4", + "Description": "Le prestataire doit definir les conditions de fin de contrat, incluant les modalites de restitution et de suppression des donnees du commanditaire.", + "Name": "Fin de contrat", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) A la fin du contrat liant le prestataire et le commanditaire, que le contrat soit arrive a son terme ou pour toute autre cause, le prestataire doit assurer un effacement securise de l'integralite des donnees du commanditaire. Cet effacement doit faire l'objet d'un preavis formel au commanditaire de la part du prestataire respectant un delai de vingt et un jours calendaires. L'effacement peut etre realise suivant l'une des methodes suivantes, et ce dans un delai precise dans la convention de service : effacement par reecriture complete de tout support ayant heberge ces donnees ; effacement des cles utilisees pour le chiffrement des espaces de stockage du commanditaire decrit au chapitre 10.1 ; recyclage securise, dans les conditions enoncees au chapitre 11.9. b) A la fin du contrat, le prestataire doit supprimer les donnees techniques relatives au commanditaire (annuaire, certificats, configuration des acces, etc.)." + } + ], + "Checks": [] + }, + { + "Id": "19.5", + "Description": "Le prestataire doit mettre en oeuvre des mesures techniques et organisationnelles appropriees pour garantir la protection des donnees a caractere personnel conformement a la reglementation en vigueur.", + "Name": "Protection des donnees a caractere personnel", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit justifier du respect des principes de protection des donnees pour les traitements de donnees a caractere personnel mis en oeuvre pour son propre compte. Il doit justifier au minimum les points suivants : les finalites des traitements determinees, explicites et legitimes ; la tracabilite des activites de traitement pour son compte et celui de son commanditaire ; le fondement licite des traitements ; l'interdiction du detournement de finalite des traitements ; les donnees utilisees respectent le principe du minimum necessaire et suffisant pour les traitements ; ainsi sont adequates, pertinentes et limitees ; la qualite des donnees utilisees pour les traitements maintenue : donnees exactes et tenues a jour ; les durees de conservation definies et limitees. b) Le prestataire doit justifier, pour les traitements de donnees a caractere personnel mis en oeuvre pour son propre compte, du respect des droits des personnes concernees. Il doit justifier au minimum les points suivants : l'information des usagers via un traitement loyal et transparent ; le recueil du consentement des usagers : expres, demontrable et retirable ; la possibilite pour les usagers d'exercer les droits d'acces, de rectification et d'effacement ; la possibilite pour les usagers d'exercer les droits de limitation du traitement, de portabilite et d'opposition. c) Lorsqu'il agit en qualite de sous-traitant au sens de l'article 28 de [RGPD], le prestataire doit apporter assistance et conseil au commanditaire en l'informant si une instruction de ce dernier constitue une violation des regles de protection des donnees." + } + ], + "Checks": [] + }, + { + "Id": "19.6", + "Description": "Le prestataire doit mettre en oeuvre des mesures de protection vis-a-vis du droit extra-europeen, afin de garantir que les donnees du commanditaire ne puissent etre soumises a des legislations extra-europeennes.", + "Name": "Protection vis-a-vis du droit extra-europeen", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le siege statutaire, administration centrale et principal etablissement du prestataire doivent etre etablis au sein d'un Etat membre de l'Union Europeenne. b) Le capital social et les droits de vote dans la societe du prestataire ne doivent pas etre, directement ou indirectement : individuellement detenus a plus de 24% ; et collectivement detenus a plus de 39% ; par des entites tierces possedant leur siege statutaire, administration centrale ou principal etablissement au sein d'un Etat non membre de l'Union europeenne. Ces entites tierces susmentionnees ne peuvent pas individuellement ou collectivement : en vertu d'un contrat ou de clauses statutaires, disposer d'un droit de veto ; en vertu d'un contrat ou de clauses statutaires, designer la majorite des membres des organes d'administration, de direction ou de surveillance du prestataire. c) En cas de recours par le prestataire, dans le cadre des services fournis au commanditaire, aux services d'une societe tierce - y compris un sous-traitant - possedant son siege statutaire, administration centrale ou principal etablissement au sein d'un Etat non membre de l'Union Europeenne ou appartenant ou etant controlee par une societe tierce domiciliee en dehors l'Union Europeenne, cette susdite societe tierce ne doit pas avoir la possibilite technique d'obtenir les donnees operees au travers du service. d) Dans le cadre de l'exigence 19.6.c, toute societe tierce a laquelle le prestataire recourt pour fournir tout ou partie du service rendu au commanditaire, doit garantir au prestataire une autonomie d'exploitation continue dans la fourniture des services d'informatique en nuage qu'il opere ou doit etre qualifie SecNumCloud. e) Le service fourni par le prestataire doit respecter la legislation en vigueur en matiere de droits fondamentaux et les valeurs de l'Union relatives au respect de la dignite humaine, a la liberte, a l'egalite, a la democratie et a l'Etat de droit. f) Le prestataire doit informer formellement le commanditaire, et dans un delai d'un mois, de tout changement juridique, organisationnel ou technique pouvant avoir un impact sur la conformite de la prestation aux exigences du chapitre 19.6." + } + ], + "Checks": [] + } + ] +} From 5a3475bed3628fdd9b99327de420e1b6b5fa9a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Wed, 18 Mar 2026 12:28:38 +0100 Subject: [PATCH 032/366] feat(compliance): add SecNumCloud 3.2 for Oracle Cloud (#10371) --- .../compliance/secnumcloud_3_2_oraclecloud.py | 24 + dashboard/pages/compliance.py | 2 + prowler/CHANGELOG.md | 3 +- .../secnumcloud_3.2_oraclecloud.json | 1433 +++++++++++++++++ 4 files changed, 1461 insertions(+), 1 deletion(-) create mode 100644 dashboard/compliance/secnumcloud_3_2_oraclecloud.py create mode 100644 prowler/compliance/oraclecloud/secnumcloud_3.2_oraclecloud.json diff --git a/dashboard/compliance/secnumcloud_3_2_oraclecloud.py b/dashboard/compliance/secnumcloud_3_2_oraclecloud.py new file mode 100644 index 0000000000..2d5517aed6 --- /dev/null +++ b/dashboard/compliance/secnumcloud_3_2_oraclecloud.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_format3 + +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_format3( + aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID" + ) diff --git a/dashboard/pages/compliance.py b/dashboard/pages/compliance.py index 20395539e5..c1da9f611e 100644 --- a/dashboard/pages/compliance.py +++ b/dashboard/pages/compliance.py @@ -80,6 +80,8 @@ def load_csv_files(csv_files): result = result.replace("_M65", " - M65") if "ALIBABACLOUD" in result: result = result.replace("_ALIBABACLOUD", " - ALIBABACLOUD") + if "ORACLECLOUD" in result: + result = result.replace("_ORACLECLOUD", " - ORACLECLOUD") results.append(result) unique_results = set(results) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 387f1fae88..9f0b773bd5 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -12,9 +12,10 @@ All notable changes to the **Prowler SDK** are documented in this file. -`entra_conditional_access_policy_require_mfa_for_admin_portals` check for Azure provider and update CIS compliance [(#10330)](https://github.com/prowler-cloud/prowler/pull/10330) - CheckMetadata Pydantic validators [(#8584)](https://github.com/prowler-cloud/prowler/pull/8583) - `organization_repository_deletion_limited` check for GitHub provider [(#10185)](https://github.com/prowler-cloud/prowler/pull/10185) -- SecNumCloud 3.2 compliance framework for the GCP provider [(#10364)](https://github.com/prowler-cloud/prowler/pull/10364) +- SecNumCloud 3.2 for the GCP provider [(#10364)](https://github.com/prowler-cloud/prowler/pull/10364) - SecNumCloud 3.2 for the Azure provider [(#10358)](https://github.com/prowler-cloud/prowler/pull/10358) - SecNumCloud 3.2 for the Alibaba Cloud provider [(#10370)](https://github.com/prowler-cloud/prowler/pull/10370) +- SecNumCloud 3.2 for the Oracle Cloud provider [(#10371)](https://github.com/prowler-cloud/prowler/pull/10371) ### 🔄 Changed diff --git a/prowler/compliance/oraclecloud/secnumcloud_3.2_oraclecloud.json b/prowler/compliance/oraclecloud/secnumcloud_3.2_oraclecloud.json new file mode 100644 index 0000000000..d3c52f21ba --- /dev/null +++ b/prowler/compliance/oraclecloud/secnumcloud_3.2_oraclecloud.json @@ -0,0 +1,1433 @@ +{ + "Framework": "SecNumCloud", + "Name": "SecNumCloud Referentiel d'Exigences v3.2", + "Version": "3.2", + "Provider": "OracleCloud", + "Description": "The SecNumCloud framework is published by ANSSI (Agence Nationale de la Securite des Systemes d'Information) to qualify cloud service providers operating in France. Version 3.2, dated March 8, 2022, covers IaaS, CaaS, PaaS, and SaaS services with requirements spanning information security policies, access control, cryptography, physical security, operational security, communications security, and data sovereignty protections against extra-European law.", + "Requirements": [ + { + "Id": "5.1", + "Description": "Le prestataire doit definir et appliquer des principes de securite de l'information adaptes a ses activites de fourniture de services cloud.", + "Name": "Principes", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit operer la prestation a l'etat de l'art pour le type d'activite retenu : utiliser des logiciels stables beneficiant d'un suivi des correctifs de securite et parametres de facon a obtenir un niveau de securite optimal. b) Le prestataire doit appliquer le guide d'hygiene informatique de l'ANSSI [HYGIENE], niveau renforce, au systeme d'information du service." + } + ], + "Checks": [] + }, + { + "Id": "5.2", + "Description": "Le prestataire doit definir, faire approuver par la direction, publier et communiquer aux salaries et aux tiers concernes un ensemble de politiques de securite de l'information.", + "Name": "Politique de securite de l'information", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de securite de l'information relative au service. b) La politique de securite de l'information doit identifier les engagements du prestataire quant au respect de la legislation et reglementation nationale en vigueur selon la nature des informations qui pourraient etre confiees par le commanditaire au prestataire ; il revient en revanche in fine au commanditaire de s'assurer du respect des contraintes legales et reglementaires applicables aux donnees qu'il confie effectivement au prestataire. c) La politique de securite de l'information doit notamment couvrir les themes abordes aux chapitres 6 a 19 du present referentiel. d) La direction du prestataire doit approuver formellement la politique de securite de l'information. e) Le prestataire doit reviser annuellement la politique de securite de l'information et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "5.3", + "Description": "Le prestataire doit definir et appliquer un processus d'appreciation des risques de securite de l'information.", + "Name": "Appreciation des risques", + "Attributes": [ + { + "Section": "5. Politiques de securite de l'information et gestion du risque", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter une appreciation des risques couvrant l'ensemble du perimetre du service. b) Le prestataire doit realiser son appreciation de risques en utilisant une methode documentee garantissant la reproductibilite et comparabilite de la demarche. c) Le prestataire doit prendre en compte dans l'appreciation des risques : la gestion d'informations du commanditaire ayant des besoins de securite differents ; les risques ayant des impacts sur les droits et libertes des personnes concernees en cas d'acces non autorise, de modification non desiree et de disparition de donnees a caractere personnel ; les risques de defaillance des mecanismes de cloisonnement des ressources de l'infrastructure technique (memoire, calcul, stockage, reseau) partagees entre les commanditaires ; les risques lies a l'effacement incomplet ou non securise des donnees stockees sur les espaces de memoire ou de stockage partages entre commanditaires, en particulier lors des reallocations des espaces de memoire et de stockage ; les risques lies a l'exposition des interfaces d'administration sur un reseau public ; les risques d'atteinte a la confidentialite des donnees des commanditaires par des tiers impliques dans la fourniture du service (fournisseurs, sous-traitants, etc.) ; les risques lies aux evenements naturels et sinistres physiques ; les risques lies a la separation des taches (voir 6.2.a) ; les risques lies aux environnements de developpement (voir 14.4.b). d) Le prestataire doit lister, dans un document specifique, les risques residuels lies a l'existence de lois extra-europeennes ayant pour objectif la collecte de donnees ou metadonnees des commanditaires sans leur consentement prealable. e) Le prestataire doit mettre a la disposition du commanditaire, sur demande de celui-ci, les elements d'appreciation des risques lies a la soumission des donnees du commanditaire au droit d'un etat non-membre de l'Union Europeenne. f) Lorsqu'il existe des exigences legales, reglementaires ou sectorielles specifiques liees aux types d'informations confiees par le commanditaire au prestataire, ce dernier doit les prendre en compte dans son appreciation des risques en s'assurant de respecter l'ensemble des exigences du present referentiel d'une part et de ne pas abaisser le niveau de securite etabli par le respect des exigences du present referentiel d'autre part. g) La direction du prestataire doit accepter formellement les risques residuels identifies dans l'appreciation des risques. h) Le prestataire doit reviser annuellement l'appreciation des risques et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "6.1", + "Description": "Le prestataire doit definir et attribuer toutes les responsabilites en matiere de securite de l'information.", + "Name": "Fonctions et responsabilites liees a la securite de l'information", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une organisation interne de la securite pour assurer la definition, la mise en place et le suivi du fonctionnement operationnel de la securite de l'information au sein de son organisation. b) Le prestataire doit designer un responsable de la securite des systemes d'information et un responsable de la securite physique. c) Le prestataire doit definir et attribuer les responsabilites en matiere de securite de l'information pour le personnel implique dans la fourniture du service. d) Le prestataire doit s'assurer apres tout changement majeur pouvant avoir un impact sur le service que l'attribution des responsabilites en matiere de securite de l'information est toujours pertinente. e) Le prestataire doit definir et attribuer les responsabilites en matiere de protection de donnees a caractere personnel, en coherence avec son role dans les traitements de donnees a caractere personnel (responsable de traitement, sous-traitant ou co-responsable). f) Le prestataire doit, lorsqu'il traite un grand nombre de donnees parmi lesquelles figurent des categories particulieres de donnees a caractere personnel telles que definies dans [RGPD], designer un delegue a la protection des donnees. g) Il est recommande que le prestataire, quel que soit le volume de donnees a caractere personnel qu'il traite, designe un delegue a la protection des donnees. h) Le prestataire doit realiser ou contribuer a la realisation d'une analyse d'impact relative a la protection des donnees a caractere personnel lorsque le traitement est susceptible d'engendrer un risque eleve pour les droits et libertes des personnes concernees (traitement de categories particulieres de donnees a caractere personnel telles que definies dans [RGPD], traitement de donnees a grande echelle, etc.). Cette analyse doit comporter une evaluation juridique du respect des principes et droits fondamentaux, ainsi qu'une etude plus technique des mesures techniques mises en oeuvre pour proteger les personnes des risques pour leur vie privee." + } + ], + "Checks": [] + }, + { + "Id": "6.2", + "Description": "Le prestataire doit separer les taches et les domaines de responsabilite incompatibles afin de reduire les possibilites de modification non autorisee ou de mauvais usage des actifs.", + "Name": "Separation des taches", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les risques associes a des cumuls de responsabilites ou de taches, les prendre en compte dans l'appreciation des risques et mettre en oeuvre des mesures de reduction de ces risques." + } + ], + "Checks": [] + }, + { + "Id": "6.3", + "Description": "Le prestataire doit etablir et maintenir des relations appropriees avec les autorites competentes.", + "Name": "Relations avec les autorites", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire mette en place des relations appropriees avec les autorites competentes en matiere de securite de l'information et de donnees a caractere personnel et, le cas echeant, avec les autorites sectorielles selon la nature des informations confiees par le commanditaire au prestataire." + } + ], + "Checks": [] + }, + { + "Id": "6.4", + "Description": "Le prestataire doit etablir et maintenir des relations appropriees avec des groupes de travail specialises, des associations professionnelles ou des forums traitant de la securite.", + "Name": "Relations avec les groupes de travail specialises", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire entretienne des contacts appropries avec des groupes de specialistes ou des sources reconnues, notamment pour prendre en compte de nouvelles menaces et les mesures de securite appropriees pour les contrer." + } + ], + "Checks": [] + }, + { + "Id": "6.5", + "Description": "Le prestataire doit integrer la securite de l'information dans la gestion de projet, quel que soit le type de projet.", + "Name": "La securite de l'information dans la gestion de projet", + "Attributes": [ + { + "Section": "6. Organisation de la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter une estimation des risques prealablement a tout projet pouvant avoir un impact sur le service, et ce quelle que soit la nature du projet. b) Dans la mesure ou un projet affecte ou est susceptible d'affecter le niveau de securite du service, le prestataire doit avertir le commanditaire et l'informer par ecrit des impacts potentiels, des mesures mises en place pour reduire ces impacts ainsi que des risques residuels le concernant." + } + ], + "Checks": [] + }, + { + "Id": "7.1", + "Description": "Le prestataire doit s'assurer que les candidats a l'embauche font l'objet de verifications proportionnees aux exigences metier, a la classification des informations accessibles et aux risques identifies.", + "Name": "Selection des candidats", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de verification des informations concernant son personnel conforme aux lois et reglements en vigueur. Ces verifications s'appliquent a toute personne impliquee dans la fourniture du service et doivent etre proportionnelles a la sensibilite ou a la specificite des informations du commanditaire confiees au prestataire ainsi qu'aux risques identifies. b) Pour les personnels disposant de privileges d'administration eleves sur les composants logiciels et materiels de l'infrastructure, le prestataire doit renforcer les verifications destinees a verifier que les antecedents de ceux-ci ne sont pas incompatibles avec l'exercice de leurs fonctions. Il est entendu par des privileges d'administration eleves, des actions permettant l'elevation de privileges ou la possibilite de realiser des actions sans traces techniques ou de desactiver, alterer les traces techniques." + } + ], + "Checks": [] + }, + { + "Id": "7.2", + "Description": "Les accords contractuels avec les salaries et les sous-traitants doivent preciser leurs responsabilites et celles du prestataire en matiere de securite de l'information.", + "Name": "Conditions d'embauche", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit disposer d'une charte d'ethique integree au reglement interieur, prevoyant notamment que : les prestations sont realisees avec loyaute, discretion et impartialite et dans des conditions de confidentialite des informations traitees ; les personnels ne recourent qu'aux methodes, outils et techniques valides par le prestataire ; les personnels s'engagent a ne pas divulguer d'informations a un tiers, meme anonymisees et decontextualisees, obtenues ou generees dans le cadre de la prestation sauf autorisation formelle et ecrite du commanditaire ; les personnels s'engagent a signaler au prestataire tout contenu manifestement illicite decouvert pendant la prestation ; les personnels s'engagent a respecter la legislation et la reglementation nationale en vigueur et les bonnes pratiques liees a leurs activites. b) Le prestataire doit faire signer la charte d'ethique a l'ensemble des personnes impliquees dans la fourniture du service. c) Le prestataire doit introduire, dans le contrat de travail des personnels disposant de privileges d'administration eleves sur les composants et materiels de l'infrastructure du service, un engagement de responsabilite avec un renvoi aux clauses du code du travail sur la protection du secret des affaires et de la propriete intellectuelle. Il est entendu par des privileges d'administration eleves, des actions permettant l'elevation de privileges ou la possibilite de realiser des actions sans traces techniques ou de desactiver, alterer les traces techniques. d) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible le reglement interieur et la charte d'ethique." + } + ], + "Checks": [] + }, + { + "Id": "7.3", + "Description": "Les salaries du prestataire et, le cas echeant, les sous-traitants doivent suivre un programme de sensibilisation et de formation adapte et regulier concernant la securite de l'information.", + "Name": "Sensibilisation, apprentissage et formations a la securite de l'information", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit sensibiliser a la securite de l'information et aux risques lies a la protection des donnees l'ensemble des personnes impliquees dans la fourniture du service. Il doit leur communiquer les mises a jour des politiques et procedures pertinentes dans le cadre de leurs missions. b) Le prestataire doit documenter et mettre en oeuvre un plan de formation concernant la securite de l'information adapte au service et aux missions des personnels. c) Le responsable de la securite des systemes d'information du prestataire doit valider formellement le plan de formation concernant la securite de l'information." + } + ], + "Checks": [] + }, + { + "Id": "7.4", + "Description": "Le prestataire doit mettre en place un processus disciplinaire formel et communique pour prendre des mesures a l'encontre des salaries ayant enfreint les regles de securite de l'information.", + "Name": "Processus disciplinaire", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus disciplinaire applicable a l'ensemble des personnes impliquees dans la fourniture du service ayant enfreint la politique de securite. b) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible les sanctions encourues en cas d'infraction a la politique de securite." + } + ], + "Checks": [] + }, + { + "Id": "7.5", + "Description": "Les responsabilites et les obligations en matiere de securite de l'information qui restent valables apres un changement ou une rupture du contrat de travail doivent etre definies, communiquees au salarie ou au sous-traitant et appliquees.", + "Name": "Rupture, terme ou modification du contrat de travail", + "Attributes": [ + { + "Section": "7. Securite des ressources humaines", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit definir et attribuer les roles et les responsabilites relatives a la rupture, au terme ou a la modification de tout contrat avec une personne impliquee dans la fourniture du service." + } + ], + "Checks": [] + }, + { + "Id": "8.1", + "Description": "Le prestataire doit identifier les actifs associes a l'information et aux moyens de traitement de l'information et doit etablir et tenir a jour un inventaire de ces actifs.", + "Name": "Inventaire et propriete des actifs", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "cloudguard", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit tenir a jour l'inventaire de l'ensemble des equipements mettant en oeuvre le service. Cet inventaire doit preciser pour chaque equipement : les informations d'identification de l'equipement (noms, adresses IP, adresses MAC, etc.) ; la fonction de l'equipement ; le modele de l'equipement ; la localisation de l'equipement ; le proprietaire de l'equipement ; le besoin de securite des informations (au sens du chapitre 8.3). b) Le prestataire doit tenir a jour l'inventaire de l'ensemble des logiciels mettant en oeuvre le service. Cet inventaire doit identifier pour chaque logiciel, sa version et les equipements sur lesquels le logiciel est installe. c) Le prestataire doit s'assurer de la validite des licences des logiciels tout au long de la prestation." + } + ], + "Checks": [ + "cloudguard_enabled" + ] + }, + { + "Id": "8.2", + "Description": "Les salaries et les utilisateurs de tiers doivent restituer tous les actifs du prestataire en leur possession au terme de la periode d'emploi, du contrat ou de l'accord.", + "Name": "Restitution des actifs", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de restitution des actifs permettant de s'assurer que chaque personne impliquee dans la fourniture du service restitue l'ensemble des actifs en sa possession a la fin de sa periode d'emploi ou de son contrat." + } + ], + "Checks": [] + }, + { + "Id": "8.3", + "Description": "Les besoins de protection de la confidentialite, de l'integrite et de la disponibilite de l'information doivent etre identifies.", + "Name": "Identification des besoins de securite de l'information", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les differents besoins de securite des informations relatives au service. b) Lorsque le commanditaire confie au prestataire des donnees soumises a des contraintes legales, reglementaires ou sectorielles specifiques, le prestataire doit identifier les besoins de securite specifiques associes a ces contraintes." + } + ], + "Checks": [] + }, + { + "Id": "8.4", + "Description": "Un ensemble de procedures appropriees pour le marquage et la manipulation de l'information doit etre elabore et mis en oeuvre conformement au plan de classification adopte par le prestataire.", + "Name": "Marquage et manipulation de l'information", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Il est recommande que le prestataire documente et mette en oeuvre une procedure pour le marquage et la manipulation de toutes les informations participant a la delivrance du service, conformement a son besoin de securite defini au chapitre 8.3." + } + ], + "Checks": [] + }, + { + "Id": "8.5", + "Description": "Des procedures de gestion des supports amovibles doivent etre mises en oeuvre conformement au plan de classification adopte par le prestataire.", + "Name": "Gestion des supports amovibles", + "Attributes": [ + { + "Section": "8. Gestion des actifs", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure pour la gestion des supports amovibles, conformement au besoin de securite defini au chapitre 8.3. Lorsque des supports amovibles sont utilises sur l'infrastructure technique ou pour des taches d'administration, ces supports doivent etre dedies a un usage." + } + ], + "Checks": [] + }, + { + "Id": "9.1", + "Description": "Une politique de controle d'acces doit etre etablie, documentee et revue en se basant sur les exigences metier et les exigences de securite de l'information. Les regles de controle d'acces et les droits pour chaque utilisateur ou groupe d'utilisateurs doivent etre clairement definis.", + "Name": "Politiques et controle d'acces", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "identity", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de controle d'acces sur la base du resultat de son appreciation des risques et du partage des responsabilites. b) Le prestataire doit reviser annuellement la politique de controle d'acces et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [ + "identity_tenancy_admin_permissions_limited", + "identity_iam_admins_cannot_update_tenancy_admins" + ] + }, + { + "Id": "9.2", + "Description": "Un processus formel d'enregistrement et de desinscription des utilisateurs doit etre mis en oeuvre pour permettre l'attribution des droits d'acces.", + "Name": "Enregistrement et desinscription des utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure d'enregistrement et de desinscription des utilisateurs s'appuyant sur une interface de gestion des comptes et des droits d'acces. Cette procedure doit indiquer quelles donnees doivent etre supprimees au depart d'un utilisateur. b) Le prestataire doit attribuer des comptes nominatifs lors de l'enregistrement des utilisateurs places sous sa responsabilite. c) Le prestataire doit mettre en oeuvre des moyens permettant de s'assurer que la desinscription d'un utilisateur entraine la suppression de tous ses acces aux ressources du systeme d'information du service, ainsi que la suppression de ses donnees conformement a la procedure d'enregistrement et de desinscription (voir exigence 9.2 a))." + } + ], + "Checks": [] + }, + { + "Id": "9.3", + "Description": "Un processus formel de gestion des droits d'acces doit etre mis en oeuvre pour controler l'attribution des droits d'acces a tous les types d'utilisateurs et a tous les systemes et services.", + "Name": "Gestion des droits d'acces", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "identity", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'assurer l'attribution, la modification et le retrait de droits d'acces aux ressources du systeme d'information du service. b) Le prestataire doit mettre a la disposition de ses commanditaires les outils et les moyens qui permettent une differenciation des roles des utilisateurs du service, par exemple suivant leur role fonctionnel. c) Le prestataire doit tenir a jour l'inventaire des utilisateurs sous sa responsabilite disposant de droits d'administration sur les ressources du systeme d'information du service. d) Le prestataire doit etre en mesure de fournir, pour une ressource donnee mettant en oeuvre le service, la liste de tous les utilisateurs y ayant acces, qu'ils soient sous la responsabilite du prestataire ou du commanditaire ainsi que les droits d'acces qui leurs ont ete attribues. e) Le prestataire doit etre en mesure de fournir, pour un utilisateur donne, qu'ils soient sous la responsabilite du prestataire ou du commanditaire, la liste de tous ses droits d'acces sur les differents elements du systeme d'information du service. f) Le prestataire doit definir une liste de droits d'acces incompatibles entre eux. Il doit s'assurer, lors de l'attribution de droits d'acces a un utilisateur qu'il ne possede pas de droits d'acces incompatibles entre eux au titre de la liste precedemment etablie. g) Le prestataire doit inclure dans la procedure de gestion des droits d'acces les actions de revocation ou de suspension des droits de tout utilisateur." + } + ], + "Checks": [ + "identity_tenancy_admin_permissions_limited", + "identity_iam_admins_cannot_update_tenancy_admins", + "identity_tenancy_admin_users_no_api_keys", + "identity_no_resources_in_root_compartment", + "identity_non_root_compartment_exists" + ] + }, + { + "Id": "9.4", + "Description": "Les proprietaires d'actifs doivent verifier les droits d'acces des utilisateurs a intervalles reguliers.", + "Name": "Revue des droits d'acces utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "identity", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit reviser annuellement les droits d'acces des utilisateurs sur son perimetre de responsabilite. b) Le prestataire doit mettre a disposition du commanditaire un outil facilitant la revue des droits d'acces des utilisateurs places sous la responsabilite de ce dernier. c) Le prestataire doit reviser trimestriellement la liste des utilisateurs sur son perimetre de responsabilite pouvant utiliser les comptes techniques mentionnes dans l'exigence 9.2 b)." + } + ], + "Checks": [ + "identity_user_api_keys_rotated_90_days", + "identity_user_auth_tokens_rotated_90_days", + "identity_user_customer_secret_keys_rotated_90_days", + "identity_user_db_passwords_rotated_90_days" + ] + }, + { + "Id": "9.5", + "Description": "L'attribution et l'utilisation des informations secretes d'authentification doivent etre gerees dans le cadre d'un processus de gestion formel incluant une politique de mot de passe robuste et l'utilisation de l'authentification multi-facteur.", + "Name": "Gestion des authentifications des utilisateurs", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "identity", + "Type": "Automated", + "Comment": "a) Le prestataire doit formaliser et mettre en oeuvre des procedures de gestion de l'authentification des utilisateurs. En accord avec les exigences du chapitre 10, celles-ci doivent notamment porter sur : la gestion des moyens d'authentification (emission et reinitialisation de mot de passe, mise a jour des CRL et import des certificats racines en cas d'utilisation de certificats, etc.) ; la mise en place des moyens permettant une authentification a multiples facteurs afin de repondre aux differents cas d'usage du referentiel ; les systemes qui generent des mots de passe ou verifient leur robustesse, lorsqu'une authentification par mot de passe est utilisee. Ils doivent suivre les recommandations de [G_AUTH]. b) Tout mecanisme d'authentification doit prevoir le blocage d'un compte apres un nombre limite de tentatives infructueuses. c) Dans le cadre d'un service SaaS, le prestataire doit proposer a ses commanditaires des moyens d'authentification a multiples facteurs pour l'acces des utilisateurs finaux. d) Lorsque des comptes techniques, non nominatifs, sont necessaires, le prestataire doit mettre en place des mesures obligeant les utilisateurs a s'authentifier avec leur compte nominatif avant de pouvoir acceder a ces comptes techniques." + } + ], + "Checks": [ + "identity_password_policy_minimum_length_14", + "identity_password_policy_prevents_reuse", + "identity_password_policy_expires_within_365_days" + ] + }, + { + "Id": "9.6", + "Description": "L'acces aux interfaces d'administration du service cloud doit etre restreint et protege par des mecanismes d'authentification forte, incluant l'utilisation de dispositifs MFA materiels pour les comptes a privileges.", + "Name": "Acces aux interfaces d'administration", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "identity", + "Type": "Partially Automated", + "Comment": "a) Les comptes d'administration sous la responsabilite du prestataire doivent etre geres a l'aide d'outils et d'annuaires distincts de ceux utilises pour la gestion des comptes utilisateurs places sous la responsabilite du commanditaire. b) Les interfaces d'administration mises a disposition des commanditaires doivent etre distinctes des interfaces d'administration utilisees par le prestataire. c) Les interfaces d'administration mises a disposition des commanditaires ne doivent permettre aucune connexion avec des comptes d'administrateurs sous la responsabilite du prestataire. d) Les interfaces d'administration utilisees par le prestataire ne doivent pas etre accessibles a partir d'un reseau public et ainsi ne doivent permettre aucune connexion des utilisateurs sous la responsabilite du commanditaire. e) Si des interfaces d'administration sont mises a disposition des commanditaires avec un acces via un reseau public, les flux d'administration doivent etre authentifies et chiffres avec des moyens en accord avec les exigences du chapitre 10.2. f) Le prestataire doit mettre en place un systeme d'authentification multifacteur fort pour l'acces : aux interfaces d'administration utilisees par le prestataire ; aux interfaces d'administration dediees aux commanditaires. g) Dans le cadre d'un service SaaS, les interfaces d'administration mises a disposition des commanditaires doivent etre differenciees des interfaces permettant l'acces des utilisateurs finaux. h) Des lors qu'une interface d'administration est accessible depuis un reseau public, le processus d'authentification doit avoir lieu avant toute interaction entre l'utilisateur et l'interface en question. i) Lorsque le prestataire utilise un service de type IaaS comme socle d'un autre type de service (CaaS, PaaS ou SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service IaaS. j) Lorsque le prestataire utilise un service de type CaaS comme socle d'un autre type de service (PaaS ou SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service CaaS. k) Lorsque le prestataire utilise un service de type PaaS comme socle d'un autre type de service (typiquement SaaS), les ressources affectees a l'usage du prestataire ne doivent en aucun cas etre accessibles via l'interface publique mise a disposition des autres commanditaires du service PaaS." + } + ], + "Checks": [ + "identity_user_mfa_enabled_console_access", + "identity_tenancy_admin_users_no_api_keys" + ] + }, + { + "Id": "9.7", + "Description": "L'acces a l'information et aux fonctions d'application des systemes doit etre restreint conformement a la politique de controle d'acces. Les ressources doivent etre protegees contre tout acces public non autorise.", + "Name": "Restriction des acces a l'information", + "Attributes": [ + { + "Section": "9. Controle d'acces et gestion des identites", + "Service": "network", + "Type": "Automated", + "Comment": "a) Le prestataire doit mettre en oeuvre des mesures de cloisonnement appropriees entre ses commanditaires. b) Le prestataire doit mettre en oeuvre des mesures de cloisonnement appropriees entre le systeme d'information du service et ses autres systemes d'information (bureautique, informatique de gestion, gestion technique du batiment, controle d'acces physique, etc.). c) Le prestataire doit concevoir, developper, configurer et deployer le systeme d'information du service en assurant au moins un cloisonnement entre d'une part l'infrastructure technique et d'autre part les equipements necessaires a l'administration des services et des ressources qu'elle heberge. d) Dans le cadre du support technique, si les actions necessaires au diagnostic et a la resolution d'un probleme rencontre par un commanditaire necessitent un acces aux donnees du commanditaire, alors le prestataire doit : n'autoriser l'acces aux donnees du commanditaire qu'apres consentement explicite du commanditaire ; verifier que la personne a qui l'acces doit etre autorise a satisfait aux verifications de l'exigence 7.1.b ; dans le cas d'une intervention realisee a distance par une personne localisee hors de l'Union Europeenne, mettre en oeuvre une passerelle securisee (poste de rebond) par laquelle la personne devra se connecter et permettant une supervision (autorisation ou interdiction des actions, demandes d'explications, etc.) en temps reel, par une personne ayant elle-meme satisfait aux verifications de l'exigence 7.1.b ; considerer les actions menees, une fois l'acces autorise, comme des actions d'administration et les journaliser comme telles ; supprimer l'autorisation d'acces aux donnees du commanditaire au terme de ces actions." + } + ], + "Checks": [ + "network_default_security_list_restricts_traffic", + "network_security_group_ingress_from_internet_to_ssh_port", + "network_security_group_ingress_from_internet_to_rdp_port", + "network_security_list_ingress_from_internet_to_ssh_port", + "network_security_list_ingress_from_internet_to_rdp_port", + "objectstorage_bucket_not_publicly_accessible", + "analytics_instance_access_restricted", + "database_autonomous_database_access_restricted", + "integration_instance_access_restricted" + ] + }, + { + "Id": "10.1", + "Description": "Les donnees stockees dans le cadre du service cloud doivent etre chiffrees au repos en utilisant des algorithmes et des longueurs de cle conformes a l'etat de l'art.", + "Name": "Chiffrement des donnees stockees", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "blockstorage", + "Type": "Automated", + "Comment": "a) Le prestataire doit definir et mettre en oeuvre un mecanisme de chiffrement empechant la recuperation des donnees des commanditaires en cas de reallocation d'une ressource ou de recuperation du support physique. Dans le cas d'un service IaaS ou CaaS, cet objectif pourra par exemple etre atteint par un chiffrement du disque ou du systeme de fichier, lorsque le protocole d'acces en mode fichiers garantit que seuls des blocs vides peuvent etre alloues, ou par un chiffrement par volume dans le cas d'un acces en mode bloc, avec au moins une cle par commanditaire. Dans le cas d'un service PaaS ou SaaS, cet objectif pourra etre atteint en utilisant un chiffrement applicatif dans le perimetre du prestataire, avec au moins une cle par commanditaire. b) Le prestataire doit utiliser une methode de chiffrement des donnees respectant les regles de [CRYPTO_B1]. c) Il est recommande d'utiliser une methode de chiffrement des donnees respectant les recommandations de [CRYPTO_B1]. d) Le prestataire doit mettre en place un chiffrement des donnees sur les supports amovibles et les supports de sauvegarde amenes a quitter le perimetre de securite physique du systeme d'information du service (au sens du chapitre 10), en fonction du besoin de securite des donnees (voir chapitre 8.3)." + } + ], + "Checks": [ + "blockstorage_block_volume_encrypted_with_cmk", + "blockstorage_boot_volume_encrypted_with_cmk", + "objectstorage_bucket_encrypted_with_cmk", + "filestorage_file_system_encrypted_with_cmk" + ] + }, + { + "Id": "10.2", + "Description": "Les flux de donnees entre les composants du service cloud et entre le service et les commanditaires doivent etre chiffres en transit en utilisant des protocoles et des algorithmes conformes a l'etat de l'art.", + "Name": "Chiffrement des flux", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "compute", + "Type": "Partially Automated", + "Comment": "a) Lorsque le prestataire met en oeuvre un mecanisme de chiffrement des flux reseau, celui-ci doit respecter les regles de [CRYPTO_B1]. b) Lorsque le prestataire met en oeuvre un mecanisme de chiffrement des flux reseau, il est recommande que celui-ci respecte les recommandations de [CRYPTO_B1]. c) Si le protocole TLS est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_TLS]. d) Si le protocole IPsec est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_IPSEC]. e) Si le protocole SSH est mis en oeuvre, le prestataire doit appliquer les recommandations de [NT_SSH]." + } + ], + "Checks": [ + "compute_instance_in_transit_encryption_enabled" + ] + }, + { + "Id": "10.3", + "Description": "Les mots de passe doivent etre stockes sous forme hachee en utilisant des algorithmes robustes conformes a l'etat de l'art et les politiques de mot de passe doivent imposer des exigences de complexite adequates.", + "Name": "Hachage des mots de passe", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "identity", + "Type": "Partially Automated", + "Comment": "a) Le prestataire ne doit stocker que l'empreinte des mots de passe des utilisateurs et des comptes techniques. b) Le prestataire doit mettre en oeuvre une fonction de hachage respectant les regles de [CRYPTO_B1]. c) Il est recommande que le prestataire mette en oeuvre une fonction de hachage respectant les recommandations de [CRYPTO_B1]. d) Le prestataire doit generer les empreintes des mots de passe avec une fonction de hachage associee a l'utilisation d'un sel cryptographique respectant les regles de [CRYPTO_B1]." + } + ], + "Checks": [ + "identity_password_policy_minimum_length_14", + "identity_password_policy_prevents_reuse" + ] + }, + { + "Id": "10.4", + "Description": "Des mecanismes de non-repudiation doivent etre mis en oeuvre pour assurer la tracabilite des actions effectuees sur le service cloud, incluant la validation de l'integrite des journaux.", + "Name": "Non repudiation", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "audit", + "Type": "Partially Automated", + "Comment": "a) Lorsque le prestataire met en oeuvre un mecanisme de signature electronique, celui-ci doit respecter les regles de [CRYPTO_B1]. b) Lorsque le prestataire met en oeuvre un mecanisme de signature electronique, il est recommande que celui-ci respecte les recommandations de [CRYPTO_B1]." + } + ], + "Checks": [ + "audit_log_retention_period_365_days" + ] + }, + { + "Id": "10.5", + "Description": "Les secrets cryptographiques (cles, certificats, mots de passe) doivent etre geres de maniere securisee tout au long de leur cycle de vie, incluant la generation, le stockage, la distribution, la rotation et la destruction.", + "Name": "Gestion des secrets", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "kms", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit mettre en oeuvre des cles cryptographiques respectant les regles de [CRYPTO_B2]. b) Il est recommande que le prestataire mette en oeuvre des cles cryptographiques respectant les recommandations de [CRYPTO_B2]. c) Le prestataire doit proteger l'acces aux cles cryptographiques et autres secrets utilises pour le chiffrement des donnees par un moyen adapte : conteneur de securite (logiciel ou materiel) ou support disjoint. d) Le prestataire doit proteger l'acces aux cles cryptographiques et autres secrets utilises pour les taches d'administration par un conteneur de securite adapte, logiciel ou materiel." + } + ], + "Checks": [ + "kms_key_rotation_enabled", + "identity_user_api_keys_rotated_90_days", + "identity_user_customer_secret_keys_rotated_90_days", + "identity_user_auth_tokens_rotated_90_days", + "identity_user_db_passwords_rotated_90_days" + ] + }, + { + "Id": "10.6", + "Description": "Les racines de confiance (certificats racine, autorites de certification) utilisees dans le cadre du service cloud doivent etre gerees de maniere securisee. Les certificats doivent etre valides et utiliser des algorithmes de cle robustes.", + "Name": "Racines de confiance", + "Attributes": [ + { + "Section": "10. Cryptologie", + "Service": "general", + "Type": "Manual", + "Comment": "a) Sur l'infrastructure technique, le prestataire doit utiliser exclusivement des certificats de cle publique issus d'une autorite de certification d'un Etat membre de l'Union Europeenne (les ceremonies de generation des cles maitresses doivent avoir lieu dans un pays membre de l'Union Europeenne et en presence du prestataire)." + } + ], + "Checks": [] + }, + { + "Id": "11.1", + "Description": "Des perimetres de securite doivent etre definis et utilises pour proteger les zones contenant des informations sensibles ou critiques et les moyens de traitement de l'information.", + "Name": "Perimetres de securite physique", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des perimetres de securite, incluant le marquage des zones et les differents moyens de limitation et de controle des acces. b) Le prestataire doit distinguer des zones publiques, des zones privees et des zones sensibles. 11.1.1. Zones publiques : a) Les zones publiques sont accessibles a tous dans les limites de la propriete du prestataire. Le prestataire ne doit heberger aucune ressource devolue au service ou permettant d'acceder a des composantes de celui-ci dans les zones publiques. 11.1.2. Zones privees : a) Les zones privees peuvent heberger : les plateformes et moyens de developpement du service ; les postes d'administration, d'exploitation et de supervision ; les locaux a partir desquels le prestataire opere. 11.1.3. Zones sensibles : a) Les zones sensibles sont reservees a l'hebergement du systeme d'information de production du service hors postes d'administration, d'exploitation et de supervision." + } + ], + "Checks": [] + }, + { + "Id": "11.2", + "Description": "Les zones securisees doivent etre protegees par des controles d'acces physiques adequats pour s'assurer que seul le personnel autorise est admis.", + "Name": "Controle d'acces physique", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "11.2.1. Zones privees : a) Le prestataire doit proteger les zones privees contre les acces non autorises. Pour ce faire, il doit mettre en oeuvre un controle d'acces physique reposant au moins sur un facteur personnel : la connaissance d'un secret, la detention d'un objet ou la biometrie. b) Il est recommande que le prestataire respecte les recommandations de [G_CVAP] pour mettre en oeuvre du controle d'acces physique. c) Le prestataire doit definir et documenter des mesures d'acces physique derogatoires en cas d'urgence. d) Le prestataire doit afficher a l'entree des zones privees un avertissement relatif aux limites et conditions d'acces a ces zones. e) Le prestataire doit definir et documenter les plages horaires et conditions d'acces aux zones privees en fonction des profils des intervenants. f) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de s'assurer que les visiteurs sont systematiquement accompagnes par le prestataire lors de leurs acces et sejours en zone privee. Le prestataire doit conserver une trace de l'identite des visiteurs conformement a la legislation et reglementation en vigueur. g) En cas d'intervention (actions de diagnostic, de maintenance, ou d'administration) en zone privee par un tiers visiteur, le prestataire doit faire superviser (suivre, autoriser, interdire, questionner) les actions par un personnel ayant satisfait aux verifications de l'exigence 7.1.b. h) Le prestataire doit documenter et mettre en oeuvre des mecanismes de surveillance et de detection des acces non autorises aux zones privees. 11.2.2. Zones sensibles : a) Le prestataire doit proteger les zones sensibles contre les acces non autorises. Pour ce faire, il doit mettre en oeuvre un controle d'acces physique reposant au moins sur deux facteurs personnels : la connaissance d'un secret, la detention d'un objet ou la biometrie. b) Il est recommande que le prestataire respecte les recommandations de [G_CVAP] pour la mise en oeuvre du controle d'acces physique. c) Le prestataire doit definir et documenter des mesures d'acces physique derogatoires en cas d'urgence. d) Le prestataire doit afficher a l'entree des zones sensibles un avertissement relatif aux limites et conditions d'acces a ces zones. e) Le prestataire doit definir et documenter les plages horaires et conditions d'acces aux zones sensibles en fonction des profils des intervenants. f) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de s'assurer que les visiteurs sont systematiquement accompagnes par le prestataire lors de leurs acces et sejours en zone sensible. Le prestataire doit conserver une trace de l'identite des visiteurs conformement a la legislation et reglementation en vigueur. g) En cas d'intervention (actions de diagnostic, de maintenance, ou d'administration) en zone sensible par un tiers visiteur, le prestataire doit faire superviser (suivre, autoriser, interdire, questionner) les actions par un personnel ayant satisfait aux verifications de l'exigence 7.1.b. h) Le prestataire doit documenter et mettre en oeuvre des mecanismes de surveillance et de detection des acces non autorises aux zones sensibles. i) Le prestataire doit mettre en place une journalisation des acces physiques aux zones sensibles. Il doit effectuer une revue de ces journaux au moins mensuellement. j) Le prestataire doit mettre en oeuvre les moyens garantissant qu'aucun acces direct n'existe entre une zone publique et une zone sensible." + } + ], + "Checks": [] + }, + { + "Id": "11.3", + "Description": "Des mesures de protection contre les menaces exterieures et environnementales, telles que les catastrophes naturelles, les attaques malveillantes ou les accidents, doivent etre concues et appliquees.", + "Name": "Protection contre les menaces exterieures et environnementales", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de minimiser les risques inherents aux sinistres physiques (incendie, degat des eaux, etc.) et naturels (risques climatiques, inondations, seismes, etc.). b) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de limiter les risques de depart et de propagation de feu ainsi que les risques de degat des eaux. c) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de prevenir et limiter les consequences d'une coupure d'alimentation electrique et permettre une reprise du service conformement aux exigences de disponibilite du service definies dans la convention de service. d) Le prestataire doit documenter et mettre en oeuvre les moyens permettant de maintenir des conditions de temperature et d'humidite adaptees aux equipements. De plus, il doit mettre en oeuvre des mesures permettant de prevenir les pannes de climatisation et d'en limiter les consequences. e) Le prestataire doit documenter et mettre en oeuvre des controles et tests reguliers des equipements de detection et de protection physique." + } + ], + "Checks": [] + }, + { + "Id": "11.4", + "Description": "Des mesures de securite physique pour le travail dans les zones privees et sensibles doivent etre concues et appliquees.", + "Name": "Travail dans les zones privees et sensibles", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit integrer les elements de securite physique dans la politique de securite et l'appreciation des risques conformement au niveau de securite requis par la categorie de la zone. b) Le prestataire doit documenter et mettre en oeuvre des procedures relatives au travail en zones privees et sensibles. Il doit communiquer ces procedures aux intervenants concernes." + } + ], + "Checks": [] + }, + { + "Id": "11.5", + "Description": "Les points d'acces tels que les zones de livraison et de chargement et les autres points par lesquels des personnes non autorisees peuvent penetrer dans les locaux doivent etre controles.", + "Name": "Zones de livraison et de chargement", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Les zones de livraison et de chargement et les autres points par lesquels des personnes non autorisees peuvent penetrer dans les locaux sans etre accompagnees sont considerees comme des zones publiques. b) Le prestataire doit isoler les points d'acces de ces zones vers les zones privees et sensibles, de facon a eviter les acces non autorises, ou a defaut, implementer des mesures compensatoires permettant d'assurer le meme niveau de securite." + } + ], + "Checks": [] + }, + { + "Id": "11.6", + "Description": "Le cablage electrique et de telecommunications transportant des donnees ou supportant des services d'information doit etre protege contre les interceptions, les interferences ou les dommages.", + "Name": "Securite du cablage", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de proteger le cablage electrique et de telecommunication des dommages physiques et des possibilites d'interception. b) Le prestataire doit etablir et tenir a jour un plan de cablage. c) Il est recommande que le prestataire mette en oeuvre des mesures permettant d'identifier les cables (par exemple code couleur, etiquette, etc.) afin d'en faciliter l'exploitation et limiter les erreurs de manipulation." + } + ], + "Checks": [] + }, + { + "Id": "11.7", + "Description": "Les materiels doivent etre entretenus correctement pour garantir leur disponibilite permanente et leur integrite.", + "Name": "Maintenance des materiels", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de s'assurer que les conditions d'installation, de maintenance et d'entretien des equipements du systeme d'information du service heberges en zones privees et sensibles sont compatibles avec les exigences de confidentialite et de disponibilite du service definies dans la convention de service. b) Le prestataire doit souscrire des contrats de maintenance permettant de disposer des mises a jour de securite des logiciels installes sur les equipements du systeme d'information du service. c) Le prestataire doit s'assurer que les supports ne peuvent etre retournes a un tiers que si les donnees du commanditaire y sont stockees chiffrees conformement au chapitre 10.1 ou ont prealablement ete detruites a l'aide d'un mecanisme d'effacement securise par reecriture de motifs aleatoires. d) Le prestataire doit documenter et mettre en oeuvre des mesures permettant de s'assurer que les conditions d'installation, de maintenance et d'entretien des equipements techniques annexes (alimentation electrique, climatisation, incendie, etc.) sont compatibles avec les exigences de disponibilite du service definies dans la convention de service." + } + ], + "Checks": [] + }, + { + "Id": "11.8", + "Description": "Les materiels, les informations ou les logiciels ne doivent pas etre sortis des locaux du prestataire sans autorisation prealable.", + "Name": "Sortie des actifs", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de transfert hors site de donnees du commanditaire, equipements et logiciels. Cette procedure doit necessiter que la direction du prestataire donne son autorisation ecrite. Dans tous les cas, le prestataire doit mettre en oeuvre les moyens permettant de garantir que le niveau de protection en confidentialite et en integrite des actifs durant leur transport est equivalent a celui sur site." + } + ], + "Checks": [] + }, + { + "Id": "11.9", + "Description": "Tous les composants des equipements contenant des supports de stockage doivent etre verifies pour s'assurer que toute donnee sensible et tout logiciel sous licence ont ete supprimes ou ecrases de facon securisee avant leur mise au rebut ou leur reutilisation.", + "Name": "Recyclage securise du materiel", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des moyens permettant d'effacer de maniere securisee par reecriture de motifs aleatoires tout support de donnees mis a disposition d'un commanditaire. Si l'espace de stockage est chiffre dans le cadre de l'exigence 10.1.a), l'effacement peut etre realise par un effacement securise de la cle de chiffrement." + } + ], + "Checks": [] + }, + { + "Id": "11.10", + "Description": "Le materiel en attente d'utilisation doit etre protege de maniere adequate.", + "Name": "Materiel en attente d'utilisation", + "Attributes": [ + { + "Section": "11. Securite physique et environnementale", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de protection du materiel en attente d'utilisation." + } + ], + "Checks": [] + }, + { + "Id": "12.1", + "Description": "Les procedures d'exploitation doivent etre documentees et mises a disposition de tous les utilisateurs concernes.", + "Name": "Procedures d'exploitation documentees", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter les procedures d'exploitation, les tenir a jour et les rendre accessibles au personnel concerne." + } + ], + "Checks": [] + }, + { + "Id": "12.2", + "Description": "Les changements apportes au systeme d'information du prestataire, aux processus metier, aux moyens de traitement de l'information et aux systemes qui ont une incidence sur la securite de l'information doivent etre geres.", + "Name": "Gestion des changements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "cloudguard", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de gestion des changements apportes aux systemes et moyens de traitement de l'information. b) Le prestataire doit documenter et mettre en oeuvre une procedure permettant, en cas d'operations realisees par le prestataire et pouvant avoir un impact sur la securite ou la disponibilite du service, de communiquer au plus tot a l'ensemble de ses commanditaires les informations suivantes : la date et l'heure programmees du debut et de la fin des operations ; la nature des operations ; les impacts sur la securite ou la disponibilite du service ; le contact au sein du prestataire. c) Dans le cadre d'un service PaaS, le prestataire doit informer au plus tot le commanditaire de toute modification a venir sur des elements logiciels sous sa responsabilite des lors que la compatibilite complete ne peut etre assuree. d) Le prestataire doit informer au plus tot le commanditaire de toute modification a venir sur les elements du service des lors qu'elle est susceptible d'occasionner une perte de fonctionnalite pour le commanditaire." + } + ], + "Checks": [ + "cloudguard_enabled", + "audit_log_retention_period_365_days" + ] + }, + { + "Id": "12.3", + "Description": "Les environnements de developpement, de test et d'exploitation doivent etre separes pour reduire les risques d'acces non autorise ou de changements non souhaites dans l'environnement d'exploitation.", + "Name": "Separation des environnements de developpement, de test et d'exploitation", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "identity", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures permettant de separer physiquement les environnements lies a la production du service des autres environnements, dont les environnements de developpement." + } + ], + "Checks": [ + "identity_non_root_compartment_exists", + "identity_no_resources_in_root_compartment" + ] + }, + { + "Id": "12.4", + "Description": "Des mesures de detection, de prevention et de recuperation conjuguees a une sensibilisation des utilisateurs doivent etre mises en oeuvre pour proteger le systeme d'information contre les codes malveillants.", + "Name": "Mesures contre les codes malveillants", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "cloudguard", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures de detection, de prevention et de restauration pour se proteger des codes malveillants. Le perimetre d'application de cette exigence sur le systeme d'information du service doit necessairement contenir les postes utilisateurs sous la responsabilite du prestataire et les flux entrants sur ce meme systeme d'information. b) Le prestataire doit documenter et mettre en oeuvre une sensibilisation de ses employes aux risques lies aux codes malveillants et aux bonnes pratiques pour reduire l'impact d'une infection." + } + ], + "Checks": [ + "cloudguard_enabled", + "events_rule_cloudguard_problems" + ] + }, + { + "Id": "12.5", + "Description": "Des copies de sauvegarde des informations, des logiciels et des images systeme doivent etre effectuees et testees regulierement conformement a une politique de sauvegarde convenue.", + "Name": "Sauvegarde des informations", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "objectstorage", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de sauvegarde et de restauration des donnees sous sa responsabilite dans le cadre du service. Cette politique doit prevoir une sauvegarde quotidienne de l'ensemble des donnees (informations, logiciels, configurations, etc.) sous la responsabilite du prestataire dans le cadre du service. b) Le prestataire doit documenter et mettre en oeuvre des mesures de protection des sauvegardes conformement a la politique de controle d'acces (voir chapitre 9). Cette politique doit prevoir une revue mensuelle des traces d'acces aux sauvegardes. c) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester regulierement la restauration des sauvegardes. d) Le prestataire doit localiser les sauvegardes a une distance suffisante des equipements principaux en coherence avec les resultats de l'appreciation de risques et permettant de faire face a des sinistres majeurs. Les sauvegardes sont assujetties aux memes exigences de localisation que les donnees operationnelles. Le ou les sites de sauvegarde sont assujettis aux memes exigences de securite que le site principal, en particulier celles listees aux chapitres 8 et 11. Les communications entre site principal et site de sauvegarde doivent etre protegees par chiffrement, conformement aux exigences du chapitre 10." + } + ], + "Checks": [ + "objectstorage_bucket_versioning_enabled" + ] + }, + { + "Id": "12.6", + "Description": "Des journaux d'evenements enregistrant les activites des utilisateurs, les exceptions, les defaillances et les evenements de securite de l'information doivent etre crees, tenus a jour et regulierement revus.", + "Name": "Journalisation des evenements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "audit", + "Type": "Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique de journalisation incluant au minimum les elements suivants : la liste des sources de collecte ; la liste des evenements a journaliser par source ; l'objet de la journalisation par evenement ; la frequence de la collecte et base de temps utilisee ; la duree de retention locale et centralisee ; les mesures de protection des journaux (dont chiffrement et duplication) ; la localisation des journaux. b) Le prestataire doit generer et collecter les evenements suivants : les activites des utilisateurs liees a la securite de l'information ; la modification des droits d'acces dans le perimetre de sa responsabilite ; les evenements issus des mecanismes de lutte contre les codes malveillants (voir chapitre 12.4) ; les exceptions ; les defaillances ; tout autre evenement lie a la securite de l'information. c) Le prestataire doit conserver les evenements issus de la journalisation pendant une duree minimale de six mois sous reserve du respect des exigences legales et reglementaires. d) Le prestataire doit fournir, sur demande d'un commanditaire, l'ensemble des evenements le concernant. e) Il est recommande que le systeme de journalisation mis en place par le prestataire respecte les recommandations de [NT_JOURNAL]." + } + ], + "Checks": [ + "audit_log_retention_period_365_days", + "events_rule_iam_group_changes", + "events_rule_iam_policy_changes", + "events_rule_identity_provider_changes", + "events_rule_idp_group_mapping_changes", + "events_rule_local_user_authentication", + "events_rule_network_gateway_changes", + "events_rule_network_security_group_changes", + "events_rule_route_table_changes", + "events_rule_security_list_changes", + "events_rule_user_changes", + "events_rule_vcn_changes", + "network_vcn_subnet_flow_logs_enabled", + "objectstorage_bucket_logging_enabled" + ] + }, + { + "Id": "12.7", + "Description": "Les moyens de journalisation et les informations journalisees doivent etre proteges contre les risques de falsification et les acces non autorises.", + "Name": "Protection de l'information journalisee", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "audit", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit proteger les equipements de journalisation et les evenements journalises contre les atteintes a leur disponibilite, integrite ou confidentialite, conformement au chapitre 3.2 de [NT_JOURNAL]. b) Le prestataire doit gerer le dimensionnement de l'espace de stockage de l'ensemble des equipements hebergeant une ou plusieurs sources de collecte afin de permettre la conservation locale des evenements journalises prevue par la politique de journalisation des evenements. Cette gestion du dimensionnement doit prendre en compte les evolutions du systeme d'information. c) Le prestataire doit transferer les evenements journalises en assurant leur protection en confidentialite et en integrite, sur un ou plusieurs serveurs centraux dedies et doit les stocker sur une machine physique distincte de celle qui les a generes. d) Le prestataire doit mettre en place une sauvegarde des evenements collectes suivant une politique adaptee. e) Le prestataire doit executer les processus de journalisation et de collecte des evenements avec des comptes disposant de privileges necessaires et suffisants et doit limiter l'acces aux evenements journalises conformement a la politique de controle d'acces (voir chapitre 9.1)." + } + ], + "Checks": [ + "audit_log_retention_period_365_days", + "objectstorage_bucket_encrypted_with_cmk", + "objectstorage_bucket_not_publicly_accessible" + ] + }, + { + "Id": "12.8", + "Description": "Les horloges de tous les systemes de traitement de l'information pertinents d'un organisme ou d'un domaine de securite doivent etre synchronisees sur une source de reference temporelle unique.", + "Name": "Synchronisation des horloges", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une synchronisation des horloges de l'ensemble des equipements sur une ou plusieurs sources de temps internes coherentes entre elles. Ces sources pourront elles-memes etre synchronisees sur plusieurs sources fiables externes, sauf pour les reseaux isoles. b) Le prestataire doit mettre en place l'horodatage de chaque evenement journalise." + } + ], + "Checks": [] + }, + { + "Id": "12.9", + "Description": "Les evenements de securite doivent etre analyses et correles afin de detecter les incidents de securite. Des systemes de detection et de correlation doivent etre mis en oeuvre.", + "Name": "Analyse et correlation des evenements", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "cloudguard", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une infrastructure permettant l'analyse et la correlation des evenements enregistres par le systeme de journalisation afin de detecter les evenements susceptibles d'affecter la securite du systeme d'information du service, en temps reel ou a posteriori pour des evenements remontant jusqu'a six mois. b) Il est recommande de s'appuyer sur le referentiel d'exigences des prestataires de detection d'incidents de securite [PDIS] pour la mise en place et l'exploitation de l'infrastructure d'analyse et de correlation des evenements. c) Le prestataire doit acquitter les alarmes remontees par l'infrastructure d'analyse et de correlation des evenements au moins quotidiennement." + } + ], + "Checks": [ + "cloudguard_enabled", + "events_rule_cloudguard_problems", + "events_notification_topic_and_subscription_exists", + "events_rule_iam_group_changes", + "events_rule_iam_policy_changes", + "events_rule_user_changes", + "events_rule_vcn_changes", + "events_rule_network_gateway_changes", + "events_rule_route_table_changes", + "events_rule_security_list_changes", + "events_rule_network_security_group_changes" + ] + }, + { + "Id": "12.10", + "Description": "Des regles regissant l'installation de logiciels par les utilisateurs doivent etre etablies et mises en oeuvre. Les systemes doivent etre geres de maniere centralisee et les correctifs appliques regulierement.", + "Name": "Installation de logiciels sur des systemes en exploitation", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de controler l'installation de logiciels sur les equipements du systeme d'information du service. b) Le prestataire doit documenter et mettre en oeuvre une procedure de gestion de la configuration des environnements logiciels mis a la disposition du commanditaire, notamment pour leur maintien en condition de securite. c) Le prestataire doit fournir une capacite d'inspection et de suppression, si necessaire, des entrants (controle de l'authenticite et de l'innocuite des mises a jour, controle de l'innocuite des outils fournis, etc.) relatifs au perimetre de l'infrastructure technique : cette capacite d'inspection et de suppression doit generer des journaux d'activite et doit pouvoir faire l'objet d'un audit de code ; les entrants doivent etre traites sur des dispositifs specifiques operes et maintenus par le prestataire et heberges dans une zone cloisonnee du reste de l'infrastructure (du type zone demilitarisee telle que definie dans [G_INT])." + } + ], + "Checks": [] + }, + { + "Id": "12.11", + "Description": "Les informations sur les vulnerabilites techniques des systemes d'information utilises doivent etre obtenues en temps voulu, l'exposition du prestataire a ces vulnerabilites doit etre evaluee et les mesures appropriees doivent etre prises pour traiter le risque associe.", + "Name": "Gestion des vulnerabilites techniques", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "cloudguard", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus de veille permettant de gerer les vulnerabilites techniques des logiciels et des systemes utilises dans le systeme d'information du service. b) Le prestataire doit evaluer son exposition a ces vulnerabilites en les incluant dans l'appreciation des risques et appliquer les mesures de traitement du risque adaptees." + } + ], + "Checks": [ + "cloudguard_enabled" + ] + }, + { + "Id": "12.12", + "Description": "L'administration des systemes d'information du service cloud doit etre effectuee de maniere securisee via des canaux dedies et des protocoles securises.", + "Name": "Administration", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure obligeant les administrateurs sous sa responsabilite a utiliser des terminaux dedies pour la realisation exclusive des taches d'administration, en accord avec le chapitre 4.1 intitule 'poste et reseau d'administration' de [NT_ADMIN]. Il doit les maitriser et les maintenir a jour. b) Le prestataire doit mettre en place des mesures de durcissement de la configuration des terminaux utilises pour les taches d'administration, notamment celles du chapitre 4.2 intitule 'securisation du socle' de [NT_ADMIN]. c) Lorsque le prestataire autorise une situation de mobilite pour les administrateurs sous sa responsabilite, il doit l'encadrer par une politique documentee. La solution mise en oeuvre doit assurer que le niveau de securite de cette situation de mobilite est au moins equivalent au niveau de securite hors situation de mobilite (voir chapitres 9.6 et 9.7). Cette solution doit notamment inclure : l'utilisation d'un tunnel chiffre, non debrayable et non contournable, pour l'ensemble des flux (voir chapitre 10.2) ; le chiffrement integral du disque (voir chapitre 10.1)." + } + ], + "Checks": [] + }, + { + "Id": "12.13", + "Description": "Le telediagnostic et la telemaintenance des composants de l'infrastructure doivent etre encadres par des procedures de securite specifiques.", + "Name": "Telediagnostic et telemaintenance des composants de l'infrastructure", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "general", + "Type": "Manual", + "Comment": "a) Dans le cadre du telediagnostic ou de la telemaintenance de composants de l'infrastructure, considerant les risques d'atteinte a la confidentialite des donnees des commanditaires, le prestataire doit : verifier que la personne a qui l'acces doit etre autorise a satisfait aux verifications de l'exigence 7.1.b ; dans le cas d'une intervention realisee par une personne n'ayant pas satisfait aux verifications de l'exigence 7.1.b, mettre en oeuvre une passerelle securisee (poste de rebond) par laquelle la personne devra se connecter et permettant une supervision des actions (autorisation ou interdiction des actions, demande d'explications, etc.) en temps reel, par une personne ayant elle-meme satisfait aux verifications de l'exigence 7.1.b. La passerelle securisee devra repondre aux objectifs de securite specifies dans [G_EXT] ; considerer les actions menees, une fois l'acces autorise, comme des actions d'administration et les journaliser comme telles ; supprimer l'autorisation d'acces a l'issue de l'intervention." + } + ], + "Checks": [] + }, + { + "Id": "12.14", + "Description": "Les flux sortants de l'infrastructure du service cloud doivent etre surveilles afin de detecter et de prevenir les exfiltrations de donnees et les communications non autorisees.", + "Name": "Surveillance des flux sortants de l'infrastructure", + "Attributes": [ + { + "Section": "12. Securite liee a l'exploitation", + "Service": "network", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit fournir une capacite d'inspection et de suppression des sortants de l'infrastructure technique relatifs au perimetre du service (informations de facturation, les eventuels journaux necessaires au traitement d'incidents, etc.) : les sortants doivent pouvoir etre expurges des donnees pouvant porter atteinte a la confidentialite des donnees des commanditaires ; cette capacite d'inspection et de suppression doit generer des journaux d'activite et doit pouvoir faire l'objet d'un audit de code ; les sortants sont traites sur des dispositifs specifiques operes et maintenus par le prestataire, et heberges dans une zone cloisonnee du reste de l'infrastructure (du type zone demilitarisee telle que definie dans [G_INT])." + } + ], + "Checks": [ + "network_vcn_subnet_flow_logs_enabled" + ] + }, + { + "Id": "13.1", + "Description": "Le prestataire doit etablir et maintenir une cartographie complete et a jour de son systeme d'information, incluant les reseaux, les flux et les composants.", + "Name": "Cartographie du systeme d'information", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "cloudguard", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit etablir et tenir a jour une cartographie du systeme d'information du service, en lien avec l'inventaire des actifs (voir chapitre 8.1), comprenant au minimum les elements suivants : la liste des ressources materielles ou virtualisees ; les noms et fonctions des applications, supportant le service ; le schema d'architecture reseau au niveau 3 du modele OSI sur lequel les points nevralgiques sont identifies : les points d'interconnexions, notamment avec les reseaux tiers et publics ; les reseaux, sous-reseaux, notamment les reseaux d'administration ; les equipements assurant des fonctions de securite (filtrage, authentification, chiffrement, etc.) ; les serveurs hebergeant des donnees ou assurant des fonctions sensibles ; la matrice des flux reseau autorises en precisant : leur description technique (services, protocoles et ports) ; la justification metier ou d'infrastructure technique ; le cas echeant, lorsque des services, protocoles ou ports reputes non surs sont utilises, les mesures compensatoires mises en place, dans la logique de defense en profondeur. b) Le prestataire doit reviser au moins annuellement la cartographie." + } + ], + "Checks": [ + "cloudguard_enabled", + "network_vcn_subnet_flow_logs_enabled" + ] + }, + { + "Id": "13.2", + "Description": "Les reseaux doivent etre cloisonnes et les flux entre les segments doivent etre filtres selon le principe du moindre privilege. Les groupes de securite et les listes de controle d'acces reseau doivent etre configures de maniere restrictive.", + "Name": "Cloisonnement des reseaux", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "network", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre, pour le systeme d'information du service, les mesures de cloisonnement (logique, physique ou par chiffrement) pour separer les flux reseau selon : la sensibilite des informations transmises ; la nature des flux (production, administration, supervision, etc.) ; le domaine d'appartenance des flux (des commanditaires - avec distinction par commanditaire ou ensemble de commanditaires, du prestataire, des tiers, etc.) ; le domaine technique (traitement, stockage, etc.). b) Le prestataire doit cloisonner, physiquement ou par chiffrement, tous les flux de donnees internes au systeme d'information du service vis-a-vis de tout autre systeme d'information. Lorsque ce cloisonnement est realise par chiffrement, il est realise en accord avec les exigences du chapitre 10.2. c) Dans le cas ou le reseau d'administration de l'infrastructure technique ne fait pas l'objet d'un cloisonnement physique, les flux d'administration doivent transiter dans un tunnel chiffre, en accord avec les exigences du chapitre 10.2. d) Le prestataire doit mettre en place et configurer un pare-feu applicatif pour proteger les interfaces d'administration destinees a ses commanditaires et exposees sur un reseau public. e) Le prestataire doit mettre en oeuvre sur l'ensemble des interfaces d'administration et de supervision de l'infrastructure technique du service un mecanisme de filtrage n'autorisant que les connexions legitimes identifiees dans la matrice des flux autorises." + } + ], + "Checks": [ + "network_default_security_list_restricts_traffic", + "network_security_group_ingress_from_internet_to_ssh_port", + "network_security_group_ingress_from_internet_to_rdp_port", + "network_security_list_ingress_from_internet_to_ssh_port", + "network_security_list_ingress_from_internet_to_rdp_port", + "network_vcn_subnet_flow_logs_enabled" + ] + }, + { + "Id": "13.3", + "Description": "Les reseaux doivent etre surveilles de maniere continue afin de detecter les activites anormales ou malveillantes.", + "Name": "Surveillance des reseaux", + "Attributes": [ + { + "Section": "13. Securite des communications", + "Service": "network", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit disposer une ou plusieurs sondes de detection d'incidents de securite sur le systeme d'information du service. Ces sondes doivent notamment permettre la supervision de chacune des interconnexions du systeme d'information du service avec des systemes d'information tiers et des reseaux publics. Ces sondes doivent etre des sources de collecte pour l'infrastructure d'analyse et de correlation des evenements (voir chapitre 12.9)." + } + ], + "Checks": [ + "cloudguard_enabled", + "network_vcn_subnet_flow_logs_enabled" + ] + }, + { + "Id": "14.1", + "Description": "Des regles de developpement securise des logiciels et des systemes doivent etre etablies et appliquees au sein du prestataire.", + "Name": "Politique de developpement securise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des regles de developpement securise des logiciels et des systemes, et les appliquer aux developpements internes. b) Le prestataire doit documenter et mettre en oeuvre une formation adaptee en developpement securise aux employes concernes." + } + ], + "Checks": [] + }, + { + "Id": "14.2", + "Description": "Les changements apportes aux systemes dans le cycle de developpement doivent etre geres a l'aide de procedures formelles de controle des changements.", + "Name": "Procedures de controle des changements de systeme", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "cloudguard", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de controle des changements apportes au systeme d'information du service. b) Le prestataire doit documenter et mettre en oeuvre une procedure de validation des changements apportes au systeme d'information du service sur un environnement de pre-production avant leur mise en production. c) Le prestataire doit conserver un historique des versions des logiciels et des systemes (developpements internes ou externes, produits commerciaux) mis en oeuvre pour permettre de reconstituer, le cas echeant dans un environnement de test, un environnement complet tel qu'il etait mis en oeuvre a une date donnee. La duree de conservation de cet historique doit etre en accord avec celle des sauvegardes (voir chapitre 12.5)." + } + ], + "Checks": [ + "cloudguard_enabled", + "audit_log_retention_period_365_days" + ] + }, + { + "Id": "14.3", + "Description": "Lorsque les plateformes d'exploitation sont modifiees, les applications critiques metier doivent etre revues et testees afin de verifier qu'il n'y a pas d'effet indesirable sur l'activite ou la securite du prestataire.", + "Name": "Revue technique des applications apres changement apporte a la plateforme d'exploitation", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester, prealablement a leur mise en production, l'ensemble des applications afin de verifier l'absence de tout effet indesirable sur l'activite ou sur la securite du service." + } + ], + "Checks": [] + }, + { + "Id": "14.4", + "Description": "Les environnements de developpement doivent etre securises et isoles des environnements de production.", + "Name": "Environnement de developpement securise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "identity", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit mettre en oeuvre un environnement securise de developpement permettant de gerer l'integralite du cycle de developpement du systeme d'information du service. b) Le prestataire doit prendre en compte les environnements de developpement dans l'appreciation des risques et en assurer la protection conformement au present referentiel." + } + ], + "Checks": [ + "identity_non_root_compartment_exists", + "identity_no_resources_in_root_compartment" + ] + }, + { + "Id": "14.5", + "Description": "Le prestataire doit superviser et surveiller l'activite de developpement externalise du systeme.", + "Name": "Developpement externalise", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de superviser et de controler l'activite de developpement externalise des logiciels et des systemes. Cette procedure doit s'assurer que l'activite de developpement externalise soit conforme a la politique de developpement securise du prestataire et permette d'atteindre un niveau de securite du developpement externe equivalent a celui d'un developpement interne (voir exigence 14.1 a))." + } + ], + "Checks": [] + }, + { + "Id": "14.6", + "Description": "Des tests de securite et de conformite doivent etre effectues tout au long du cycle de developpement et apres chaque changement significatif.", + "Name": "Test de la securite et conformite du systeme", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "cloudguard", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit soumettre les systemes d'information, nouveaux ou mis a jour, a des tests de conformite et de fonctionnalite de securite pendant le developpement. Il doit documenter et mettre en oeuvre une procedure de test qui identifie : les taches a realiser ; les donnees d'entree ; les resultats attendus en sortie." + } + ], + "Checks": [ + "cloudguard_enabled" + ] + }, + { + "Id": "14.7", + "Description": "Les donnees de test doivent etre soigneusement selectionnees, protegees et controlees.", + "Name": "Protection des donnees de test", + "Attributes": [ + { + "Section": "14. Acquisition, developpement et maintenance des systemes d'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'assurer l'integrite des donnees de tests utilises en pre-production. b) Si le prestataire souhaite utiliser des donnees du commanditaire issues de la production pour realiser des tests, le prestataire doit prealablement obtenir l'accord du commanditaire et les anonymiser. Le prestataire doit assurer la confidentialite des donnees lors de leur anonymisation." + } + ], + "Checks": [] + }, + { + "Id": "15.1", + "Description": "Le prestataire doit identifier les tiers ayant acces a l'information ou aux moyens de traitement de l'information et evaluer les risques associes.", + "Name": "Identification des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit tenir a jour une liste exhaustive des tiers participant a la mise en oeuvre du service (hebergeur, developpeur, integrateur, archiveur, sous-traitant operant sur site ou a distance, fournisseurs de climatisation, etc.). Cette liste doit preciser la contribution du tiers au service et au traitement des donnees a caractere personnel. Elle doit tenir compte des cas de sous-traitance a plusieurs niveaux. b) Le prestataire doit tenir a disposition du commanditaire la liste de l'ensemble des tiers qui peuvent acceder aux donnees et l'informer de tout changement de sous-traitants au sens de l'article 28 du [RGPD] afin que le commanditaire puisse emettre des objections a cet egard." + } + ], + "Checks": [] + }, + { + "Id": "15.2", + "Description": "Tous les aspects pertinents de la securite de l'information doivent etre traites dans les accords conclus avec les tiers.", + "Name": "La securite dans les accords conclus avec les tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit exiger des tiers participant a la mise en oeuvre du service, dans leur contribution au service, un niveau de securite au moins equivalent a celui qu'il s'engage a maintenir dans sa propre politique de securite. Il doit le faire au travers d'exigences, adaptees a chaque tiers et a sa contribution au service, dans les cahiers des charges ou dans les clauses de securite des accords de partenariat. Le prestataire doit inclure ces exigences dans les contrats conclus avec les tiers. b) Le prestataire doit contractualiser, avec chacun des tiers participant a la mise en oeuvre du service, des clauses d'audit permettant a un organisme de qualification de verifier que ces tiers respectent les exigences du present referentiel. c) Le prestataire doit definir et attribuer les roles et les responsabilites relatives a la modification ou a la fin du contrat le liant a un tiers participant a la mise en oeuvre du service." + } + ], + "Checks": [] + }, + { + "Id": "15.3", + "Description": "Le prestataire doit surveiller, revoir et auditer a intervalles reguliers la prestation des services des tiers.", + "Name": "Surveillance et revue des services des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de controler regulierement les mesures mises en place par les tiers participant a la mise en oeuvre du service pour respecter les exigences du present referentiel, conformement au chapitre 18.3." + } + ], + "Checks": [] + }, + { + "Id": "15.4", + "Description": "Les changements dans les services des tiers, incluant le maintien et l'amelioration des politiques, procedures et mesures existantes de securite de l'information, doivent etre geres.", + "Name": "Gestion des changements apportes dans les services des tiers", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de suivi des changements apportes par les tiers participant a la mise en oeuvre du service susceptibles d'affecter le niveau de securite du systeme d'information du service. b) Dans la mesure ou un changement de tiers participant a la mise en oeuvre du service affecte le niveau de securite du service, le prestataire doit en informer l'ensemble des commanditaires sans delais conformement au chapitre 12.2 et mettre en oeuvre les mesures permettant de retablir le niveau de securite precedent." + } + ], + "Checks": [] + }, + { + "Id": "15.5", + "Description": "Les personnes intervenant dans le cadre du service cloud doivent etre soumises a des engagements de confidentialite.", + "Name": "Engagements de confidentialite", + "Attributes": [ + { + "Section": "15. Relations avec les tiers", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de reviser au moins annuellement les exigences en matiere d'engagements de confidentialite ou de non-divulgation vis-a-vis des tiers participant a la mise en oeuvre du service." + } + ], + "Checks": [] + }, + { + "Id": "16.1", + "Description": "Des responsabilites et des procedures de gestion doivent etre etablies pour garantir une reponse rapide, efficace et ordonnee aux incidents lies a la securite de l'information.", + "Name": "Responsabilites et procedures", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'apporter des reponses rapides et efficaces aux incidents de securite. Ces procedures doivent definir les moyens et delais de communication des incidents de securite a l'ensemble des commanditaires concernes ainsi que le niveau de confidentialite exige pour cette communication. b) Le prestataire doit informer ses employes et l'ensemble des tiers participant a la mise en oeuvre du service de cette procedure. c) Le prestataire doit documenter toute violation de donnees a caractere personnel et en informer son commanditaire. La violation doit etre notifiee a la CNIL si elle presente un risque pour les droits et libertes des personnes concernees. Elle doit faire l'objet d'une information aupres des personnes concernees lorsque le risque pour leur vie privee est eleve." + } + ], + "Checks": [] + }, + { + "Id": "16.2", + "Description": "Les evenements lies a la securite de l'information doivent etre signales dans les meilleurs delais par les voies hierarchiques appropriees. Des mecanismes de detection et de notification automatises doivent etre mis en oeuvre.", + "Name": "Signalements lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "cloudguard", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure exigeant de ses employes et des tiers participant a la mise en oeuvre du service qu'ils lui rendent compte de tout incident de securite, avere ou suspecte ainsi que de toute faille de securite. b) Le prestataire doit documenter et mettre en oeuvre une procedure permettant a l'ensemble des commanditaires de signaler tout incident de securite, avere ou suspecte et toute faille de securite. c) Le prestataire doit communiquer sans delai aux commanditaires les incidents de securite et les preconisations associees pour en limiter les impacts. Il doit permettre au commanditaire de choisir les niveaux de gravite des incidents pour lesquels il souhaite etre informe. d) Le prestataire doit communiquer les incidents de securite aux autorites competentes conformement aux exigences legales et reglementaires en vigueur." + } + ], + "Checks": [ + "cloudguard_enabled", + "events_notification_topic_and_subscription_exists", + "events_rule_cloudguard_problems" + ] + }, + { + "Id": "16.3", + "Description": "Les evenements lies a la securite de l'information doivent etre apprecies et il doit etre decide s'il est necessaire de les classer comme incidents lies a la securite de l'information.", + "Name": "Appreciation des evenements et prise de decision", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "events", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit apprecier les evenements lies a la securite de l'information et decider s'il faut les qualifier en incidents de securite. Pour l'appreciation, il doit s'appuyer sur une ou plusieurs echelles (estimation, evaluation, etc.) partagees avec le commanditaire. Note : Les incidents de securite incluent les violations de donnees a caractere personnel. b) Le prestataire doit utiliser une classification permettant d'identifier clairement les incidents de securite touchant des donnees relatives aux commanditaires, conformement aux resultats de l'appreciation des risques. Cette classification doit inclure les violations de donnees a caractere personnel." + } + ], + "Checks": [ + "events_rule_cloudguard_problems" + ] + }, + { + "Id": "16.4", + "Description": "Les incidents lies a la securite de l'information doivent etre traites conformement aux procedures documentees.", + "Name": "Reponse aux incidents lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit traiter les incidents de securite jusqu'a leur resolution et doit informer les commanditaires conformement aux procedures." + } + ], + "Checks": [] + }, + { + "Id": "16.5", + "Description": "Les connaissances acquises lors de l'analyse et du traitement des incidents lies a la securite de l'information doivent etre exploitees pour reduire la probabilite ou l'impact d'incidents futurs.", + "Name": "Tirer des enseignements des incidents lies a la securite de l'information", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un processus d'amelioration continue afin de diminuer l'occurrence et l'impact de types d'incidents de securite deja traites." + } + ], + "Checks": [] + }, + { + "Id": "16.6", + "Description": "Le prestataire doit definir et appliquer des procedures pour l'identification, le recueil, l'acquisition et la preservation de preuves. Les journaux d'audit doivent etre proteges et valides.", + "Name": "Recueil de preuves", + "Attributes": [ + { + "Section": "16. Gestion des incidents lies a la securite de l'information", + "Service": "audit", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant d'enregistrer les informations relatives aux incidents de securite et pouvant servir d'elements de preuve." + } + ], + "Checks": [ + "audit_log_retention_period_365_days" + ] + }, + { + "Id": "17.1", + "Description": "Le prestataire doit determiner ses exigences en matiere de securite de l'information et de continuite du management de la securite de l'information dans des situations defavorables, par exemple lors d'une crise ou d'un sinistre.", + "Name": "Organisation de la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre oeuvre un plan de continuite d'activite prenant en compte la securite de l'information. b) Le prestataire doit reviser annuellement le plan de continuite d'activite du service et a chaque changement majeur pouvant avoir un impact sur le service." + } + ], + "Checks": [] + }, + { + "Id": "17.2", + "Description": "Le prestataire doit etablir, documenter, mettre en oeuvre et maintenir des processus, des procedures et des mesures de controle pour assurer le niveau requis de continuite de la securite de l'information au cours d'une situation defavorable. Les services doivent etre deployes en multi-AZ.", + "Name": "Mise en oeuvre de la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "objectstorage", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre des procedures permettant de maintenir ou de restaurer l'exploitation du service et d'assurer la disponibilite des informations au niveau et dans les delais pour lesquels le prestataire s'est engage vis-a-vis du commanditaire dans la convention de service." + } + ], + "Checks": [ + "objectstorage_bucket_versioning_enabled" + ] + }, + { + "Id": "17.3", + "Description": "Le prestataire doit verifier a intervalles reguliers les mesures de continuite de la securite de l'information mises en oeuvre afin de s'assurer qu'elles sont valables et efficaces dans des situations defavorables.", + "Name": "Verifier, revoir et evaluer la continuite d'activite", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure permettant de tester le plan de continuite d'activites afin de s'assurer qu'il est pertinent et efficace en situation de crise." + } + ], + "Checks": [] + }, + { + "Id": "17.4", + "Description": "Les moyens de traitement de l'information doivent etre mis en oeuvre avec suffisamment de redondance pour repondre aux exigences de disponibilite. Les mecanismes de protection contre la suppression accidentelle doivent etre actives.", + "Name": "Disponibilite des moyens de traitement de l'information", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "objectstorage", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre les mesures qui lui permettent de repondre au besoin de disponibilite du service defini dans la convention de service (voir chapitre 19.1)." + } + ], + "Checks": [ + "objectstorage_bucket_versioning_enabled" + ] + }, + { + "Id": "17.5", + "Description": "La configuration de l'infrastructure technique du service cloud doit etre sauvegardee regulierement afin de permettre sa restauration en cas de sinistre.", + "Name": "Sauvegarde de la configuration de l'infrastructure technique", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une procedure de sauvegarde hors-ligne de la configuration de l'infrastructure technique." + } + ], + "Checks": [] + }, + { + "Id": "17.6", + "Description": "Le prestataire doit mettre a disposition du commanditaire un dispositif de sauvegarde de ses donnees, permettant la restauration en cas de sinistre.", + "Name": "Mise a disposition d'un dispositif de sauvegarde des donnees du commanditaire", + "Attributes": [ + { + "Section": "17. Continuite d'activite", + "Service": "objectstorage", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre a disposition du commanditaire un service de sauvegarde de ses donnees." + } + ], + "Checks": [ + "objectstorage_bucket_versioning_enabled" + ] + }, + { + "Id": "18.1", + "Description": "Toutes les exigences legales, reglementaires et contractuelles en vigueur, ainsi que l'approche du prestataire pour satisfaire ces exigences, doivent etre explicitement definies, documentees et tenues a jour pour chaque systeme d'information et pour le prestataire.", + "Name": "Identification de la legislation et des exigences contractuelles applicables", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit identifier les exigences legales, reglementaires et contractuelles en vigueur applicables au service. En France, le prestataire doit considerer au minimum les textes suivants : les donnees a caractere personnel [LOI_IL], [RGPD] ; le secret professionnel [CP_ART_226_13], le cas echeant sans prejudice de l'application de l'article 40 alinea 2 du Code de procedure penale relatif au signalement a une autorite judiciaire ; l'abus de confiance [CP_ART_314-1] ; le secret des correspondances privees [CP_ART_226-15] ; l'atteinte a la vie privee [CP_ART_226-1] ; l'acces ou le maintien frauduleux a un systeme d'information [CP_ART_323-1]. b) Le prestataire doit, selon son role dans les traitements de donnees a caractere personnel (responsable de traitement, sous-traitant ou co-responsable) justifier et documenter les choix de mesures techniques et organisationnelles realises en vue de repondre aux exigences de protection des donnees a caractere personnel du present referentiel (voir partie 19.5). c) Le prestataire doit documenter et mettre en oeuvre les procedures permettant de respecter les exigences legales, reglementaires et contractuelles en vigueur applicables au service, ainsi que les besoins de securite specifiques (voir exigence 8.3b)). d) Le prestataire doit, sur demande d'un commanditaire, lui rendre accessible l'ensemble de ces procedures. e) Le prestataire doit documenter et mettre en oeuvre un processus de veille actif des exigences legales, reglementaires et contractuelles en vigueur applicables au service." + } + ], + "Checks": [] + }, + { + "Id": "18.2", + "Description": "L'approche du prestataire vis-a-vis de la gestion de la securite de l'information et sa mise en oeuvre (c'est-a-dire les objectifs de controle, les mesures, les politiques, les procedures et les processus relatifs a la securite de l'information) doivent etre revues de maniere independante a intervalles definis ou en cas de changement significatif.", + "Name": "Revue independante de la securite de l'information", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre un programme d'audit sur trois ans definissant le perimetre et la frequence des audits en accord avec la gestion du changement, les politiques, et les resultats de l'appreciation des risques. Le prestataire doit inclure dans le programme d'audit un audit qualifie par an realise par un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie. L'ensemble du programme d'audit doit notamment couvrir : l'audit de la configuration de l'infrastructure technique du service (par echantillonnage et doit inclure tous types d'equipements et de serveurs presents dans le systeme d'information du service) ; le test d'intrusion des interfaces d'administration exposees sur un reseau public ; le test d'intrusion de l'interface utilisateur pour les services SaaS ; si le service beneficie de developpements internes, l'audit de code source portant sur les fonctionnalites de securite implementees (l'approche en continue doit etre privilegiee). b) Il est recommande que le prestataire mette en oeuvre des mecanismes automatises d'audit de la configuration adaptes a l'infrastructure technique du service." + } + ], + "Checks": [] + }, + { + "Id": "18.3", + "Description": "Les responsables doivent regulierement s'assurer de la conformite du traitement de l'information et des procedures au sein de leur domaine de responsabilite, au regard des politiques et des normes de securite.", + "Name": "Conformite avec les politiques et les normes de securite", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "cloudguard", + "Type": "Partially Automated", + "Comment": "a) Le prestataire via le responsable de la securite de l'information doit s'assurer regulierement de l'execution correcte de l'ensemble des procedures de securite placees sous sa responsabilite en vue de garantir leur conformite avec les politiques et normes de securite." + } + ], + "Checks": [ + "cloudguard_enabled" + ] + }, + { + "Id": "18.4", + "Description": "Les systemes d'information doivent etre examines regulierement quant a leur conformite avec les politiques et les normes de securite de l'information du prestataire.", + "Name": "Examen de la conformite technique", + "Attributes": [ + { + "Section": "18. Conformite", + "Service": "cloudguard", + "Type": "Partially Automated", + "Comment": "a) Le prestataire doit documenter et mettre en oeuvre une politique permettant de verifier la conformite technique du service aux exigences du present referentiel. Cette politique doit definir les objectifs, methodes, frequences, resultats attendus et mesures correctrices." + } + ], + "Checks": [ + "cloudguard_enabled" + ] + }, + { + "Id": "19.1", + "Description": "Le prestataire doit etablir une convention de service avec le commanditaire definissant les engagements de niveau de service, les responsabilites et les conditions d'utilisation du service cloud.", + "Name": "Convention de service", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit etablir une convention de service avec chacun des commanditaires du service. Toute modification de la convention de service doit etre soumise a acceptation du commanditaire. b) Le prestataire doit identifier dans la convention de service : les obligations, droits et responsabilites de chacune des parties : prestataire et tiers impliques dans la fourniture du service, commanditaires, etc. ; les elements explicitement exclus des responsabilites du prestataire dans la limite de ce que prevoient les exigences legales et reglementaires en vigueur, notamment l'article 28 du [RGPD] ; la localisation du service. La localisation du support doit etre precisee lorsqu'il est realise depuis un Etat hors l'Union Europeenne, comme le permet l'exigence 19.2.e. c) Le prestataire doit proposer une convention de service appliquant le droit d'un Etat membre de l'Union Europeenne. Le droit applicable doit etre identifie dans la convention de service. d) La convention de service doit indiquer que la collecte, la manipulation, le stockage, et plus generalement le traitement des donnees faits dans le cadre de l'avant-vente, de la mise en oeuvre, de la maintenance et l'arret du service sont realises conformement aux exigences edictees par la legislation en vigueur. e) La convention de service doit indiquer que le prestataire doit mettre a la disposition du commanditaire, sur demande de celui-ci, les elements d'appreciation des risques lies a la soumission des donnees du commanditaire au droit d'un etat non-membre de l'Union Europeenne (voir 5.3.e). f) Le prestataire doit decrire dans la convention de service les moyens techniques et organisationnels qu'il met en oeuvre pour assurer le respect du droit applicable. g) Le prestataire doit inclure dans la convention de service une clause de revision de la convention prevoyant notamment une resiliation sans penalite pour le commanditaire en cas de perte de la qualification octroyee au service. h) Le prestataire doit inclure dans la convention de service une clause de reversibilite permettant au commanditaire de recuperer l'ensemble de ses donnees (fournies directement par le commanditaire ou produites dans le cadre du service a partir des donnees ou des actions du commanditaire). i) Le prestataire doit assurer cette reversibilite via l'une des modalites techniques suivantes : la mise a disposition de fichiers suivant un ou plusieurs formats documentes et exploitables en dehors du service fourni par le prestataire ; la mise en place d'interfaces techniques permettant l'acces aux donnees suivant un schema documente et exploitable (API, format pivot, etc.). Les modalites techniques de la reversibilite figurent dans la convention de service. j) Le prestataire doit indiquer dans la convention de service le niveau de disponibilite du service. k) Le prestataire doit indiquer dans la convention de service qu'il ne peut disposer des donnees transmises et generees par le commanditaire, leur disposition etant reservee au commanditaire. l) Le prestataire doit indiquer dans la convention de service qu'il ne divulgue aucune information relative a la prestation a des tiers, sauf autorisation formelle et ecrite du commanditaire. m) Le prestataire doit indiquer dans la convention de service si les donnees du commanditaire sont automatiquement sauvegardees ou non. Dans la negative, le prestataire doit sensibiliser le commanditaire aux risques encourus et clairement indiquer les operations a mener par le commanditaire pour que ses donnees soient sauvegardees. n) Le prestataire doit indiquer dans la convention de service s'il autorise l'acces distant pour des actions d'administration ou de support au systeme d'information du service. o) Le prestataire doit preciser dans la convention de service que : le service est qualifie et inclure l'attestation de qualification ; le commanditaire peut deposer une reclamation relative au service qualifie aupres de l'ANSSI ; le commanditaire autorise l'ANSSI et l'organisme de qualification a auditer le service et son systeme d'information du service afin de verifier qu'ils respectent les exigences du present referentiel. p) Le prestataire doit preciser dans la convention de service que le commanditaire autorise, conformement au present referentiel (voir chapitre 18.2, un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie mandate par le prestataire a auditer le service et son systeme d'information dans le cadre du plan de controle. q) Le prestataire doit preciser dans la convention de service qu'il s'engage a mettre a disposition toutes les informations necessaires a la realisation d'audits de conformite aux dispositions de l'article 28 du [RGPD], menes par le commanditaire ou un tiers mandate. r) Il est recommande que le tiers mandate pour les audits soit un prestataire d'audit de la securite des systemes d'information [PASSI] qualifie." + } + ], + "Checks": [] + }, + { + "Id": "19.2", + "Description": "Les donnees du commanditaire doivent etre stockees et traitees dans des centres de donnees situes sur le territoire de l'Union europeenne. Les politiques de restriction de region doivent etre appliquees.", + "Name": "Localisation des donnees", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit documenter et communiquer au commanditaire la localisation du stockage et du traitement des donnees de ce dernier. b) Le prestataire doit stocker et traiter les donnees du commanditaire au sein de l'Union Europeenne. c) Les operations d'administration et de supervision du service doivent etre realisees depuis le territoire de l'Union Europeenne. d) Le prestataire doit stocker et traiter les donnees techniques (identites des beneficiaires et des administrateurs de l'infrastructure technique, donnees manipulees par le Software Defined Network, journaux de l'infrastructure technique, annuaire, certificats, configuration des acces, etc.) au sein de l'Union Europeenne. e) Le prestataire peut realiser des operations de support aux commanditaires depuis un Etat hors de l'Union Europeenne. Il doit documenter la liste des operations qui peuvent etre effectuees par le support au commanditaire depuis un Etat hors de l'Union Europeenne, et les mecanismes permettant d'en assurer le controle d'acces et la supervision depuis l'Union Europeenne." + } + ], + "Checks": [] + }, + { + "Id": "19.3", + "Description": "Les services cloud qualifies SecNumCloud doivent etre operes depuis le territoire de l'Union europeenne.", + "Name": "Regionalisation", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit s'assurer que les interfaces du service accessibles au commanditaire soient au moins disponibles en langue francaise. b) Le prestataire doit fournir un support de premier niveau en langue francaise." + } + ], + "Checks": [] + }, + { + "Id": "19.4", + "Description": "Le prestataire doit definir les conditions de fin de contrat, incluant les modalites de restitution et de suppression des donnees du commanditaire.", + "Name": "Fin de contrat", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) A la fin du contrat liant le prestataire et le commanditaire, que le contrat soit arrive a son terme ou pour toute autre cause, le prestataire doit assurer un effacement securise de l'integralite des donnees du commanditaire. Cet effacement doit faire l'objet d'un preavis formel au commanditaire de la part du prestataire respectant un delai de vingt et un jours calendaires. L'effacement peut etre realise suivant l'une des methodes suivantes, et ce dans un delai precise dans la convention de service : effacement par reecriture complete de tout support ayant heberge ces donnees ; effacement des cles utilisees pour le chiffrement des espaces de stockage du commanditaire decrit au chapitre 10.1 ; recyclage securise, dans les conditions enoncees au chapitre 11.9. b) A la fin du contrat, le prestataire doit supprimer les donnees techniques relatives au commanditaire (annuaire, certificats, configuration des acces, etc.)." + } + ], + "Checks": [] + }, + { + "Id": "19.5", + "Description": "Le prestataire doit mettre en oeuvre des mesures techniques et organisationnelles appropriees pour garantir la protection des donnees a caractere personnel conformement a la reglementation en vigueur.", + "Name": "Protection des donnees a caractere personnel", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le prestataire doit justifier du respect des principes de protection des donnees pour les traitements de donnees a caractere personnel mis en oeuvre pour son propre compte. Il doit justifier au minimum les points suivants : les finalites des traitements determinees, explicites et legitimes ; la tracabilite des activites de traitement pour son compte et celui de son commanditaire ; le fondement licite des traitements ; l'interdiction du detournement de finalite des traitements ; les donnees utilisees respectent le principe du minimum necessaire et suffisant pour les traitements ; ainsi sont adequates, pertinentes et limitees ; la qualite des donnees utilisees pour les traitements maintenue : donnees exactes et tenues a jour ; les durees de conservation definies et limitees. b) Le prestataire doit justifier, pour les traitements de donnees a caractere personnel mis en oeuvre pour son propre compte, du respect des droits des personnes concernees. Il doit justifier au minimum les points suivants : l'information des usagers via un traitement loyal et transparent ; le recueil du consentement des usagers : expres, demontrable et retirable ; la possibilite pour les usagers d'exercer les droits d'acces, de rectification et d'effacement ; la possibilite pour les usagers d'exercer les droits de limitation du traitement, de portabilite et d'opposition. c) Lorsqu'il agit en qualite de sous-traitant au sens de l'article 28 de [RGPD], le prestataire doit apporter assistance et conseil au commanditaire en l'informant si une instruction de ce dernier constitue une violation des regles de protection des donnees." + } + ], + "Checks": [] + }, + { + "Id": "19.6", + "Description": "Le prestataire doit mettre en oeuvre des mesures de protection vis-a-vis du droit extra-europeen, afin de garantir que les donnees du commanditaire ne puissent etre soumises a des legislations extra-europeennes.", + "Name": "Protection vis-a-vis du droit extra-europeen", + "Attributes": [ + { + "Section": "19. Exigences supplementaires", + "Service": "general", + "Type": "Manual", + "Comment": "a) Le siege statutaire, administration centrale et principal etablissement du prestataire doivent etre etablis au sein d'un Etat membre de l'Union Europeenne. b) Le capital social et les droits de vote dans la societe du prestataire ne doivent pas etre, directement ou indirectement : individuellement detenus a plus de 24% ; et collectivement detenus a plus de 39% ; par des entites tierces possedant leur siege statutaire, administration centrale ou principal etablissement au sein d'un Etat non membre de l'Union europeenne. Ces entites tierces susmentionnees ne peuvent pas individuellement ou collectivement : en vertu d'un contrat ou de clauses statutaires, disposer d'un droit de veto ; en vertu d'un contrat ou de clauses statutaires, designer la majorite des membres des organes d'administration, de direction ou de surveillance du prestataire. c) En cas de recours par le prestataire, dans le cadre des services fournis au commanditaire, aux services d'une societe tierce - y compris un sous-traitant - possedant son siege statutaire, administration centrale ou principal etablissement au sein d'un Etat non membre de l'Union Europeenne ou appartenant ou etant controlee par une societe tierce domiciliee en dehors l'Union Europeenne, cette susdite societe tierce ne doit pas avoir la possibilite technique d'obtenir les donnees operees au travers du service. d) Dans le cadre de l'exigence 19.6.c, toute societe tierce a laquelle le prestataire recourt pour fournir tout ou partie du service rendu au commanditaire, doit garantir au prestataire une autonomie d'exploitation continue dans la fourniture des services d'informatique en nuage qu'il opere ou doit etre qualifie SecNumCloud. e) Le service fourni par le prestataire doit respecter la legislation en vigueur en matiere de droits fondamentaux et les valeurs de l'Union relatives au respect de la dignite humaine, a la liberte, a l'egalite, a la democratie et a l'Etat de droit. f) Le prestataire doit informer formellement le commanditaire, et dans un delai d'un mois, de tout changement juridique, organisationnel ou technique pouvant avoir un impact sur la conformite de la prestation aux exigences du chapitre 19.6." + } + ], + "Checks": [] + } + ] +} From 11a8873155a3818973b946f87c15ec7396effd4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Wed, 18 Mar 2026 19:35:50 +0100 Subject: [PATCH 033/366] feat(ui): add attack path custom query skill for Lighthouse AI (#10323) Co-authored-by: alejandrobailo --- ui/CHANGELOG.md | 3 +- .../definitions/attack-path-custom-query.ts | 311 ++++++++++++++++++ ui/lib/lighthouse/skills/index.ts | 6 + ui/lib/lighthouse/workflow.ts | 1 + 4 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 ui/lib/lighthouse/skills/definitions/attack-path-custom-query.ts diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index f30f068463..4e1a50d510 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -6,7 +6,8 @@ All notable changes to the **Prowler UI** are documented in this file. ### 🚀 Added -- Add skill system to Lighthouse AI [(#10322)](https://github.com/prowler-cloud/prowler/pull/10322) +- Skill system to Lighthouse AI [(#10322)](https://github.com/prowler-cloud/prowler/pull/10322) +- Skill for creating custom queries on Attack Paths [(#10323)](https://github.com/prowler-cloud/prowler/pull/10323) ### 🔄 Changed diff --git a/ui/lib/lighthouse/skills/definitions/attack-path-custom-query.ts b/ui/lib/lighthouse/skills/definitions/attack-path-custom-query.ts new file mode 100644 index 0000000000..7bb2ed08a1 --- /dev/null +++ b/ui/lib/lighthouse/skills/definitions/attack-path-custom-query.ts @@ -0,0 +1,311 @@ +import type { SkillDefinition } from "../types"; + +export const customAttackPathQuerySkill: SkillDefinition = { + metadata: { + id: "attack-path-custom-query", + name: "Attack Paths Custom Query", + description: + "Write an openCypher graph query against Cartography-ingested cloud infrastructure to analyze attack paths, privilege escalation, and network exposure.", + }, + instructions: `# Attack Paths Custom Query Skill + +This skill provides openCypher syntax guidance and Cartography schema knowledge for writing graph queries against Prowler's cloud infrastructure data. + +## Workflow + +Follow these steps when the user asks you to write a custom openCypher query: + +1. **Find a completed scan**: Use \`prowler_app_list_attack_paths_scans\` (filter by \`state=['completed']\`) to find a scan for the user's provider. You need the \`scan_id\` for the next step. + +2. **Fetch the Cartography schema**: Use \`prowler_app_get_attack_paths_cartography_schema\` with the \`scan_id\`. This returns the full schema markdown with all node labels, relationships, and properties for the scan's provider and Cartography version. If this tool fails, use the Cartography Schema Reference section below as a fallback (AWS only). + +3. **Analyze the schema**: From \`schema_content\`, identify the node labels, properties, and relationships relevant to the user's request. Cross-reference with the Common openCypher Patterns section below. + +4. **Write the query**: Compose the openCypher query following all rules in this skill: + - Scope every MATCH to the root account node (see Provider Isolation) + - Use \`$provider_uid\` and \`$provider_id\` parameters (see Query Parameters) + - Include \`ProwlerFinding\` OPTIONAL MATCH (see Include Prowler Findings) + - Use openCypher v9 compatible syntax only (see openCypher Version 9 Compatibility) + +5. **Present the query**: Show the complete query in a \`cypher\` code block with: + - A brief explanation of what the query finds + - The node types and relationships it traverses + - What results to expect + +**Note**: Custom queries cannot be executed through the available tools yet. Present the query to the user for review and manual execution. + +## Query Parameters + +All queries receive these built-in parameters (do NOT hardcode these values): + +| Parameter | Matches property | Used on | Purpose | +|-----------|-----------------|---------|---------| +| \`$provider_uid\` | \`id\` | \`AWSAccount\` | Scopes to a specific cloud account | +| \`$provider_id\` | \`_provider_id\` | Any non-account node | Scopes nodes to the provider context | + +Use \`$provider_uid\` on account root nodes. Use \`$provider_id\` on other nodes that need provider scoping (e.g., \`Internet\`). + +## openCypher Query Guidelines + +### Provider Isolation (CRITICAL) + +Every query MUST chain from the root account node to prevent cross-provider data leakage. +The tenant database contains data from multiple providers. + +\`\`\`cypher +// CORRECT: scoped to the specific account's subgraph +MATCH (aws:AWSAccount {id: $provider_uid})--(role:AWSRole) +WHERE role.name = 'admin' + +// WRONG: matches ALL AWSRoles across all providers +MATCH (role:AWSRole) WHERE role.name = 'admin' +\`\`\` + +Every \`MATCH\` clause must connect to the \`aws\` variable (or another variable already bound to the account's subgraph). An unanchored \`MATCH\` returns nodes from all providers. + +**Exception**: The \`Internet\` sentinel node uses \`OPTIONAL MATCH\` with \`_provider_id\` for scoping instead of chaining from \`aws\`. + +### Include Prowler Findings + +Always include Prowler findings to enrich results with security context: + +\`\`\`cypher +UNWIND nodes(path) as n +OPTIONAL MATCH (n)-[pfr]-(pf:ProwlerFinding {status: 'FAIL', provider_uid: $provider_uid}) + +RETURN path, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr +\`\`\` + +For network exposure queries, also return the internet node and relationship: + +\`\`\`cypher +RETURN path, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr, + internet, can_access +\`\`\` + +### openCypher Version 9 Compatibility + +Queries must use openCypher Version 9 (compatible with both Neo4j and Amazon Neptune). + +| Avoid | Reason | Use instead | +|-------|--------|-------------| +| APOC procedures (\`apoc.*\`) | Neo4j-specific plugin | Real nodes and relationships in the graph | +| Neptune extensions | Not available in Neo4j | Standard openCypher | +| \`reduce()\` function | Not in openCypher spec | \`UNWIND\` + \`collect()\` | +| \`FOREACH\` clause | Not in openCypher spec | \`WITH\` + \`UNWIND\` + \`SET\` | +| Regex operator (\`=~\`) | Not supported in Neptune | \`toLower()\` + exact match, or \`CONTAINS\`/\`STARTS WITH\` | +| \`CALL () { UNION }\` | Complex, hard to maintain | Multi-label OR in WHERE (see patterns below) | + +**Supported with limitations:** +- \`CALL\` subqueries require \`WITH\` clause to import variables + +## Cartography Schema Reference (Quick Reference / Fallback) + +### AWS Node Labels + +| Label | Description | +|-------|-------------| +| \`AWSAccount\` | AWS account root node | +| \`AWSPrincipal\` | IAM principal (user, role, service) | +| \`AWSRole\` | IAM role | +| \`AWSUser\` | IAM user | +| \`AWSPolicy\` | IAM policy | +| \`AWSPolicyStatement\` | Policy statement with effect, action, resource | +| \`EC2Instance\` | EC2 instance | +| \`EC2SecurityGroup\` | Security group | +| \`EC2PrivateIp\` | EC2 private IP (has \`public_ip\`) | +| \`IpPermissionInbound\` | Inbound security group rule | +| \`IpRange\` | IP range (e.g., \`0.0.0.0/0\`) | +| \`NetworkInterface\` | ENI (has \`public_ip\`) | +| \`ElasticIPAddress\` | Elastic IP (has \`public_ip\`) | +| \`S3Bucket\` | S3 bucket | +| \`RDSInstance\` | RDS database instance | +| \`LoadBalancer\` | Classic ELB | +| \`LoadBalancerV2\` | ALB/NLB | +| \`ELBListener\` | Classic ELB listener | +| \`ELBV2Listener\` | ALB/NLB listener | +| \`LaunchTemplate\` | EC2 launch template | +| \`AWSTag\` | Resource tag with key/value properties | + +### Prowler-Specific Labels + +| Label | Description | +|-------|-------------| +| \`ProwlerFinding\` | Prowler finding node with \`status\`, \`provider_uid\`, \`severity\` properties | +| \`Internet\` | Internet sentinel node, scoped by \`_provider_id\` (used in network exposure queries) | + +### Common Relationships + +| Relationship | Description | +|-------------|-------------| +| \`TRUSTS_AWS_PRINCIPAL\` | Role trust relationship | +| \`STS_ASSUMEROLE_ALLOW\` | Can assume role (variable-length for chains) | +| \`CAN_ACCESS\` | Internet-to-resource exposure link | +| \`POLICY\` | Has policy attached | +| \`STATEMENT\` | Policy has statement | + +### Key Properties + +- \`AWSAccount\`: \`id\` (account ID used with \`$provider_uid\`) +- \`AWSPolicyStatement\`: \`effect\` ('Allow'/'Deny'), \`action\` (list), \`resource\` (list) +- \`EC2Instance\`: \`exposed_internet\` (boolean), \`publicipaddress\` +- \`EC2PrivateIp\`: \`public_ip\` +- \`NetworkInterface\`: \`public_ip\` +- \`ElasticIPAddress\`: \`public_ip\` +- \`EC2SecurityGroup\`: \`name\`, \`id\` +- \`IpPermissionInbound\`: \`toport\`, \`fromport\`, \`protocol\` +- \`S3Bucket\`: \`name\`, \`anonymous_access\` (boolean) +- \`RDSInstance\`: \`storage_encrypted\` (boolean) +- \`ProwlerFinding\`: \`status\` ('FAIL'/'PASS'/'MANUAL'), \`severity\`, \`provider_uid\` +- \`Internet\`: \`_provider_id\` (provider UUID used with \`$provider_id\`) + +## Common openCypher Patterns + +### Match Account and Principal + +\`\`\`cypher +MATCH path_principal = (aws:AWSAccount {id: $provider_uid})--(principal:AWSPrincipal)--(policy:AWSPolicy)--(stmt:AWSPolicyStatement) +\`\`\` + +### Check IAM Action Permissions + +\`\`\`cypher +WHERE stmt.effect = 'Allow' + AND any(action IN stmt.action WHERE + toLower(action) = 'iam:passrole' + OR toLower(action) = 'iam:*' + OR action = '*' + ) +\`\`\` + +### Find Roles Trusting a Service + +\`\`\`cypher +MATCH path_target = (aws)--(target_role:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]->(:AWSPrincipal {arn: 'ec2.amazonaws.com'}) +\`\`\` + +### Check Resource Scope + +\`\`\`cypher +WHERE any(resource IN stmt.resource WHERE + resource = '*' + OR target_role.arn CONTAINS resource + OR resource CONTAINS target_role.name +) +\`\`\` + +### Match Internet Sentinel Node + +Used in network exposure queries. The Internet node is a real graph node, scoped by \`_provider_id\`: + +\`\`\`cypher +OPTIONAL MATCH (internet:Internet {_provider_id: $provider_id}) +\`\`\` + +### Link Internet to Exposed Resource + +The \`CAN_ACCESS\` relationship links the Internet node to exposed resources: + +\`\`\`cypher +OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(resource) +\`\`\` + +### Multi-label OR (match multiple resource types) + +When a query needs to match different resource types in the same position, use label checks in WHERE: + +\`\`\`cypher +MATCH path = (aws:AWSAccount {id: $provider_uid})-[r]-(x)-[q]-(y) +WHERE (x:EC2PrivateIp AND x.public_ip = $ip) + OR (x:EC2Instance AND x.publicipaddress = $ip) + OR (x:NetworkInterface AND x.public_ip = $ip) + OR (x:ElasticIPAddress AND x.public_ip = $ip) +\`\`\` + +## Example Query Patterns + +### Resource Inventory + +\`\`\`cypher +MATCH path = (aws:AWSAccount {id: $provider_uid})--(rds:RDSInstance) + +UNWIND nodes(path) as n +OPTIONAL MATCH (n)-[pfr]-(pf:ProwlerFinding {status: 'FAIL', provider_uid: $provider_uid}) + +RETURN path, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr +\`\`\` + +### Network Exposure + +\`\`\`cypher +// Match the Internet sentinel node +OPTIONAL MATCH (internet:Internet {_provider_id: $provider_id}) + +// Match exposed resources (MUST chain from aws) +MATCH path = (aws:AWSAccount {id: $provider_uid})--(resource:EC2Instance) +WHERE resource.exposed_internet = true + +// Link Internet to resource +OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(resource) + +UNWIND nodes(path) as n +OPTIONAL MATCH (n)-[pfr]-(pf:ProwlerFinding {status: 'FAIL', provider_uid: $provider_uid}) + +RETURN path, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr, + internet, can_access +\`\`\` + +### IAM Permission Check + +\`\`\`cypher +MATCH path_principal = (aws:AWSAccount {id: $provider_uid})--(principal:AWSPrincipal)--(policy:AWSPolicy)--(stmt:AWSPolicyStatement) +WHERE stmt.effect = 'Allow' + AND any(action IN stmt.action WHERE + toLower(action) = 'iam:passrole' + OR toLower(action) = 'iam:*' + OR action = '*' + ) + +UNWIND nodes(path_principal) as n +OPTIONAL MATCH (n)-[pfr]-(pf:ProwlerFinding {status: 'FAIL', provider_uid: $provider_uid}) + +RETURN path_principal, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr +\`\`\` + +### Privilege Escalation (Role Assumption Chain) + +\`\`\`cypher +// Find principals with iam:PassRole +MATCH path_principal = (aws:AWSAccount {id: $provider_uid})--(principal:AWSPrincipal)--(policy:AWSPolicy)--(stmt:AWSPolicyStatement) +WHERE stmt.effect = 'Allow' + AND any(action IN stmt.action WHERE + toLower(action) = 'iam:passrole' + OR toLower(action) = 'iam:*' + OR action = '*' + ) + +// Find target roles trusting a service +MATCH path_target = (aws)--(target_role:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]->(:AWSPrincipal {arn: 'ec2.amazonaws.com'}) +WHERE any(resource IN stmt.resource WHERE + resource = '*' + OR target_role.arn CONTAINS resource + OR resource CONTAINS target_role.name +) + +UNWIND nodes(path_principal) + nodes(path_target) as n +OPTIONAL MATCH (n)-[pfr]-(pf:ProwlerFinding {status: 'FAIL', provider_uid: $provider_uid}) + +RETURN path_principal, path_target, + collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr +\`\`\` + +## Best Practices + +1. **Always scope by provider**: Use \`{id: $provider_uid}\` on \`AWSAccount\` nodes. Use \`{_provider_id: $provider_id}\` on non-account nodes that need provider scoping (e.g., \`Internet\`). +2. **Chain all MATCHes from the root account node**: Every \`MATCH\` must connect to the \`aws\` variable. The \`Internet\` node is the only exception (uses \`OPTIONAL MATCH\` with \`_provider_id\`). +3. **Include Prowler findings**: Always add the \`OPTIONAL MATCH\` for \`ProwlerFinding\` nodes. +4. **Return distinct findings**: Use \`collect(DISTINCT pf)\` to avoid duplicates. +5. **Comment the query purpose**: Add inline comments explaining each \`MATCH\` clause. +6. **Use alternatives for unsupported features**: Replace \`=~\` with \`toLower()\` + exact match or \`CONTAINS\`/\`STARTS WITH\`. Replace \`reduce()\` with \`UNWIND\` + \`collect()\`. +`, +}; diff --git a/ui/lib/lighthouse/skills/index.ts b/ui/lib/lighthouse/skills/index.ts index 2ec8d586d7..b0b3156466 100644 --- a/ui/lib/lighthouse/skills/index.ts +++ b/ui/lib/lighthouse/skills/index.ts @@ -1,3 +1,9 @@ +import { customAttackPathQuerySkill } from "./definitions/attack-path-custom-query"; +import { registerSkill } from "./registry"; + +// Explicit registration — tree-shake-proof +registerSkill(customAttackPathQuerySkill); + // Re-export registry functions and types export { getAllSkillMetadata, diff --git a/ui/lib/lighthouse/workflow.ts b/ui/lib/lighthouse/workflow.ts index c94adaf8db..e5811e976a 100644 --- a/ui/lib/lighthouse/workflow.ts +++ b/ui/lib/lighthouse/workflow.ts @@ -87,6 +87,7 @@ const ALLOWED_TOOLS = new Set([ "prowler_app_list_attack_paths_queries", "prowler_app_list_attack_paths_scans", "prowler_app_run_attack_paths_query", + "prowler_app_get_attack_paths_cartography_schema", ]); /** From 0f2fdcfb3f27c72b499b75c2e12646efdbf19012 Mon Sep 17 00:00:00 2001 From: rchotacode <32524742+rchotacode@users.noreply.github.com> Date: Thu, 19 Mar 2026 05:41:58 -0400 Subject: [PATCH 034/366] chore(oraclecloud): Add Oracle Defense Cloud Support (#10376) Co-authored-by: Ronan Chota Co-authored-by: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Co-authored-by: Hugo P.Brito --- .github/workflows/sdk-refresh-oci-regions.yml | 3 ++- prowler/CHANGELOG.md | 1 + prowler/providers/oraclecloud/config.py | 13 ++++++++++++- util/update_oci_regions.py | 4 ++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdk-refresh-oci-regions.yml b/.github/workflows/sdk-refresh-oci-regions.yml index e9a3665911..c4c2ad1a5a 100644 --- a/.github/workflows/sdk-refresh-oci-regions.yml +++ b/.github/workflows/sdk-refresh-oci-regions.yml @@ -72,12 +72,13 @@ jobs: This PR updates the `OCI_COMMERCIAL_REGIONS` dictionary in `prowler/providers/oraclecloud/config.py` with the latest regions fetched from the OCI Identity API (`list_regions()`). - Government regions (`OCI_GOVERNMENT_REGIONS`) are preserved unchanged + - DOD regions (`OCI_US_DOD_REGIONS`) are preserved unchanged - Region display names are mapped from Oracle's official documentation ### Checklist - [x] This is an automated update from OCI official sources - - [x] Government regions (us-langley-1, us-luke-1) preserved + - [x] Government regions (us-langley-1, us-luke-1) and DOD regions (us-gov-ashburn-1, us-gov-phoenix-1, us-gov-chicago-1) are preserved - [x] No manual review of region data required ### License diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 9f0b773bd5..351fea5b78 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -25,6 +25,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update M365 Teams service metadata to new format [(#9685)](https://github.com/prowler-cloud/prowler/pull/9685) - Update M365 Entra ID service metadata to new format [(#9682)](https://github.com/prowler-cloud/prowler/pull/9682) - Update ResourceType and Categories for Azure Entra ID service metadata [(#10334)](https://github.com/prowler-cloud/prowler/pull/10334) +- Update OCI Regions to include US DoD regions [(#10375)](https://github.com/prowler-cloud/prowler/pull/10376) ### 🔐 Security diff --git a/prowler/providers/oraclecloud/config.py b/prowler/providers/oraclecloud/config.py index 9e428672d5..0c20c44f57 100644 --- a/prowler/providers/oraclecloud/config.py +++ b/prowler/providers/oraclecloud/config.py @@ -65,5 +65,16 @@ OCI_GOVERNMENT_REGIONS = { "us-luke-1": "US Gov East", } +# OCI Defense Regions +OCI_US_DOD_REGIONS = { + "us-gov-ashburn-1": "US DoD East (Ashburn)", + "us-gov-chicago-1": "US DoD North (Chicago)", + "us-gov-phoenix-1": "US DoD West (Phoenix)", +} + # All OCI Regions -OCI_REGIONS = {**OCI_COMMERCIAL_REGIONS, **OCI_GOVERNMENT_REGIONS} +OCI_REGIONS = { + **OCI_COMMERCIAL_REGIONS, + **OCI_GOVERNMENT_REGIONS, + **OCI_US_DOD_REGIONS, +} diff --git a/util/update_oci_regions.py b/util/update_oci_regions.py index 1d778b777a..cb012bec58 100644 --- a/util/update_oci_regions.py +++ b/util/update_oci_regions.py @@ -156,6 +156,10 @@ def update_config_file(regions, config_file_path): raise Exception( "Validation failed: OCI_GOVERNMENT_REGIONS section missing after update. Aborting to prevent data loss." ) + if "OCI_US_DOD_REGIONS" not in updated_content: + raise Exception( + "Validation failed: OCI_US_DOD_REGIONS section missing after update. Aborting to prevent data loss." + ) # Verify the replacement was successful if updated_content == config_content: From 5c4ee0bc484eb41c1ff49f2b13bbab2785dfb949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Thu, 19 Mar 2026 10:47:46 +0100 Subject: [PATCH 035/366] chore(mcp): bump MCP server version to 0.5.0 (#10383) --- mcp_server/CHANGELOG.md | 2 +- mcp_server/prowler_mcp_server/__init__.py | 2 +- mcp_server/pyproject.toml | 2 +- mcp_server/uv.lock | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mcp_server/CHANGELOG.md b/mcp_server/CHANGELOG.md index 0b2589fb7c..a6e83a7569 100644 --- a/mcp_server/CHANGELOG.md +++ b/mcp_server/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the **Prowler MCP Server** are documented in this file. -## [0.5.0] (Prowler v5.20.0 UNRELEASED) +## [0.5.0] (Prowler v5.20.0) ### 🚀 Added diff --git a/mcp_server/prowler_mcp_server/__init__.py b/mcp_server/prowler_mcp_server/__init__.py index 5fcfa74c55..fe7af2dcea 100644 --- a/mcp_server/prowler_mcp_server/__init__.py +++ b/mcp_server/prowler_mcp_server/__init__.py @@ -5,7 +5,7 @@ This package provides MCP tools for accessing: - Prowler Hub: All security artifacts (detections, remediations and frameworks) supported by Prowler """ -__version__ = "0.4.0" +__version__ = "0.5.0" __author__ = "Prowler Team" __email__ = "engineering@prowler.com" diff --git a/mcp_server/pyproject.toml b/mcp_server/pyproject.toml index 2cfb14e671..4ea4a9859e 100644 --- a/mcp_server/pyproject.toml +++ b/mcp_server/pyproject.toml @@ -11,7 +11,7 @@ description = "MCP server for Prowler ecosystem" name = "prowler-mcp" readme = "README.md" requires-python = ">=3.12" -version = "0.4.0" +version = "0.5.0" [project.scripts] prowler-mcp = "prowler_mcp_server.main:main" diff --git a/mcp_server/uv.lock b/mcp_server/uv.lock index f98c2eaa57..31e4dd84d6 100644 --- a/mcp_server/uv.lock +++ b/mcp_server/uv.lock @@ -717,7 +717,7 @@ wheels = [ [[package]] name = "prowler-mcp" -version = "0.3.0" +version = "0.5.0" source = { editable = "." } dependencies = [ { name = "fastmcp" }, From 8317eff67b0096cefa047e55124719022c17eb06 Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Thu, 19 Mar 2026 10:09:51 +0000 Subject: [PATCH 036/366] chore(changelog): prepare for v5.21.0 (#10380) --- api/CHANGELOG.md | 4 ++-- mcp_server/CHANGELOG.md | 2 +- prowler/CHANGELOG.md | 12 ++++++------ ui/CHANGELOG.md | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 15702e342d..7cd4fce52f 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -2,16 +2,16 @@ All notable changes to the **Prowler API** are documented in this file. -## [1.22.0] (Prowler UNRELEASED) +## [1.22.0] (Prowler v5.21.0) ### 🚀 Added - `CORS_ALLOWED_ORIGINS` configurable via environment variable [(#10355)](https://github.com/prowler-cloud/prowler/pull/10355) +- Attack Paths: Tenant and provider related labels to the nodes so they can be easily filtered on custom queries [(#10308)](https://github.com/prowler-cloud/prowler/pull/10308) ### 🔄 Changed - Attack Paths: Complete migration to private graph labels and properties, removing deprecated dual-write support [(#10268)](https://github.com/prowler-cloud/prowler/pull/10268) -- Attack Paths: Added tenant and provider related labels to the nodes so they can be easily filtered on custom queries [(#10308)](https://github.com/prowler-cloud/prowler/pull/10308) - Attack Paths: Reduce sync and findings memory usage with smaller batches, cursor iteration, and sequential sessions [(#10359)](https://github.com/prowler-cloud/prowler/pull/10359) ### 🐞 Fixed diff --git a/mcp_server/CHANGELOG.md b/mcp_server/CHANGELOG.md index a6e83a7569..0a91ce06a7 100644 --- a/mcp_server/CHANGELOG.md +++ b/mcp_server/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the **Prowler MCP Server** are documented in this file. -## [0.5.0] (Prowler v5.20.0) +## [0.5.0] (Prowler v5.21.0) ### 🚀 Added diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 351fea5b78..9c57c2bece 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the **Prowler SDK** are documented in this file. -## [5.21.0] (Prowler UNRELEASED) +## [5.21.0] (Prowler v5.21.0) ### 🚀 Added @@ -10,7 +10,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - `entra_conditional_access_policy_device_code_flow_blocked` check for M365 provider [(#10218)](https://github.com/prowler-cloud/prowler/pull/10218) - RBI compliance for the Azure provider [(#10339)](https://github.com/prowler-cloud/prowler/pull/10339) -`entra_conditional_access_policy_require_mfa_for_admin_portals` check for Azure provider and update CIS compliance [(#10330)](https://github.com/prowler-cloud/prowler/pull/10330) -- CheckMetadata Pydantic validators [(#8584)](https://github.com/prowler-cloud/prowler/pull/8583) +- CheckMetadata Pydantic validators [(#8583)](https://github.com/prowler-cloud/prowler/pull/8583) - `organization_repository_deletion_limited` check for GitHub provider [(#10185)](https://github.com/prowler-cloud/prowler/pull/10185) - SecNumCloud 3.2 for the GCP provider [(#10364)](https://github.com/prowler-cloud/prowler/pull/10364) - SecNumCloud 3.2 for the Azure provider [(#10358)](https://github.com/prowler-cloud/prowler/pull/10358) @@ -27,16 +27,16 @@ All notable changes to the **Prowler SDK** are documented in this file. - Update ResourceType and Categories for Azure Entra ID service metadata [(#10334)](https://github.com/prowler-cloud/prowler/pull/10334) - Update OCI Regions to include US DoD regions [(#10375)](https://github.com/prowler-cloud/prowler/pull/10376) -### 🔐 Security - -- Bump `multipart` to 1.3.1 to fix [GHSA-p2m9-wcp5-6qw3](https://github.com/defnull/multipart/security/advisories/GHSA-p2m9-wcp5-6qw3) [(#10331)](https://github.com/prowler-cloud/prowler/pull/10331) - ### 🐞 Fixed - Route53 dangling IP check false positive when using `--region` flag [(#9952)](https://github.com/prowler-cloud/prowler/pull/9952) - RBI compliance framework support on Prowler Dashboard for the Azure provider [(#10360)](https://github.com/prowler-cloud/prowler/pull/10360) - CheckMetadata strict validators rejecting valid external tool provider data (image, iac, llm) [(#10363)](https://github.com/prowler-cloud/prowler/pull/10363) +### 🔐 Security + +- Bump `multipart` to 1.3.1 to fix [GHSA-p2m9-wcp5-6qw3](https://github.com/defnull/multipart/security/advisories/GHSA-p2m9-wcp5-6qw3) [(#10331)](https://github.com/prowler-cloud/prowler/pull/10331) + --- ## [5.20.0] (Prowler v5.20.0) diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 4e1a50d510..2c17bcc5d6 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the **Prowler UI** are documented in this file. -## [1.21.0] (Prowler v5.21.0 UNRELEASED) +## [1.21.0] (Prowler v5.21.0) ### 🚀 Added @@ -13,8 +13,8 @@ All notable changes to the **Prowler UI** are documented in this file. - Google Workspace provider support [(#10333)](https://github.com/prowler-cloud/prowler/pull/10333) - Image (Container Registry) provider support in UI: badge icon, credentials form, and provider-type filtering [(#10167)](https://github.com/prowler-cloud/prowler/pull/10167) -- Organization and organizational unit row actions (Edit Name, Update Credentials, Test Connections, Delete) in providers table dropdown [(#10317)](https://github.com/prowler-cloud/prowler/pull/10317) - Events tab in Findings and Resource detail cards showing an AWS CloudTrail timeline with expandable event rows, actor info, request/response JSON payloads, and error details [(#10320)](https://github.com/prowler-cloud/prowler/pull/10320) +- AWS Organization and organizational unit row actions (Edit Name, Update Credentials, Test Connections, Delete) in providers table dropdown [(#10317)](https://github.com/prowler-cloud/prowler/pull/10317) --- From aecc234f7811a313ebd990e2f207bcabbac169a4 Mon Sep 17 00:00:00 2001 From: Prowler Bot Date: Thu, 19 Mar 2026 13:40:22 +0100 Subject: [PATCH 037/366] chore(release): Bump version to v5.22.0 (#10389) Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com> --- prowler/config/config.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prowler/config/config.py b/prowler/config/config.py index 1b7eb87ff2..628d171b3b 100644 --- a/prowler/config/config.py +++ b/prowler/config/config.py @@ -38,7 +38,7 @@ class _MutableTimestamp: timestamp = _MutableTimestamp(datetime.today()) timestamp_utc = _MutableTimestamp(datetime.now(timezone.utc)) -prowler_version = "5.21.0" +prowler_version = "5.22.0" html_logo_url = "https://github.com/prowler-cloud/prowler/" square_logo_img = "https://raw.githubusercontent.com/prowler-cloud/prowler/dc7d2d5aeb92fdf12e8604f42ef6472cd3e8e889/docs/img/prowler-logo-black.png" aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png" diff --git a/pyproject.toml b/pyproject.toml index d24f1692e8..4b211f7fbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,7 +94,7 @@ maintainers = [{name = "Prowler Engineering", email = "engineering@prowler.com"} name = "prowler" readme = "README.md" requires-python = ">3.9.1,<3.13" -version = "5.21.0" +version = "5.22.0" [project.scripts] prowler = "prowler.__main__:prowler" From 997f9bf64a2a22dde71ec939532eb6f607b5ac96 Mon Sep 17 00:00:00 2001 From: Prowler Bot Date: Thu, 19 Mar 2026 13:40:33 +0100 Subject: [PATCH 038/366] docs: Update version to v5.21.0 (#10391) Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com> --- docs/getting-started/installation/prowler-app.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/getting-started/installation/prowler-app.mdx b/docs/getting-started/installation/prowler-app.mdx index 793fc6450b..e9a88f708e 100644 --- a/docs/getting-started/installation/prowler-app.mdx +++ b/docs/getting-started/installation/prowler-app.mdx @@ -121,8 +121,8 @@ To update the environment file: Edit the `.env` file and change version values: ```env -PROWLER_UI_VERSION="5.20.0" -PROWLER_API_VERSION="5.20.0" +PROWLER_UI_VERSION="5.21.0" +PROWLER_API_VERSION="5.21.0" ``` From 4638b39ed47d49bb1520daa6b43f4116d5f5e6e9 Mon Sep 17 00:00:00 2001 From: Prowler Bot Date: Thu, 19 Mar 2026 13:42:46 +0100 Subject: [PATCH 039/366] chore(api): Bump version to v1.23.0 (#10393) Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com> --- api/pyproject.toml | 2 +- api/src/backend/api/specs/v1.yaml | 2 +- api/src/backend/api/v1/views.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index 2dcb958474..bc9c742a6c 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -49,7 +49,7 @@ name = "prowler-api" package-mode = false # Needed for the SDK compatibility requires-python = ">=3.11,<3.13" -version = "1.22.0" +version = "1.23.0" [project.scripts] celery = "src.backend.config.settings.celery" diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml index be17679f42..8f8ae0f729 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.22.0 + version: 1.23.0 description: |- Prowler API specification. diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index 8c0518ce89..7878b51351 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -408,7 +408,7 @@ class SchemaView(SpectacularAPIView): def get(self, request, *args, **kwargs): spectacular_settings.TITLE = "Prowler API" - spectacular_settings.VERSION = "1.22.0" + spectacular_settings.VERSION = "1.23.0" spectacular_settings.DESCRIPTION = ( "Prowler API specification.\n\nThis file is auto-generated." ) From ab266080d07711145a86fdb23b831151402fefb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Pe=C3=B1a?= Date: Thu, 19 Mar 2026 13:54:50 +0100 Subject: [PATCH 040/366] perf(api): add trigram indexes for finding groups (#10378) --- ...inding_group_daily_summary_trgm_indexes.py | 31 +++++++++++++++++++ api/src/backend/api/models.py | 9 ++++++ 2 files changed, 40 insertions(+) create mode 100644 api/src/backend/api/migrations/0085_finding_group_daily_summary_trgm_indexes.py diff --git a/api/src/backend/api/migrations/0085_finding_group_daily_summary_trgm_indexes.py b/api/src/backend/api/migrations/0085_finding_group_daily_summary_trgm_indexes.py new file mode 100644 index 0000000000..f6511184cd --- /dev/null +++ b/api/src/backend/api/migrations/0085_finding_group_daily_summary_trgm_indexes.py @@ -0,0 +1,31 @@ +# Generated by Django 5.1.15 on 2026-03-18 + +from django.contrib.postgres.indexes import GinIndex, OpClass +from django.contrib.postgres.operations import AddIndexConcurrently +from django.db import migrations +from django.db.models.functions import Upper + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ("api", "0084_googleworkspace_provider"), + ] + + operations = [ + AddIndexConcurrently( + model_name="findinggroupdailysummary", + index=GinIndex( + OpClass(Upper("check_id"), name="gin_trgm_ops"), + name="fgds_check_id_trgm_idx", + ), + ), + AddIndexConcurrently( + model_name="findinggroupdailysummary", + index=GinIndex( + OpClass(Upper("check_title"), name="gin_trgm_ops"), + name="fgds_check_title_trgm_idx", + ), + ), + ] diff --git a/api/src/backend/api/models.py b/api/src/backend/api/models.py index e736ef403a..7b7ccb9061 100644 --- a/api/src/backend/api/models.py +++ b/api/src/backend/api/models.py @@ -1783,6 +1783,15 @@ class FindingGroupDailySummary(RowLevelSecurityProtectedModel): fields=["tenant_id", "provider", "inserted_at"], name="fgds_tenant_prov_ins_idx", ), + # Trigram indexes for case-insensitive search + GinIndex( + OpClass(Upper("check_id"), name="gin_trgm_ops"), + name="fgds_check_id_trgm_idx", + ), + GinIndex( + OpClass(Upper("check_title"), name="gin_trgm_ops"), + name="fgds_check_title_trgm_idx", + ), ] class JSONAPIMeta: From cece2cb87eb2d9817adac655e79f87d6e2f70737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Arroba?= <19954079+cesararroba@users.noreply.github.com> Date: Thu, 19 Mar 2026 14:32:38 +0100 Subject: [PATCH 041/366] chore: pin Prowler version to lastest master commit on push (#10384) --- .github/workflows/api-container-build-push.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/api-container-build-push.yml b/.github/workflows/api-container-build-push.yml index 69edd64e64..e013329e32 100644 --- a/.github/workflows/api-container-build-push.yml +++ b/.github/workflows/api-container-build-push.yml @@ -99,6 +99,12 @@ jobs: with: persist-credentials: false + - name: Pin prowler SDK to latest master commit + if: github.event_name == 'push' + run: | + LATEST_SHA=$(git ls-remote https://github.com/prowler-cloud/prowler.git refs/heads/master | cut -f1) + sed -i "s|prowler-cloud/prowler.git@master|prowler-cloud/prowler.git@${LATEST_SHA}|" api/pyproject.toml + - name: Login to DockerHub uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: From 2fe92cfce32752a3d8a888ef1478ca6334704306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Pe=C3=B1a?= Date: Thu, 19 Mar 2026 16:48:26 +0100 Subject: [PATCH 042/366] feat(api): add check title search for finding groups (#10377) --- api/CHANGELOG.md | 1 + api/src/backend/api/filters.py | 6 ++++ .../tests/integration/test_authentication.py | 9 +++--- api/src/backend/api/tests/test_apps.py | 30 +++++++++++-------- api/src/backend/api/tests/test_views.py | 16 ++++++++++ api/src/backend/api/v1/views.py | 25 +++++++++++++--- 6 files changed, 65 insertions(+), 22 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 7cd4fce52f..dff4da4899 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the **Prowler API** are documented in this file. ### 🚀 Added - `CORS_ALLOWED_ORIGINS` configurable via environment variable [(#10355)](https://github.com/prowler-cloud/prowler/pull/10355) +- Finding groups support `check_title` substring filtering [(#10377)](https://github.com/prowler-cloud/prowler/pull/10377) - Attack Paths: Tenant and provider related labels to the nodes so they can be easily filtered on custom queries [(#10308)](https://github.com/prowler-cloud/prowler/pull/10308) ### 🔄 Changed diff --git a/api/src/backend/api/filters.py b/api/src/backend/api/filters.py index a64cc0ea13..d8b803d475 100644 --- a/api/src/backend/api/filters.py +++ b/api/src/backend/api/filters.py @@ -926,6 +926,9 @@ class FindingGroupSummaryFilter(FilterSet): check_id = CharFilter(field_name="check_id", lookup_expr="exact") check_id__in = CharInFilter(field_name="check_id", lookup_expr="in") check_id__icontains = CharFilter(field_name="check_id", lookup_expr="icontains") + check_title__icontains = CharFilter( + field_name="check_title", lookup_expr="icontains" + ) # Provider filters provider_id = UUIDFilter(field_name="provider_id", lookup_expr="exact") @@ -1025,6 +1028,9 @@ class LatestFindingGroupSummaryFilter(FilterSet): check_id = CharFilter(field_name="check_id", lookup_expr="exact") check_id__in = CharInFilter(field_name="check_id", lookup_expr="in") check_id__icontains = CharFilter(field_name="check_id", lookup_expr="icontains") + check_title__icontains = CharFilter( + field_name="check_title", lookup_expr="icontains" + ) # Provider filters provider_id = UUIDFilter(field_name="provider_id", lookup_expr="exact") diff --git a/api/src/backend/api/tests/integration/test_authentication.py b/api/src/backend/api/tests/integration/test_authentication.py index 2abf725124..26d5b8e108 100644 --- a/api/src/backend/api/tests/integration/test_authentication.py +++ b/api/src/backend/api/tests/integration/test_authentication.py @@ -301,7 +301,7 @@ class TestTokenSwitchTenant: assert invalid_tenant_response.status_code == 400 assert invalid_tenant_response.json()["errors"][0]["code"] == "invalid" assert invalid_tenant_response.json()["errors"][0]["detail"] == ( - "Tenant does not exist or user is not a " "member." + "Tenant does not exist or user is not a member." ) @@ -912,10 +912,9 @@ class TestAPIKeyLifecycle: auth_response = client.get(reverse("provider-list"), headers=api_key_headers) # Must return 401 Unauthorized, not 500 Internal Server Error - assert auth_response.status_code == 401, ( - f"Expected 401 but got {auth_response.status_code}: " - f"{auth_response.json()}" - ) + assert ( + auth_response.status_code == 401 + ), f"Expected 401 but got {auth_response.status_code}: {auth_response.json()}" # Verify error message is present response_json = auth_response.json() diff --git a/api/src/backend/api/tests/test_apps.py b/api/src/backend/api/tests/test_apps.py index 712bc33882..5889b4e2cb 100644 --- a/api/src/backend/api/tests/test_apps.py +++ b/api/src/backend/api/tests/test_apps.py @@ -10,11 +10,11 @@ from django.conf import settings import api import api.apps as api_apps_module from api.apps import ( - ApiConfig, PRIVATE_KEY_FILE, PUBLIC_KEY_FILE, SIGNING_KEY_ENV, VERIFYING_KEY_ENV, + ApiConfig, ) @@ -187,9 +187,10 @@ def test_ready_initializes_driver_for_api_process(monkeypatch): _set_argv(monkeypatch, ["gunicorn"]) _set_testing(monkeypatch, False) - with patch.object(ApiConfig, "_ensure_crypto_keys", return_value=None), patch( - "api.attack_paths.database.init_driver" - ) as init_driver: + with ( + patch.object(ApiConfig, "_ensure_crypto_keys", return_value=None), + patch("api.attack_paths.database.init_driver") as init_driver, + ): config.ready() init_driver.assert_called_once() @@ -200,9 +201,10 @@ def test_ready_skips_driver_for_celery(monkeypatch): _set_argv(monkeypatch, ["celery", "-A", "api"]) _set_testing(monkeypatch, False) - with patch.object(ApiConfig, "_ensure_crypto_keys", return_value=None), patch( - "api.attack_paths.database.init_driver" - ) as init_driver: + with ( + patch.object(ApiConfig, "_ensure_crypto_keys", return_value=None), + patch("api.attack_paths.database.init_driver") as init_driver, + ): config.ready() init_driver.assert_not_called() @@ -213,9 +215,10 @@ def test_ready_skips_driver_for_manage_py_skip_command(monkeypatch): _set_argv(monkeypatch, ["manage.py", "migrate"]) _set_testing(monkeypatch, False) - with patch.object(ApiConfig, "_ensure_crypto_keys", return_value=None), patch( - "api.attack_paths.database.init_driver" - ) as init_driver: + with ( + patch.object(ApiConfig, "_ensure_crypto_keys", return_value=None), + patch("api.attack_paths.database.init_driver") as init_driver, + ): config.ready() init_driver.assert_not_called() @@ -226,9 +229,10 @@ def test_ready_skips_driver_when_testing(monkeypatch): _set_argv(monkeypatch, ["gunicorn"]) _set_testing(monkeypatch, True) - with patch.object(ApiConfig, "_ensure_crypto_keys", return_value=None), patch( - "api.attack_paths.database.init_driver" - ) as init_driver: + with ( + patch.object(ApiConfig, "_ensure_crypto_keys", return_value=None), + patch("api.attack_paths.database.init_driver") as init_driver, + ): config.ready() init_driver.assert_not_called() diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index d857b2bc07..7dd14bf420 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -15526,6 +15526,22 @@ class TestFindingGroupViewSet: assert len(response.json()["data"]) == 1 assert "bucket" in response.json()["data"][0]["id"].lower() + def test_finding_groups_check_title_icontains( + self, authenticated_client, finding_groups_fixture + ): + """Test searching check titles with icontains.""" + response = authenticated_client.get( + reverse("finding-group-list"), + { + "filter[inserted_at]": TODAY, + "filter[check_title.icontains]": "public access", + }, + ) + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + assert len(data) == 1 + assert data[0]["id"] == "s3_bucket_public_access" + def test_resources_not_found(self, authenticated_client): """Test 404 returned for nonexistent check_id.""" response = authenticated_client.get( diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index 7878b51351..7c2de4a41c 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -4,7 +4,6 @@ import json import logging import os import time - from collections import defaultdict from copy import deepcopy from datetime import datetime, timedelta, timezone @@ -12,7 +11,6 @@ from decimal import ROUND_HALF_UP, Decimal, InvalidOperation from urllib.parse import urljoin import sentry_sdk - from allauth.socialaccount.models import SocialAccount, SocialApp from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter @@ -76,6 +74,7 @@ from rest_framework.exceptions import ( ) from rest_framework.generics import GenericAPIView, get_object_or_404 from rest_framework.permissions import SAFE_METHODS +from rest_framework_json_api import filters as jsonapi_filters from rest_framework_json_api.views import RelationshipView, Response from rest_framework_simplejwt.exceptions import InvalidToken, TokenError from tasks.beat import schedule_provider_scan @@ -100,7 +99,6 @@ from api.attack_paths import database as graph_database from api.attack_paths import get_queries_for_provider, get_query_by_id from api.attack_paths import views_helpers as attack_paths_views_helpers from api.base_views import BaseRLSViewSet, BaseTenantViewset, BaseUserViewset -from api.renderers import APIJSONRenderer, PlainTextRenderer from api.compliance import ( PROWLER_COMPLIANCE_OVERVIEW_TEMPLATE, get_compliance_frameworks, @@ -199,6 +197,7 @@ from api.models import ( ) from api.pagination import ComplianceOverviewPagination from api.rbac.permissions import Permissions, get_providers, get_role +from api.renderers import APIJSONRenderer, PlainTextRenderer from api.rls import Tenant from api.utils import ( CustomOAuth2Client, @@ -6777,13 +6776,29 @@ class FindingGroupViewSet(BaseRLSViewSet): queryset = FindingGroupDailySummary.objects.all() serializer_class = FindingGroupSerializer filterset_class = FindingGroupSummaryFilter + filter_backends = [ + jsonapi_filters.QueryParameterValidationFilter, + jsonapi_filters.OrderingFilter, + CustomDjangoFilterBackend, + ] http_method_names = ["get"] required_permissions = [] def get_filterset_class(self): - """Return appropriate filter based on action.""" + """Return the filterset class used for schema generation and the list action. + + Note: The resources and latest_resources actions do not use this method + at runtime. They manually instantiate FindingGroupFilter / + LatestFindingGroupFilter against a Finding queryset (see + _get_finding_queryset). The class returned here for those actions only + affects the OpenAPI schema generated by drf-spectacular. + """ if self.action == "latest": return LatestFindingGroupSummaryFilter + if self.action == "resources": + return FindingGroupFilter + if self.action == "latest_resources": + return LatestFindingGroupFilter return FindingGroupSummaryFilter def get_queryset(self): @@ -7237,6 +7252,7 @@ class FindingGroupViewSet(BaseRLSViewSet): and timing information including how long they have been failing. """, tags=["Finding Groups"], + filters=True, ) @action(detail=True, methods=["get"], url_path="resources") def resources(self, request, pk=None): @@ -7311,6 +7327,7 @@ class FindingGroupViewSet(BaseRLSViewSet): and timing information. No date filters required. """, tags=["Finding Groups"], + filters=True, ) @action( detail=False, From 872e6e239cd791fd2a8b000cdbcbbcd701cb4e9d Mon Sep 17 00:00:00 2001 From: Josema Camacho Date: Thu, 19 Mar 2026 17:30:06 +0100 Subject: [PATCH 043/366] perf(api): replace JOINs with pre-check in threat score aggregation query (#10394) --- api/CHANGELOG.md | 8 ++++++ .../backend/tasks/jobs/threatscore_utils.py | 11 ++++---- api/src/backend/tasks/tests/test_reports.py | 26 +++++++------------ 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index dff4da4899..5a4bdcf3d4 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to the **Prowler API** are documented in this file. +## [1.22.1] (Prowler v5.21.1) + +### 🐞 Fixed + +- Threat score aggregation query to eliminate unnecessary JOINs and `COUNT(DISTINCT)` overhead [(#10394)](https://github.com/prowler-cloud/prowler/pull/10394) + +--- + ## [1.22.0] (Prowler v5.21.0) ### 🚀 Added diff --git a/api/src/backend/tasks/jobs/threatscore_utils.py b/api/src/backend/tasks/jobs/threatscore_utils.py index a17125af46..2ef29484ee 100644 --- a/api/src/backend/tasks/jobs/threatscore_utils.py +++ b/api/src/backend/tasks/jobs/threatscore_utils.py @@ -4,7 +4,7 @@ from django.db.models import Count, Q from api.db_router import READ_REPLICA_ALIAS from api.db_utils import rls_transaction -from api.models import Finding, StatusChoices +from api.models import Finding, Scan, StatusChoices from prowler.lib.outputs.finding import Finding as FindingOutput logger = get_task_logger(__name__) @@ -35,25 +35,26 @@ def _aggregate_requirement_statistics_from_database( } """ requirement_statistics_by_check_id = {} - # TODO: take into account that now the relation is 1 finding == 1 resource, review this when the logic changes + # TODO: review when finding-resource relation changes from 1:1 with rls_transaction(tenant_id, using=READ_REPLICA_ALIAS): + # Pre-check: skip if the scan's provider is deleted (avoids JOINs in the main query) + if Scan.all_objects.filter(id=scan_id, provider__is_deleted=True).exists(): + return requirement_statistics_by_check_id + aggregated_statistics_queryset = ( Finding.all_objects.filter( tenant_id=tenant_id, scan_id=scan_id, muted=False, - resources__provider__is_deleted=False, ) .values("check_id") .annotate( total_findings=Count( "id", - distinct=True, filter=Q(status__in=[StatusChoices.PASS, StatusChoices.FAIL]), ), passed_findings=Count( "id", - distinct=True, filter=Q(status=StatusChoices.PASS), ), ) diff --git a/api/src/backend/tasks/tests/test_reports.py b/api/src/backend/tasks/tests/test_reports.py index a6a80b0891..858f4c06ca 100644 --- a/api/src/backend/tasks/tests/test_reports.py +++ b/api/src/backend/tasks/tests/test_reports.py @@ -169,35 +169,27 @@ class TestAggregateRequirementStatistics: assert result["check_1"]["passed"] == 1 assert result["check_1"]["total"] == 1 - def test_excludes_findings_without_resources(self, tenants_fixture, scans_fixture): - """Verify findings without resources are excluded from aggregation.""" + def test_skips_aggregation_for_deleted_provider( + self, tenants_fixture, scans_fixture + ): + """Verify aggregation returns empty when the scan's provider is soft-deleted.""" tenant = tenants_fixture[0] scan = scans_fixture[0] - # Finding WITH resource → should be counted self._create_finding_with_resource( tenant, scan, "finding-1", "check_1", StatusChoices.PASS ) - # Finding WITHOUT resource → should be EXCLUDED - Finding.objects.create( - tenant_id=tenant.id, - scan=scan, - uid="finding-2", - check_id="check_1", - status=StatusChoices.FAIL, - severity=Severity.high, - impact=Severity.high, - check_metadata={}, - raw_result={}, - ) + # Soft-delete the provider + provider = scan.provider + provider.is_deleted = True + provider.save(update_fields=["is_deleted"]) result = _aggregate_requirement_statistics_from_database( str(tenant.id), str(scan.id) ) - assert result["check_1"]["passed"] == 1 - assert result["check_1"]["total"] == 1 + assert result == {} def test_multiple_resources_no_double_count(self, tenants_fixture, scans_fixture): """Verify a finding with multiple resources is only counted once.""" From 0b7a21a70c5d932f8f641617643450fda47a279b Mon Sep 17 00:00:00 2001 From: Sandiyo Christan <55909152+sandiyochristan@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:14:52 +0530 Subject: [PATCH 044/366] fix(api): [security] use defusedxml to prevent XML bomb DoS in SAML metadata parsing (#10165) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Adrián Peña --- api/CHANGELOG.md | 1 + api/src/backend/api/models.py | 5 +++- api/src/backend/api/tests/test_models.py | 33 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 5a4bdcf3d4..ea4f957077 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -30,6 +30,7 @@ All notable changes to the **Prowler API** are documented in this file. ### 🔐 Security - Use `psycopg2.sql` to safely compose DDL in `PostgresEnumMigration`, preventing SQL injection via f-string interpolation [(#10166)](https://github.com/prowler-cloud/prowler/pull/10166) +- Replace stdlib XML parser with `defusedxml` in SAML metadata parsing to prevent XML bomb (billion laughs) DoS attacks [(#10165)](https://github.com/prowler-cloud/prowler/pull/10165) --- diff --git a/api/src/backend/api/models.py b/api/src/backend/api/models.py index 7b7ccb9061..78bfec5262 100644 --- a/api/src/backend/api/models.py +++ b/api/src/backend/api/models.py @@ -1,7 +1,6 @@ import json import logging import re -import xml.etree.ElementTree as ET from datetime import datetime, timedelta, timezone from uuid import UUID, uuid4 @@ -9,6 +8,8 @@ from allauth.socialaccount.models import SocialApp from config.custom_logging import BackendLogger from config.settings.social_login import SOCIALACCOUNT_PROVIDERS from cryptography.fernet import Fernet, InvalidToken +import defusedxml +from defusedxml import ElementTree as ET from django.conf import settings from django.contrib.auth.models import AbstractBaseUser from django.contrib.postgres.fields import ArrayField @@ -2067,6 +2068,8 @@ class SAMLConfiguration(RowLevelSecurityProtectedModel): root = ET.fromstring(self.metadata_xml) except ET.ParseError as e: raise ValidationError({"metadata_xml": f"Invalid XML: {e}"}) + except defusedxml.DefusedXmlException as e: + raise ValidationError({"metadata_xml": f"Unsafe XML content rejected: {e}"}) # Entity ID entity_id = root.attrib.get("entityID") diff --git a/api/src/backend/api/tests/test_models.py b/api/src/backend/api/tests/test_models.py index 13878794b1..b8b7f61dd1 100644 --- a/api/src/backend/api/tests/test_models.py +++ b/api/src/backend/api/tests/test_models.py @@ -243,6 +243,39 @@ class TestSAMLConfigurationModel: assert "Invalid XML" in errors["metadata_xml"][0] assert "not well-formed" in errors["metadata_xml"][0] + def test_xml_bomb_rejected(self, tenants_fixture): + """ + Regression test: a 'billion laughs' XML bomb in the SAML metadata field + must be rejected and not allowed to exhaust server memory / CPU. + + Before the fix, xml.etree.ElementTree was used directly, which does not + protect against entity-expansion attacks. The fix switches to defusedxml + which raises an exception for any XML containing entity definitions. + """ + tenant = tenants_fixture[0] + xml_bomb = ( + "" + "" + " " + " " + " " + "]>" + "" + ) + config = SAMLConfiguration( + email_domain="xmlbomb.com", + metadata_xml=xml_bomb, + tenant=tenant, + ) + + with pytest.raises(ValidationError) as exc_info: + config._parse_metadata() + + errors = exc_info.value.message_dict + assert "metadata_xml" in errors + def test_metadata_missing_sso_fails(self, tenants_fixture): tenant = tenants_fixture[0] xml = """ From 3361393b7d380969abaca7062302e0c5ab3d621e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Pe=C3=B1a?= Date: Thu, 19 Mar 2026 17:55:18 +0100 Subject: [PATCH 045/366] chore: update changelog (#10400) --- api/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index ea4f957077..17acf2155a 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to the **Prowler API** are documented in this file. +## [1.23.0] (Prowler UNRELEASED) + +### 🔐 Security + +- Replace stdlib XML parser with `defusedxml` in SAML metadata parsing to prevent XML bomb (billion laughs) DoS attacks [(#10165)](https://github.com/prowler-cloud/prowler/pull/10165) + +--- + ## [1.22.1] (Prowler v5.21.1) ### 🐞 Fixed From ad6368a44658b9bdc042c178f5f929eee8bb2280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Pe=C3=B1a?= Date: Thu, 19 Mar 2026 18:26:55 +0100 Subject: [PATCH 046/366] chore: add defusedxml as api dependency (#10401) --- api/poetry.lock | 30 +++++++++++++++++++++--------- api/pyproject.toml | 1 + 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/api/poetry.lock b/api/poetry.lock index 6fbdb874bc..0b0da9c853 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "about-time" @@ -2699,6 +2699,18 @@ files = [ {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, ] +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + [[package]] name = "deprecated" version = "1.3.1" @@ -2971,7 +2983,7 @@ files = [ [package.dependencies] autopep8 = "*" Django = ">=4.2" -gprof2dot = ">=2017.09.19" +gprof2dot = ">=2017.9.19" sqlparse = "*" [[package]] @@ -4579,7 +4591,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" +jsonschema-specifications = ">=2023.3.6" referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -4787,7 +4799,7 @@ librabbitmq = ["librabbitmq (>=2.0.0) ; python_version < \"3.11\""] mongodb = ["pymongo (==4.15.3)"] msgpack = ["msgpack (==1.1.2)"] pyro = ["pyro4 (==4.82)"] -qpid = ["qpid-python (==1.36.0-1)", "qpid-tools (==1.36.0-1)"] +qpid = ["qpid-python (==1.36.0.post1)", "qpid-tools (==1.36.0.post1)"] redis = ["redis (>=4.5.2,!=4.5.5,!=5.0.2,<6.5)"] slmq = ["softlayer_messaging (>=1.0.3)"] sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] @@ -4808,7 +4820,7 @@ files = [ ] [package.dependencies] -certifi = ">=14.05.14" +certifi = ">=14.5.14" durationpy = ">=0.7" google-auth = ">=1.0.1" oauthlib = ">=3.2.2" @@ -7170,7 +7182,7 @@ files = [ ] [package.dependencies] -astroid = ">=3.2.2,<=3.3.0-dev0" +astroid = ">=3.2.2,<=3.3.0.dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, @@ -8184,10 +8196,10 @@ files = [ ] [package.dependencies] -botocore = ">=1.37.4,<2.0a.0" +botocore = ">=1.37.4,<2.0a0" [package.extras] -crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] +crt = ["botocore[crt] (>=1.37.4,<2.0a0)"] [[package]] name = "safety" @@ -9382,4 +9394,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.13" -content-hash = "6e38c38b1f8dc05b881f49703fa445eec299527e6697992b18e4613534fbcdb6" +content-hash = "2ed5b4e47d81da81963814f21702220ac5619f50cd605fd779be53c8c46ffca5" diff --git a/api/pyproject.toml b/api/pyproject.toml index bc9c742a6c..3a89f52e05 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -22,6 +22,7 @@ dependencies = [ "drf-nested-routers (>=0.94.1,<1.0.0)", "drf-spectacular==0.27.2", "drf-spectacular-jsonapi==0.5.1", + "defusedxml==0.7.1", "gunicorn==23.0.0", "lxml==5.3.2", "prowler @ git+https://github.com/prowler-cloud/prowler.git@master", From 1eda94140dc027317a1ac72f2351cbc198d3cbe0 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:45:08 +0000 Subject: [PATCH 047/366] fix(sdk): use case-insensitive comparison for Azure VM backup checks (#10395) --- prowler/CHANGELOG.md | 8 ++ .../vm/vm_backup_enabled/vm_backup_enabled.py | 3 +- ...ufficient_daily_backup_retention_period.py | 3 +- .../vm_backup_enabled_test.py | 79 ++++++++++++++++ ...ient_daily_backup_retention_period_test.py | 94 +++++++++++++++++++ 5 files changed, 185 insertions(+), 2 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 9c57c2bece..a0061fb196 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to the **Prowler SDK** are documented in this file. +## [5.21.2] (Prowler UNRELEASED) + +### 🐞 Fixed + +- Azure `vm_backup_enabled` and `vm_sufficient_daily_backup_retention_period` checks now compare VM names case-insensitively to avoid false negatives when Azure stores backup item names in a different case [(#10373)](https://github.com/prowler-cloud/prowler/pull/10373) + +--- + ## [5.21.0] (Prowler v5.21.0) ### 🚀 Added diff --git a/prowler/providers/azure/services/vm/vm_backup_enabled/vm_backup_enabled.py b/prowler/providers/azure/services/vm/vm_backup_enabled/vm_backup_enabled.py index e9da4662ed..d5865937f9 100644 --- a/prowler/providers/azure/services/vm/vm_backup_enabled/vm_backup_enabled.py +++ b/prowler/providers/azure/services/vm/vm_backup_enabled/vm_backup_enabled.py @@ -31,7 +31,8 @@ class vm_backup_enabled(Check): for backup_item in vault.backup_protected_items.values(): if ( backup_item.workload_type == DataSourceType.VM - and backup_item.name.split(";")[-1] == vm.resource_name + and backup_item.name.split(";")[-1].lower() + == vm.resource_name.lower() ): found = True found_vault_name = vault.name diff --git a/prowler/providers/azure/services/vm/vm_sufficient_daily_backup_retention_period/vm_sufficient_daily_backup_retention_period.py b/prowler/providers/azure/services/vm/vm_sufficient_daily_backup_retention_period/vm_sufficient_daily_backup_retention_period.py index 221df85351..444cfcfa3b 100644 --- a/prowler/providers/azure/services/vm/vm_sufficient_daily_backup_retention_period/vm_sufficient_daily_backup_retention_period.py +++ b/prowler/providers/azure/services/vm/vm_sufficient_daily_backup_retention_period/vm_sufficient_daily_backup_retention_period.py @@ -27,7 +27,8 @@ class vm_sufficient_daily_backup_retention_period(Check): for backup_item in vault.backup_protected_items.values(): if ( backup_item.workload_type == DataSourceType.VM - and backup_item.name.split(";")[-1] == vm.resource_name + and backup_item.name.split(";")[-1].lower() + == vm.resource_name.lower() ): backup_found = True policy_id = backup_item.backup_policy_id diff --git a/tests/providers/azure/services/vm/vm_backup_enabled/vm_backup_enabled_test.py b/tests/providers/azure/services/vm/vm_backup_enabled/vm_backup_enabled_test.py index 23394addca..a99be2ea54 100644 --- a/tests/providers/azure/services/vm/vm_backup_enabled/vm_backup_enabled_test.py +++ b/tests/providers/azure/services/vm/vm_backup_enabled/vm_backup_enabled_test.py @@ -221,6 +221,85 @@ class Test_vm_backup_enabled: == f"VM {vm_name} in subscription {AZURE_SUBSCRIPTION_ID} is not protected by Azure Backup." ) + def test_vm_protected_by_backup_case_insensitive(self): + vm_id = str(uuid4()) + vm_name = "vmtest" + vault_id = str(uuid4()) + vault_name = "vault1" + mock_vm_client = mock.MagicMock() + mock_recovery_client = mock.MagicMock() + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.vm.vm_backup_enabled.vm_backup_enabled.vm_client", + new=mock_vm_client, + ), + mock.patch( + "prowler.providers.azure.services.vm.vm_backup_enabled.vm_backup_enabled.recovery_client", + new=mock_recovery_client, + ), + ): + from azure.mgmt.recoveryservicesbackup.activestamp.models import ( + DataSourceType, + ) + + from prowler.providers.azure.services.recovery.recovery_service import ( + BackupItem, + BackupVault, + ) + from prowler.providers.azure.services.vm.vm_backup_enabled.vm_backup_enabled import ( + vm_backup_enabled, + ) + from prowler.providers.azure.services.vm.vm_service import ( + ManagedDiskParameters, + OSDisk, + StorageProfile, + VirtualMachine, + ) + + vm = VirtualMachine( + resource_id=vm_id, + resource_name=vm_name, + location="eastus", + security_profile=None, + extensions=[], + storage_profile=StorageProfile( + os_disk=OSDisk( + name="os_disk_name", + operating_system_type="Linux", + managed_disk=ManagedDiskParameters(id="managed_disk_id"), + ), + data_disks=[], + ), + ) + backup_item = BackupItem( + id=str(uuid4()), + name="someprefix;VMTEST", + workload_type=DataSourceType.VM, + ) + vault = BackupVault( + id=vault_id, + name=vault_name, + location="eastus", + backup_protected_items={backup_item.id: backup_item}, + ) + mock_vm_client.virtual_machines = {AZURE_SUBSCRIPTION_ID: {vm_id: vm}} + mock_recovery_client.vaults = {AZURE_SUBSCRIPTION_ID: {vault_id: vault}} + check = vm_backup_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == vm_name + assert result[0].resource_id == vm_id + assert ( + result[0].status_extended + == f"VM {vm_name} in subscription {AZURE_SUBSCRIPTION_ID} is protected by Azure Backup (vault: {vault_name})." + ) + def test_vm_protected_by_backup_non_vm_workload(self): vm_id = str(uuid4()) vm_name = "VMTest" diff --git a/tests/providers/azure/services/vm/vm_sufficient_daily_backup_retention_period/vm_sufficient_daily_backup_retention_period_test.py b/tests/providers/azure/services/vm/vm_sufficient_daily_backup_retention_period/vm_sufficient_daily_backup_retention_period_test.py index b43b75548a..28aab1b38b 100644 --- a/tests/providers/azure/services/vm/vm_sufficient_daily_backup_retention_period/vm_sufficient_daily_backup_retention_period_test.py +++ b/tests/providers/azure/services/vm/vm_sufficient_daily_backup_retention_period/vm_sufficient_daily_backup_retention_period_test.py @@ -156,6 +156,100 @@ class Test_vm_sufficient_daily_backup_retention_period: in result[0].status_extended ) + def test_vm_with_sufficient_retention_case_insensitive(self): + from azure.mgmt.recoveryservicesbackup.activestamp.models import DataSourceType + + from prowler.providers.azure.services.recovery.recovery_service import ( + BackupItem, + BackupPolicy, + BackupVault, + ) + from prowler.providers.azure.services.vm.vm_service import ( + ManagedDiskParameters, + OSDisk, + StorageProfile, + VirtualMachine, + ) + + vm_id = str(uuid4()) + vm_name = "vmtest" + vault_id = str(uuid4()) + policy_id = str(uuid4()) + retention_days = 14 + min_retention_days = 7 + + vm = VirtualMachine( + resource_id=vm_id, + resource_name=vm_name, + location="eastus", + security_profile=None, + extensions=[], + storage_profile=StorageProfile( + os_disk=OSDisk( + name="os_disk_name", + operating_system_type="Linux", + managed_disk=ManagedDiskParameters(id="managed_disk_id"), + ), + data_disks=[], + ), + ) + backup_item = BackupItem( + id=str(uuid4()), + name="someprefix;VMTEST", + workload_type=DataSourceType.VM, + backup_policy_id=policy_id, + ) + backup_policy = BackupPolicy( + id=policy_id, + name="policy1", + retention_days=retention_days, + ) + vault = BackupVault( + id=vault_id, + name="vault1", + location="eastus", + backup_protected_items={backup_item.id: backup_item}, + backup_policies={policy_id: backup_policy}, + ) + vm_client = mock.MagicMock() + recovery_client = mock.MagicMock() + vm_client.virtual_machines = {AZURE_SUBSCRIPTION_ID: {vm_id: vm}} + recovery_client.vaults = {AZURE_SUBSCRIPTION_ID: {vault_id: vault}} + vm_client.audit_config = { + "vm_backup_min_daily_retention_days": min_retention_days + } + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider( + audit_config=vm_client.audit_config + ), + ), + mock.patch( + "prowler.providers.azure.services.vm.vm_sufficient_daily_backup_retention_period.vm_sufficient_daily_backup_retention_period.vm_client", + new=vm_client, + ), + mock.patch( + "prowler.providers.azure.services.vm.vm_sufficient_daily_backup_retention_period.vm_sufficient_daily_backup_retention_period.recovery_client", + new=recovery_client, + ), + ): + from prowler.providers.azure.services.vm.vm_sufficient_daily_backup_retention_period.vm_sufficient_daily_backup_retention_period import ( + vm_sufficient_daily_backup_retention_period, + ) + + check = vm_sufficient_daily_backup_retention_period() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == vm_name + assert result[0].resource_id == vm_id + assert ( + f"has a daily backup retention period of {retention_days} days" + in result[0].status_extended + ) + def test_vm_with_insufficient_retention(self): from azure.mgmt.recoveryservicesbackup.activestamp.models import DataSourceType From 7df73a9d4fe66e54f1e9377e5279f21dbcb164b3 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:59:14 +0000 Subject: [PATCH 048/366] fix(sdk): use case-insensitive comparison for Azure MySQL flexible server checks (#10396) --- prowler/CHANGELOG.md | 3 +- ...e_server_audit_log_connection_activated.py | 4 +- ...mysql_flexible_server_audit_log_enabled.py | 2 +- ..._flexible_server_ssl_connection_enabled.py | 5 +- ...ver_audit_log_connection_activated_test.py | 51 +++++++++++++++++++ ..._flexible_server_audit_log_enabled_test.py | 51 +++++++++++++++++++ ...ible_server_ssl_connection_enabled_test.py | 51 +++++++++++++++++++ 7 files changed, 162 insertions(+), 5 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index a0061fb196..2c8b47daf4 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -6,7 +6,8 @@ All notable changes to the **Prowler SDK** are documented in this file. ### 🐞 Fixed -- Azure `vm_backup_enabled` and `vm_sufficient_daily_backup_retention_period` checks now compare VM names case-insensitively to avoid false negatives when Azure stores backup item names in a different case [(#10373)](https://github.com/prowler-cloud/prowler/pull/10373) +- Azure MySQL flexible server checks now compare configuration values case-insensitively to avoid false negatives when Azure returns lowercase values [(#10396)](https://github.com/prowler-cloud/prowler/pull/10396) +- Azure `vm_backup_enabled` and `vm_sufficient_daily_backup_retention_period` checks now compare VM names case-insensitively to avoid false negatives when Azure stores backup item names in a different case [(#10395)](https://github.com/prowler-cloud/prowler/pull/10395) --- diff --git a/prowler/providers/azure/services/mysql/mysql_flexible_server_audit_log_connection_activated/mysql_flexible_server_audit_log_connection_activated.py b/prowler/providers/azure/services/mysql/mysql_flexible_server_audit_log_connection_activated/mysql_flexible_server_audit_log_connection_activated.py index 03c94bcfed..5071da4b20 100644 --- a/prowler/providers/azure/services/mysql/mysql_flexible_server_audit_log_connection_activated/mysql_flexible_server_audit_log_connection_activated.py +++ b/prowler/providers/azure/services/mysql/mysql_flexible_server_audit_log_connection_activated/mysql_flexible_server_audit_log_connection_activated.py @@ -21,9 +21,9 @@ class mysql_flexible_server_audit_log_connection_activated(Check): "audit_log_events" ].resource_id - if "CONNECTION" in server.configurations[ + if "connection" in server.configurations[ "audit_log_events" - ].value.split(","): + ].value.lower().split(","): report.status = "PASS" report.status_extended = f"Audit log is enabled for server {server.name} in subscription {subscription_name}." diff --git a/prowler/providers/azure/services/mysql/mysql_flexible_server_audit_log_enabled/mysql_flexible_server_audit_log_enabled.py b/prowler/providers/azure/services/mysql/mysql_flexible_server_audit_log_enabled/mysql_flexible_server_audit_log_enabled.py index c8ae94fb31..81918f7756 100644 --- a/prowler/providers/azure/services/mysql/mysql_flexible_server_audit_log_enabled/mysql_flexible_server_audit_log_enabled.py +++ b/prowler/providers/azure/services/mysql/mysql_flexible_server_audit_log_enabled/mysql_flexible_server_audit_log_enabled.py @@ -21,7 +21,7 @@ class mysql_flexible_server_audit_log_enabled(Check): "audit_log_enabled" ].resource_id - if server.configurations["audit_log_enabled"].value == "ON": + if server.configurations["audit_log_enabled"].value.lower() == "on": report.status = "PASS" report.status_extended = f"Audit log is enabled for server {server.name} in subscription {subscription_name}." diff --git a/prowler/providers/azure/services/mysql/mysql_flexible_server_ssl_connection_enabled/mysql_flexible_server_ssl_connection_enabled.py b/prowler/providers/azure/services/mysql/mysql_flexible_server_ssl_connection_enabled/mysql_flexible_server_ssl_connection_enabled.py index a18a1aba5e..79930de947 100644 --- a/prowler/providers/azure/services/mysql/mysql_flexible_server_ssl_connection_enabled/mysql_flexible_server_ssl_connection_enabled.py +++ b/prowler/providers/azure/services/mysql/mysql_flexible_server_ssl_connection_enabled/mysql_flexible_server_ssl_connection_enabled.py @@ -20,7 +20,10 @@ class mysql_flexible_server_ssl_connection_enabled(Check): report.resource_id = server.configurations[ "require_secure_transport" ].resource_id - if server.configurations["require_secure_transport"].value == "ON": + if ( + server.configurations["require_secure_transport"].value.lower() + == "on" + ): report.status = "PASS" report.status_extended = f"SSL connection is enabled for server {server.name} in subscription {subscription_name}." diff --git a/tests/providers/azure/services/mysql/mysql_flexible_server_audit_log_connection_activated/mysql_flexible_server_audit_log_connection_activated_test.py b/tests/providers/azure/services/mysql/mysql_flexible_server_audit_log_connection_activated/mysql_flexible_server_audit_log_connection_activated_test.py index 84daaa758c..47ef92551b 100644 --- a/tests/providers/azure/services/mysql/mysql_flexible_server_audit_log_connection_activated/mysql_flexible_server_audit_log_connection_activated_test.py +++ b/tests/providers/azure/services/mysql/mysql_flexible_server_audit_log_connection_activated/mysql_flexible_server_audit_log_connection_activated_test.py @@ -56,6 +56,57 @@ class Test_mysql_flexible_server_audit_log_connection_activated: result = check.execute() assert len(result) == 0 + def test_mysql_audit_log_connection_activated_lowercase(self): + server_name = str(uuid4()) + mysql_client = mock.MagicMock + mysql_client.flexible_servers = { + AZURE_SUBSCRIPTION_ID: { + "/subscriptions/resource_id": FlexibleServer( + resource_id="/subscriptions/resource_id", + name=server_name, + location="location", + version="version", + configurations={ + "audit_log_events": Configuration( + resource_id=f"/subscriptions/{server_name}/configurations/audit_log_events", + description="description", + value="connection", + ) + }, + ) + } + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.mysql.mysql_flexible_server_audit_log_connection_activated.mysql_flexible_server_audit_log_connection_activated.mysql_client", + new=mysql_client, + ), + ): + from prowler.providers.azure.services.mysql.mysql_flexible_server_audit_log_connection_activated.mysql_flexible_server_audit_log_connection_activated import ( + mysql_flexible_server_audit_log_connection_activated, + ) + + check = mysql_flexible_server_audit_log_connection_activated() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == server_name + assert result[0].location == "location" + assert ( + result[0].resource_id + == f"/subscriptions/{server_name}/configurations/audit_log_events" + ) + assert ( + result[0].status_extended + == f"Audit log is enabled for server {server_name} in subscription {AZURE_SUBSCRIPTION_ID}." + ) + def test_mysql_audit_log_connection_not_connection(self): server_name = str(uuid4()) mysql_client = mock.MagicMock diff --git a/tests/providers/azure/services/mysql/mysql_flexible_server_audit_log_enabled/mysql_flexible_server_audit_log_enabled_test.py b/tests/providers/azure/services/mysql/mysql_flexible_server_audit_log_enabled/mysql_flexible_server_audit_log_enabled_test.py index ad243c5807..7c32f337fd 100644 --- a/tests/providers/azure/services/mysql/mysql_flexible_server_audit_log_enabled/mysql_flexible_server_audit_log_enabled_test.py +++ b/tests/providers/azure/services/mysql/mysql_flexible_server_audit_log_enabled/mysql_flexible_server_audit_log_enabled_test.py @@ -56,6 +56,57 @@ class Test_mysql_flexible_server_audit_log_enabled: result = check.execute() assert len(result) == 0 + def test_mysql_audit_log_enabled_lowercase(self): + server_name = str(uuid4()) + mysql_client = mock.MagicMock + mysql_client.flexible_servers = { + AZURE_SUBSCRIPTION_ID: { + "/subscriptions/resource_id": FlexibleServer( + resource_id="/subscriptions/resource_id", + name=server_name, + location="location", + version="version", + configurations={ + "audit_log_enabled": Configuration( + resource_id=f"/subscriptions/{server_name}/configurations/audit_log_enabled", + description="description", + value="on", + ) + }, + ) + } + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.mysql.mysql_flexible_server_audit_log_enabled.mysql_flexible_server_audit_log_enabled.mysql_client", + new=mysql_client, + ), + ): + from prowler.providers.azure.services.mysql.mysql_flexible_server_audit_log_enabled.mysql_flexible_server_audit_log_enabled import ( + mysql_flexible_server_audit_log_enabled, + ) + + check = mysql_flexible_server_audit_log_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == server_name + assert result[0].location == "location" + assert ( + result[0].resource_id + == f"/subscriptions/{server_name}/configurations/audit_log_enabled" + ) + assert ( + result[0].status_extended + == f"Audit log is enabled for server {server_name} in subscription {AZURE_SUBSCRIPTION_ID}." + ) + def test_mysql_audit_log_disabled(self): server_name = str(uuid4()) mysql_client = mock.MagicMock diff --git a/tests/providers/azure/services/mysql/mysql_flexible_server_ssl_connection_enabled/mysql_flexible_server_ssl_connection_enabled_test.py b/tests/providers/azure/services/mysql/mysql_flexible_server_ssl_connection_enabled/mysql_flexible_server_ssl_connection_enabled_test.py index f540fe4865..2b87a28d8f 100644 --- a/tests/providers/azure/services/mysql/mysql_flexible_server_ssl_connection_enabled/mysql_flexible_server_ssl_connection_enabled_test.py +++ b/tests/providers/azure/services/mysql/mysql_flexible_server_ssl_connection_enabled/mysql_flexible_server_ssl_connection_enabled_test.py @@ -107,6 +107,57 @@ class Test_mysql_flexible_server_ssl_connection_enabled: == f"SSL connection is enabled for server {server_name} in subscription {AZURE_SUBSCRIPTION_ID}." ) + def test_mysql_connection_enabled_lowercase(self): + server_name = str(uuid4()) + mysql_client = mock.MagicMock + mysql_client.flexible_servers = { + AZURE_SUBSCRIPTION_ID: { + "/subscriptions/resource_id": FlexibleServer( + resource_id="/subscriptions/resource_id", + name=server_name, + location="location", + version="version", + configurations={ + "require_secure_transport": Configuration( + resource_id=f"/subscriptions/{server_name}/configurations/require_secure_transport", + description="description", + value="on", + ) + }, + ) + } + } + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.mysql.mysql_flexible_server_ssl_connection_enabled.mysql_flexible_server_ssl_connection_enabled.mysql_client", + new=mysql_client, + ), + ): + from prowler.providers.azure.services.mysql.mysql_flexible_server_ssl_connection_enabled.mysql_flexible_server_ssl_connection_enabled import ( + mysql_flexible_server_ssl_connection_enabled, + ) + + check = mysql_flexible_server_ssl_connection_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == server_name + assert result[0].location == "location" + assert ( + result[0].resource_id + == f"/subscriptions/{server_name}/configurations/require_secure_transport" + ) + assert ( + result[0].status_extended + == f"SSL connection is enabled for server {server_name} in subscription {AZURE_SUBSCRIPTION_ID}." + ) + def test_mysql_ssl_connection_disabled(self): server_name = str(uuid4()) mysql_client = mock.MagicMock From 93b8a7c74ca2e3ae2ee29c11360a98a55388eda7 Mon Sep 17 00:00:00 2001 From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 11:12:26 +0100 Subject: [PATCH 049/366] docs(attack-paths): Lighthouse AI support and supported queries to Attack Paths (#10409) Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com> Co-authored-by: Josema Camacho --- .../tutorials/prowler-app-attack-paths.mdx | 122 +++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/tutorials/prowler-app-attack-paths.mdx b/docs/user-guide/tutorials/prowler-app-attack-paths.mdx index 88d9a3a25d..646ee557de 100644 --- a/docs/user-guide/tutorials/prowler-app-attack-paths.mdx +++ b/docs/user-guide/tutorials/prowler-app-attack-paths.mdx @@ -202,15 +202,135 @@ To expand the graph for detailed exploration, click the fullscreen icon in the g width="700" /> -## Using Attack Paths with the MCP Server +## Using Attack Paths with the MCP Server and Lighthouse AI Attack Paths capabilities are also available through the [Prowler MCP Server](/getting-started/products/prowler-mcp), enabling interaction with Attack Paths data via AI assistants like Claude Desktop, Cursor, and other MCP clients. +[Prowler Lighthouse AI](/getting-started/products/prowler-lighthouse-ai) also supports Attack Paths queries, allowing you to analyze privilege escalation chains and security misconfigurations directly from the chat interface. + The following MCP tools are available for Attack Paths: - **`prowler_app_list_attack_paths_scans`** - List and filter Attack Paths scans - **`prowler_app_list_attack_paths_queries`** - Discover available queries for a completed scan - **`prowler_app_run_attack_paths_query`** - Execute a query and retrieve graph results with nodes and relationships +- **`prowler_app_get_attack_paths_cartography_schema`** - Retrieve the Cartography graph schema for custom openCypher queries + +### Example Questions + +Ask through the MCP Server or Lighthouse AI: + +- "Find EC2 instances exposed to the internet with access to sensitive S3 buckets" +- "Are there any IAM roles that can escalate their own privileges?" +- "Show me all internet-facing resources with open security groups" +- "Which principals can create Lambda functions with privileged roles?" +- "List all RDS instances with storage encryption disabled" +- "Find S3 buckets that allow anonymous access" +- "Are there any CloudFormation stacks that could be hijacked for privilege escalation?" +- "Show me all roles that can be assumed for lateral movement" + +### Supported Queries + +Attack Paths currently supports the following built-in queries for AWS: + +#### Custom Attack Path Queries + +| Query | Description | +|---|---| +| **Internet-Exposed EC2 with Sensitive S3 Access** | Find SSH-exposed EC2 instances that can assume roles to read tagged sensitive S3 buckets | + +#### Basic Resource Queries + +| Query | Description | +|---|---| +| **RDS Instances Inventory** | List all provisioned RDS database instances in the account | +| **Unencrypted RDS Instances** | Find RDS instances with storage encryption disabled | +| **S3 Buckets with Anonymous Access** | Find S3 buckets that allow anonymous access | +| **IAM Statements Allowing All Actions** | Find IAM policy statements that allow all actions via wildcard (\*) | +| **IAM Statements Allowing Policy Deletion** | Find IAM policy statements that allow iam:DeletePolicy | +| **IAM Statements Allowing Create Actions** | Find IAM policy statements that allow any create action | + +#### Network Exposure Queries + +| Query | Description | +|---|---| +| **Internet-Exposed EC2 Instances** | Find EC2 instances flagged as exposed to the internet | +| **Open Security Groups on Internet-Facing Resources** | Find internet-facing resources with security groups allowing inbound from 0.0.0.0/0 | +| **Internet-Exposed Classic Load Balancers** | Find Classic Load Balancers exposed to the internet with their listeners | +| **Internet-Exposed ALB/NLB Load Balancers** | Find ELBv2 (ALB/NLB) load balancers exposed to the internet with their listeners | +| **Resource Lookup by Public IP** | Find the AWS resource associated with a given public IP address | + +#### Privilege Escalation Queries + +These queries are based on research from [pathfinding.cloud](https://pathfinding.cloud) by Datadog. + +| Query | Description | +|---|---| +| **App Runner Service Creation with Privileged Role (APPRUNNER-001)** | Create an App Runner service with a privileged IAM role to gain its permissions | +| **App Runner Service Update for Role Access (APPRUNNER-002)** | Update an existing App Runner service to leverage its already-attached privileged role | +| **Bedrock Code Interpreter with Privileged Role (BEDROCK-001)** | Create a Bedrock AgentCore Code Interpreter with a privileged role attached | +| **Bedrock Code Interpreter Session Hijacking (BEDROCK-002)** | Start a session on an existing Bedrock code interpreter to exfiltrate its privileged role credentials | +| **CloudFormation Stack Creation with Privileged Role (CLOUDFORMATION-001)** | Create a CloudFormation stack with a privileged role to provision arbitrary AWS resources | +| **CloudFormation Stack Update for Role Access (CLOUDFORMATION-002)** | Update an existing CloudFormation stack to leverage its already-attached privileged service role | +| **CloudFormation StackSet Creation with Privileged Role (CLOUDFORMATION-003)** | Create a CloudFormation StackSet with a privileged execution role to provision arbitrary resources across accounts | +| **CloudFormation StackSet Update with Privileged Role (CLOUDFORMATION-004)** | Update an existing CloudFormation StackSet to inject malicious resources using a privileged execution role | +| **CloudFormation Change Set Privilege Escalation (CLOUDFORMATION-005)** | Create and execute a change set on an existing stack to leverage its privileged service role | +| **CodeBuild Project Creation with Privileged Role (CODEBUILD-001)** | Create a CodeBuild project with a privileged role to execute arbitrary code via a malicious buildspec | +| **CodeBuild Buildspec Override for Role Access (CODEBUILD-002)** | Start a build on an existing CodeBuild project with a buildspec override to execute code with its privileged role | +| **CodeBuild Batch Buildspec Override for Role Access (CODEBUILD-003)** | Start a batch build on an existing CodeBuild project with a buildspec override to execute code with its privileged role | +| **CodeBuild Batch Project Creation with Privileged Role (CODEBUILD-004)** | Create a CodeBuild project configured for batch builds with a privileged role to execute arbitrary code via a malicious buildspec | +| **Data Pipeline Creation with Privileged Role (DATAPIPELINE-001)** | Create a Data Pipeline with a privileged role to execute arbitrary commands on provisioned infrastructure | +| **EC2 Instance Launch with Privileged Role (EC2-001)** | Launch EC2 instances with privileged IAM roles to gain their permissions via IMDS | +| **EC2 Role Hijacking via UserData Injection (EC2-002)** | Inject malicious scripts into EC2 instance userData to gain the attached role's permissions | +| **Spot Instance Launch with Privileged Role (EC2-003)** | Launch EC2 Spot Instances with privileged IAM roles to gain their permissions via IMDS | +| **Launch Template Poisoning for Role Access (EC2-004)** | Inject malicious userData into launch templates that reference privileged roles, no PassRole needed | +| **EC2 Instance Connect SSH Access for Role Credentials (EC2INSTANCECONNECT-003)** | Push a temporary SSH key to an EC2 instance via Instance Connect to access its attached role credentials through IMDS | +| **ECS Service Creation with Privileged Role (ECS-001 - New Cluster)** | Create an ECS cluster and service with a privileged Fargate task role to execute arbitrary code | +| **ECS Task Execution with Privileged Role (ECS-002 - New Cluster)** | Create an ECS cluster and run a one-off Fargate task with a privileged role to execute arbitrary code | +| **ECS Service Creation with Privileged Role (ECS-003 - Existing Cluster)** | Deploy a Fargate service with a privileged role on an existing ECS cluster | +| **ECS Task Execution with Privileged Role (ECS-004 - Existing Cluster)** | Run a one-off Fargate task with a privileged role on an existing ECS cluster | +| **ECS Task Start with Privileged Role on EC2 (ECS-005 - Existing Cluster)** | Register a task definition with a privileged role and start it on an EC2 container instance to execute arbitrary code | +| **ECS Exec Container Hijacking for Role Credentials (ECS-006)** | Shell into a running ECS container via ECS Exec to steal the attached task role's credentials | +| **Glue Dev Endpoint with Privileged Role (GLUE-001)** | Create a Glue development endpoint with a privileged role attached to gain its permissions | +| **Glue Dev Endpoint SSH Hijacking via Update (GLUE-002)** | Update an existing Glue development endpoint to inject an SSH public key and access its attached role credentials | +| **Glue Job Creation with Privileged Role (GLUE-003)** | Create a Glue job with a privileged role and start it to execute arbitrary code with that role's permissions | +| **Glue Job Creation with Scheduled Trigger and Privileged Role (GLUE-004)** | Create a Glue job with a privileged role and a scheduled trigger to persistently execute arbitrary code | +| **Glue Job Hijacking via Update with Privileged Role (GLUE-005)** | Update an existing Glue job to attach a privileged role and inject malicious code, then start it to gain that role's permissions | +| **Glue Job Hijacking with Scheduled Trigger and Privileged Role (GLUE-006)** | Update an existing Glue job to attach a privileged role and inject malicious code, then create a scheduled trigger for persistent automated execution | +| **Policy Version Override for Self-Escalation (IAM-001)** | Create a new version of an attached policy with administrative permissions, instantly escalating the principal's own privileges | +| **Access Key Creation for Lateral Movement (IAM-002)** | Create access keys for other IAM users to gain their permissions and move laterally across the account | +| **Access Key Rotation Attack for Lateral Movement (IAM-003)** | Delete and recreate access keys for other IAM users to bypass the two-key limit and gain their permissions | +| **Console Login Profile Creation for Lateral Movement (IAM-004)** | Create console login profiles for other IAM users to access the AWS Console with their permissions | +| **Inline Policy Injection for Self-Escalation (IAM-005)** | Attach an inline policy with administrative permissions to your own role, instantly escalating privileges | +| **Console Password Override for Lateral Movement (IAM-006)** | Change the console password of other IAM users to log in as them and gain their permissions | +| **Inline Policy Injection on User for Self-Escalation (IAM-007)** | Attach an inline policy with administrative permissions to your own IAM user, instantly escalating privileges | +| **Managed Policy Attachment on User for Self-Escalation (IAM-008)** | Attach existing managed policies with administrative permissions to your own IAM user, instantly escalating privileges | +| **Managed Policy Attachment on Role for Self-Escalation (IAM-009)** | Attach existing managed policies with administrative permissions to your own IAM role, instantly escalating privileges | +| **Managed Policy Attachment on Group for Self-Escalation (IAM-010)** | Attach existing managed policies with administrative permissions to a group you belong to, escalating privileges for all group members | +| **Inline Policy Injection on Group for Self-Escalation (IAM-011)** | Attach an inline policy with administrative permissions to a group you belong to, escalating privileges for all group members | +| **Trust Policy Hijacking for Role Assumption (IAM-012)** | Modify a role's trust policy to allow yourself to assume it, gaining the role's permissions | +| **Group Membership Hijacking for Privilege Escalation (IAM-013)** | Add yourself to a privileged IAM group to inherit its permissions, gaining access to all policies attached to the group | +| **Managed Policy Attachment with Role Assumption for Lateral Movement (IAM-014)** | Attach administrative managed policies to another role you can assume, then assume it to gain elevated privileges | +| **Managed Policy Attachment with Access Key Creation for Lateral Movement (IAM-015)** | Attach administrative managed policies to another IAM user and create access keys for them to gain programmatic access with elevated privileges | +| **Policy Version Override with Role Assumption for Lateral Movement (IAM-016)** | Create a new version of a customer-managed policy attached to another role with administrative permissions, then assume that role to gain elevated access | +| **Inline Policy Injection with Role Assumption for Lateral Movement (IAM-017)** | Attach an inline policy with administrative permissions to another role you can assume, then assume it to gain elevated privileges | +| **Inline Policy Injection with Access Key Creation for Lateral Movement (IAM-018)** | Attach an inline policy with administrative permissions to another IAM user and create access keys for them to gain programmatic access with elevated privileges | +| **Managed Policy Attachment with Trust Policy Hijacking for Privilege Escalation (IAM-019)** | Attach administrative managed policies to a role and modify its trust policy to allow yourself to assume it, gaining elevated privileges without prior assume-role access | +| **Policy Version Override with Trust Policy Hijacking for Privilege Escalation (IAM-020)** | Create a new version of a customer-managed policy attached to a role with administrative permissions and modify its trust policy to assume it, without prior assume-role access | +| **Inline Policy Injection with Trust Policy Hijacking for Privilege Escalation (IAM-021)** | Add an inline policy with administrative permissions to a role and modify its trust policy to allow yourself to assume it, gaining elevated privileges without prior assume-role access | +| **Lambda Function Creation with Privileged Role (LAMBDA-001)** | Create a Lambda function with a privileged IAM role and invoke it to execute code with that role's permissions | +| **Lambda Function Creation with Event Source Trigger (LAMBDA-002)** | Create a Lambda function with a privileged IAM role and an event source mapping to trigger it automatically, executing code with the role's permissions | +| **Lambda Function Code Injection (LAMBDA-003)** | Modify the code of an existing Lambda function to execute arbitrary commands with the function's execution role permissions | +| **Lambda Function Code Injection with Direct Invocation (LAMBDA-004)** | Modify the code of an existing Lambda function and invoke it directly to execute arbitrary commands with the function's execution role permissions | +| **Lambda Function Code Injection with Resource Policy Grant (LAMBDA-005)** | Modify the code of an existing Lambda function and grant yourself invocation permission via its resource-based policy to execute code with the function's execution role | +| **Lambda Function Creation with Resource Policy Invocation (LAMBDA-006)** | Create a Lambda function with a privileged IAM role and grant yourself invocation permission via its resource-based policy to execute code with the role's permissions | +| **SageMaker Notebook Creation with Privileged Role (SAGEMAKER-001)** | Create a SageMaker notebook instance with a privileged IAM role to execute arbitrary code with the role's permissions via the Jupyter environment | +| **SageMaker Training Job Creation with Privileged Role (SAGEMAKER-002)** | Create a SageMaker training job with a privileged IAM role to execute arbitrary container code with the role's permissions | +| **SageMaker Processing Job Creation with Privileged Role (SAGEMAKER-003)** | Create a SageMaker processing job with a privileged IAM role to execute arbitrary container code with the role's permissions | +| **SageMaker Presigned Notebook URL for Privilege Escalation (SAGEMAKER-004)** | Generate a presigned URL to access an existing SageMaker notebook instance and execute code with its execution role's permissions | +| **SageMaker Notebook Lifecycle Config Injection (SAGEMAKER-005)** | Inject a malicious lifecycle configuration into an existing SageMaker notebook to execute code with the notebook's execution role during startup | +| **SSM Session Access for EC2 Role Credentials (SSM-001)** | Start an SSM session on an EC2 instance to access its attached role credentials through IMDS | +| **SSM Send Command for EC2 Role Credentials (SSM-002)** | Execute commands on an EC2 instance via SSM Run Command to access its attached role credentials through IMDS | +| **Role Assumption for Privilege Escalation (STS-001)** | Assume IAM roles with elevated permissions by exploiting bidirectional trust between the starting principal and the target role | These tools enable workflows such as: - Asking an AI assistant to identify privilege escalation paths in a specific AWS account From 591f5a860382cccf7aa03b2b3c40b6f0d765d1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Pe=C3=B1a?= Date: Mon, 23 Mar 2026 12:43:45 +0100 Subject: [PATCH 050/366] fix(api): align finding-group latest aggregation (#10419) --- api/CHANGELOG.md | 5 ++ api/src/backend/api/tests/test_views.py | 63 +++++++++++++++++++++++-- api/src/backend/api/v1/views.py | 41 +++++++++++----- 3 files changed, 94 insertions(+), 15 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 17acf2155a..e09b601197 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to the **Prowler API** are documented in this file. ## [1.23.0] (Prowler UNRELEASED) +### 🐞 Fixed + +- Finding groups latest endpoint now aggregates the latest snapshot per provider before check-level totals, keeping impacted resources aligned across providers [(#10419)](https://github.com/prowler-cloud/prowler/pull/10419) +- Mute rule creation now triggers finding-group summary re-aggregation after historical muting, keeping stats in sync after mute operations [(#10419)](https://github.com/prowler-cloud/prowler/pull/10419) + ### 🔐 Security - Replace stdlib XML parser with `defusedxml` in SAML metadata parsing to prevent XML bomb (billion laughs) DoS attacks [(#10165)](https://github.com/prowler-cloud/prowler/pull/10165) diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index 7dd14bf420..b2e82d2ea1 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -45,6 +45,7 @@ from api.models import ( ComplianceRequirementOverview, DailySeveritySummary, Finding, + FindingGroupDailySummary, Integration, Invitation, LighthouseProviderConfiguration, @@ -14689,10 +14690,16 @@ class TestMuteRuleViewSet: assert len(data) == 2 assert data[0]["id"] == str(mute_rules_fixture[first_index].id) - @patch("tasks.tasks.mute_historical_findings_task.apply_async") + @patch("api.v1.views.chain") + @patch("api.v1.views.aggregate_finding_group_summaries_task.si") + @patch("api.v1.views.mute_historical_findings_task.si") + @patch("api.v1.views.transaction.on_commit", side_effect=lambda fn: fn()) def test_mute_rules_create_valid( self, - mock_task, + _mock_on_commit, + mock_mute_signature, + mock_aggregate_signature, + mock_chain, authenticated_client, findings_fixture, create_test_user, @@ -14730,8 +14737,14 @@ class TestMuteRuleViewSet: assert finding.muted_at is not None assert finding.muted_reason == "Security exception approved" - # Verify background task was called - mock_task.assert_called_once() + # Verify background task chain was called + mock_mute_signature.assert_called_once() + mock_aggregate_signature.assert_called_once() + mock_chain.assert_called_once_with( + mock_mute_signature.return_value, + mock_aggregate_signature.return_value, + ) + mock_chain.return_value.apply_async.assert_called_once() @patch("tasks.tasks.mute_historical_findings_task.apply_async") def test_mute_rules_create_converts_finding_ids_to_uids( @@ -15840,6 +15853,48 @@ class TestFindingGroupViewSet: assert len(data) == 1 assert data[0]["id"] == "cloudtrail_enabled" + def test_finding_groups_latest_aggregates_latest_per_provider( + self, authenticated_client, providers_fixture + ): + """Test /latest aggregates latest summary from each provider for the same check.""" + provider1 = providers_fixture[0] + provider2 = providers_fixture[1] + + check_id = "cross_provider_latest_resources_total" + now = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0) + + FindingGroupDailySummary.objects.create( + tenant_id=provider1.tenant_id, + provider=provider1, + check_id=check_id, + inserted_at=now - timedelta(days=1), + resources_total=20, + resources_fail=20, + fail_count=20, + ) + FindingGroupDailySummary.objects.create( + tenant_id=provider2.tenant_id, + provider=provider2, + check_id=check_id, + inserted_at=now, + resources_total=7, + resources_fail=7, + fail_count=7, + ) + + response = authenticated_client.get( + reverse("finding-group-latest"), + {"filter[check_id]": check_id}, + ) + + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + assert len(data) == 1 + attrs = data[0]["attributes"] + assert attrs["resources_total"] == 27 + assert attrs["resources_fail"] == 27 + assert attrs["fail_count"] == 27 + def test_finding_groups_latest_provider_type_filter( self, authenticated_client, finding_groups_fixture ): diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index 7c2de4a41c..85c3062965 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -16,6 +16,7 @@ from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter from allauth.socialaccount.providers.saml.views import FinishACSView, LoginView from botocore.exceptions import ClientError, NoCredentialsError, ParamValidationError +from celery import chain from celery.result import AsyncResult from config.custom_logging import BackendLogger from config.env import env @@ -81,6 +82,7 @@ from tasks.beat import schedule_provider_scan from tasks.jobs.attack_paths import db_utils as attack_paths_db_utils from tasks.jobs.export import get_s3_client from tasks.tasks import ( + aggregate_finding_group_summaries_task, backfill_compliance_summaries_task, backfill_scan_resource_summaries_task, check_integration_connection_task, @@ -6725,10 +6727,25 @@ class MuteRuleViewSet(BaseRLSViewSet): ) # Launch background task for historical muting - with transaction.atomic(): - mute_historical_findings_task.apply_async( - kwargs={"tenant_id": tenant_id, "mute_rule_id": str(mute_rule.id)} - ) + latest_scan_id = ( + Scan.objects.filter(tenant_id=tenant_id, state=StateChoices.COMPLETED) + .order_by("-completed_at", "-inserted_at") + .values_list("id", flat=True) + .first() + ) + + transaction.on_commit( + lambda: chain( + mute_historical_findings_task.si( + tenant_id=tenant_id, + mute_rule_id=str(mute_rule.id), + ), + aggregate_finding_group_summaries_task.si( + tenant_id=tenant_id, + scan_id=str(latest_scan_id), + ), + ).apply_async() + ) # Return the created mute rule serializer = self.get_serializer(mute_rule) @@ -7210,13 +7227,15 @@ class FindingGroupViewSet(BaseRLSViewSet): raise ValidationError(filterset.errors) filtered_queryset = filterset.qs - # Keep only rows from the latest inserted_at date per check_id - latest_per_check = filtered_queryset.annotate( - latest_inserted_at=Window( - expression=Max("inserted_at"), - partition_by=[F("check_id")], - ) - ).filter(inserted_at=F("latest_inserted_at")) + # Keep only the latest row per (check_id, provider), then aggregate by check_id. + latest_per_check_ids = ( + filtered_queryset.order_by("check_id", "provider_id", "-inserted_at") + .distinct("check_id", "provider_id") + .values("id") + ) + latest_per_check = filtered_queryset.filter( + id__in=Subquery(latest_per_check_ids) + ) # Re-aggregate daily summaries aggregated_queryset = self._aggregate_daily_summaries(latest_per_check) From 14356e31871cc18e258313214064b1c759e8b6dc Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:51:07 +0100 Subject: [PATCH 051/366] docs: add cookbooks section (#10410) Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com> --- docs/docs.json | 10 +- docs/user-guide/cookbooks/cicd-pipeline.mdx | 243 ++++++++++++++++++ .../cookbooks/kubernetes-in-cluster.mdx | 207 +++++++++++++++ .../kubernetes/getting-started-k8s.mdx | 4 + 4 files changed, 463 insertions(+), 1 deletion(-) create mode 100644 docs/user-guide/cookbooks/cicd-pipeline.mdx create mode 100644 docs/user-guide/cookbooks/kubernetes-in-cluster.mdx diff --git a/docs/docs.json b/docs/docs.json index 3ec056989f..a8fd6dedd6 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -225,7 +225,8 @@ "group": "Kubernetes", "pages": [ "user-guide/providers/kubernetes/getting-started-k8s", - "user-guide/providers/kubernetes/misc" + "user-guide/providers/kubernetes/misc", + "user-guide/cookbooks/kubernetes-in-cluster" ] }, { @@ -304,6 +305,13 @@ "pages": [ "user-guide/compliance/tutorials/threatscore" ] + }, + { + "group": "Cookbooks", + "pages": [ + "user-guide/cookbooks/kubernetes-in-cluster", + "user-guide/cookbooks/cicd-pipeline" + ] } ] }, diff --git a/docs/user-guide/cookbooks/cicd-pipeline.mdx b/docs/user-guide/cookbooks/cicd-pipeline.mdx new file mode 100644 index 0000000000..9dffd5049c --- /dev/null +++ b/docs/user-guide/cookbooks/cicd-pipeline.mdx @@ -0,0 +1,243 @@ +--- +title: 'Run Prowler in CI/CD and Send Findings to Prowler Cloud' +--- + +This cookbook demonstrates how to integrate Prowler into CI/CD pipelines so that security scans run automatically and findings are sent to Prowler Cloud via [Import Findings](/user-guide/tutorials/prowler-app-import-findings). Examples cover GitHub Actions and GitLab CI. + +## Prerequisites + +* A **Prowler Cloud** account with an active subscription (see [Prowler Cloud Pricing](https://prowler.com/pricing)) +* A Prowler Cloud **API key** with the **Manage Ingestions** permission (see [API Keys](/user-guide/tutorials/prowler-app-api-keys)) +* Cloud provider credentials configured in the CI/CD environment (e.g., AWS credentials for scanning AWS accounts) +* Access to configure pipeline workflows and secrets in the CI/CD platform + +## Key Concepts + +Prowler CLI provides the `--push-to-cloud` flag, which uploads scan results directly to Prowler Cloud after a scan completes. Combined with the `PROWLER_CLOUD_API_KEY` environment variable, this enables fully automated ingestion without manual file uploads. + +For full details on the flag and API, refer to the [Import Findings](/user-guide/tutorials/prowler-app-import-findings) documentation. + + +The examples in this guide use AWS as the target provider, but the same approach applies to any provider supported by Prowler (Azure, GCP, Kubernetes, and others). Replace `prowler aws` with the desired provider command (e.g., `prowler gcp`, `prowler azure`) and configure the corresponding credentials in the CI/CD environment. + + +## GitHub Actions + +### Store Secrets + +Before creating the workflow, add the following secrets to the repository (under "Settings" > "Secrets and variables" > "Actions"): + +* `PROWLER_CLOUD_API_KEY` — the Prowler Cloud API key +* Cloud provider credentials (e.g., `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, or configure OIDC-based role assumption) + +### Workflow: Scheduled AWS Scan + +This workflow runs Prowler against an AWS account on a daily schedule and on every push to the `main` branch: + +```yaml +name: Prowler Security Scan + +on: + schedule: + - cron: "0 3 * * *" # Daily at 03:00 UTC + push: + branches: [main] + workflow_dispatch: # Allow manual triggers + +permissions: + id-token: write # Required for OIDC + contents: read + +jobs: + prowler-scan: + runs-on: ubuntu-latest + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::123456789012:role/ProwlerScanRole + aws-region: us-east-1 + + - name: Install Prowler + run: pip install prowler + + - name: Run Prowler Scan + env: + PROWLER_CLOUD_API_KEY: ${{ secrets.PROWLER_CLOUD_API_KEY }} + run: | + prowler aws --push-to-cloud +``` + + +Replace `123456789012` with the actual AWS account ID and `ProwlerScanRole` with the IAM role name. For IAM role setup, refer to the [AWS authentication guide](/user-guide/providers/aws/authentication). + + +### Workflow: Scan Specific Services on Pull Request + +To run targeted scans on pull requests without blocking the merge pipeline, use `continue-on-error`: + +```yaml +name: Prowler PR Check + +on: + pull_request: + branches: [main] + +jobs: + prowler-scan: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::123456789012:role/ProwlerScanRole + aws-region: us-east-1 + + - name: Install Prowler + run: pip install prowler + + - name: Run Prowler Scan + env: + PROWLER_CLOUD_API_KEY: ${{ secrets.PROWLER_CLOUD_API_KEY }} + run: | + prowler aws --services s3,iam,ec2 --push-to-cloud +``` + + +Limiting the scan to specific services with `--services` reduces execution time, making it practical for pull request checks. + + +## GitLab CI + +### Store Variables + +Add the following CI/CD variables in the GitLab project (under "Settings" > "CI/CD" > "Variables"): + +* `PROWLER_CLOUD_API_KEY` — mark as **masked** and **protected** +* Cloud provider credentials as needed + +### Pipeline: Scheduled AWS Scan + +Add the following to `.gitlab-ci.yml`: + +```yaml +prowler-scan: + image: python:3.12-slim + stage: test + script: + - pip install prowler + - prowler aws --push-to-cloud + variables: + PROWLER_CLOUD_API_KEY: $PROWLER_CLOUD_API_KEY + AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY + AWS_DEFAULT_REGION: "us-east-1" + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + when: manual +``` + +To run the scan on a schedule, create a **Pipeline Schedule** in GitLab (under "Build" > "Pipeline Schedules") with the desired cron expression. + +### Pipeline: Multi-Provider Scan + +To scan multiple cloud providers in parallel: + +```yaml +stages: + - security + +.prowler-base: + image: python:3.12-slim + stage: security + before_script: + - pip install prowler + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + +prowler-aws: + extends: .prowler-base + script: + - prowler aws --push-to-cloud + variables: + PROWLER_CLOUD_API_KEY: $PROWLER_CLOUD_API_KEY + AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY + +prowler-gcp: + extends: .prowler-base + script: + - prowler gcp --push-to-cloud + variables: + PROWLER_CLOUD_API_KEY: $PROWLER_CLOUD_API_KEY + GOOGLE_APPLICATION_CREDENTIALS: $GCP_SERVICE_ACCOUNT_KEY +``` + +## Tips and Best Practices + +### When to Run Scans + +* **Scheduled scans** (daily or weekly) provide continuous monitoring and are ideal for baseline security assessments +* **On-merge scans** catch configuration changes introduced by new code +* **Pull request scans** provide early feedback but should target specific services to keep execution times reasonable + +### Handling Scan Failures + +By default, Prowler exits with a non-zero code when it finds failing checks. This causes the CI/CD job to fail. To prevent scan results from blocking the pipeline: + +* **GitHub Actions**: Add `continue-on-error: true` to the job +* **GitLab CI**: Add `allow_failure: true` to the job + + +Ingestion failures (e.g., network issues reaching Prowler Cloud) do not affect the Prowler exit code. The scan completes normally and only a warning is emitted. See [Import Findings troubleshooting](/user-guide/tutorials/prowler-app-import-findings#troubleshooting) for details. + + +### Caching Prowler Installation + +For faster pipeline runs, cache the Prowler installation: + +**GitHub Actions:** +```yaml +- name: Cache pip packages + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-prowler + restore-keys: ${{ runner.os }}-pip- + +- name: Install Prowler + run: pip install prowler +``` + +**GitLab CI:** +```yaml +prowler-scan: + cache: + paths: + - .cache/pip + variables: + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" +``` + +### Output Formats + +To generate additional report formats alongside the cloud upload: + +```bash +prowler aws --push-to-cloud -M csv,html -o /tmp/prowler-reports +``` + +This produces CSV and HTML files locally while also pushing OCSF findings to Prowler Cloud. The local files can be stored as CI/CD artifacts for archival purposes. + +### Scanning Multiple AWS Accounts + +To scan multiple accounts sequentially in a single job, use [role assumption](/user-guide/providers/aws/role-assumption): + +```bash +prowler aws -R arn:aws:iam::111111111111:role/ProwlerScanRole --push-to-cloud +prowler aws -R arn:aws:iam::222222222222:role/ProwlerScanRole --push-to-cloud +``` + +Each scan run creates a separate ingestion job in Prowler Cloud. diff --git a/docs/user-guide/cookbooks/kubernetes-in-cluster.mdx b/docs/user-guide/cookbooks/kubernetes-in-cluster.mdx new file mode 100644 index 0000000000..765bcf9313 --- /dev/null +++ b/docs/user-guide/cookbooks/kubernetes-in-cluster.mdx @@ -0,0 +1,207 @@ +--- +title: 'Run Kubernetes In-Cluster and Send Findings to Prowler Cloud' +--- + +This cookbook walks through deploying Prowler inside a Kubernetes cluster on a recurring schedule and automatically sending findings to Prowler Cloud via [Import Findings](/user-guide/tutorials/prowler-app-import-findings). By the end, security scan results from the cluster appear in Prowler Cloud without any manual file uploads. + +## Prerequisites + +* A **Prowler Cloud** account with an active subscription (see [Prowler Cloud Pricing](https://prowler.com/pricing)) +* A Prowler Cloud **API key** with the **Manage Ingestions** permission (see [API Keys](/user-guide/tutorials/prowler-app-api-keys)) +* Access to a Kubernetes cluster with `kubectl` configured +* Permissions to create ServiceAccounts, Roles, RoleBindings, Secrets, and CronJobs in the cluster + +## Step 1: Create the ServiceAccount and RBAC Resources + +Prowler needs a ServiceAccount with read access to cluster resources. Apply the manifests from the [`kubernetes` directory](https://github.com/prowler-cloud/prowler/tree/master/kubernetes) of the Prowler repository: + +```console +kubectl apply -f kubernetes/prowler-sa.yaml +kubectl apply -f kubernetes/prowler-role.yaml +kubectl apply -f kubernetes/prowler-rolebinding.yaml +``` + +This creates: + +* A `prowler-sa` ServiceAccount in the `prowler-ns` namespace +* A ClusterRole with the read permissions Prowler requires +* A ClusterRoleBinding linking the ServiceAccount to the role + +For more details on these resources, refer to [Getting Started with Kubernetes](/user-guide/providers/kubernetes/getting-started-k8s). + +## Step 2: Store the Prowler Cloud API Key as a Secret + +Create a Kubernetes Secret to hold the API key securely: + +```console +kubectl create secret generic prowler-cloud-api-key \ + --from-literal=api-key=pk_your_api_key_here \ + --namespace prowler-ns +``` + +Replace `pk_your_api_key_here` with the actual API key from Prowler Cloud. + + +Avoid embedding the API key directly in the CronJob manifest. Using a Kubernetes Secret keeps credentials out of version control and pod specs. + + +## Step 3: Create the CronJob Manifest + +The CronJob runs Prowler on a schedule, scanning the cluster and pushing findings to Prowler Cloud with the `--push-to-cloud` flag. + +Create a file named `prowler-cronjob.yaml`: + +```yaml +apiVersion: batch/v1 +kind: CronJob +metadata: + name: prowler-k8s-scan + namespace: prowler-ns +spec: + schedule: "0 2 * * *" # Runs daily at 02:00 UTC + concurrencyPolicy: Forbid + jobTemplate: + spec: + backoffLimit: 1 + template: + metadata: + labels: + app: prowler + spec: + serviceAccountName: prowler-sa + containers: + - name: prowler + image: prowlercloud/prowler:stable + args: + - "kubernetes" + - "--push-to-cloud" + env: + - name: PROWLER_CLOUD_API_KEY + valueFrom: + secretKeyRef: + name: prowler-cloud-api-key + key: api-key + - name: CLUSTER_NAME + value: "my-cluster" + imagePullPolicy: Always + volumeMounts: + - name: var-lib-cni + mountPath: /var/lib/cni + readOnly: true + - name: var-lib-etcd + mountPath: /var/lib/etcd + readOnly: true + - name: var-lib-kubelet + mountPath: /var/lib/kubelet + readOnly: true + - name: etc-kubernetes + mountPath: /etc/kubernetes + readOnly: true + hostPID: true + restartPolicy: Never + volumes: + - name: var-lib-cni + hostPath: + path: /var/lib/cni + - name: var-lib-etcd + hostPath: + path: /var/lib/etcd + - name: var-lib-kubelet + hostPath: + path: /var/lib/kubelet + - name: etc-kubernetes + hostPath: + path: /etc/kubernetes +``` + + +Replace `my-cluster` with a meaningful name for the cluster. This value appears in Prowler Cloud reports and helps identify the source of findings. See the `--cluster-name` flag documentation in [Getting Started with Kubernetes](/user-guide/providers/kubernetes/getting-started-k8s) for more details. + + +### Customizing the Schedule + +The `schedule` field uses standard cron syntax. Common examples: + +* `"0 2 * * *"` — daily at 02:00 UTC +* `"0 */6 * * *"` — every 6 hours +* `"0 2 * * 1"` — weekly on Mondays at 02:00 UTC + +### Scanning Specific Namespaces + +To limit the scan to specific namespaces, add the `--namespace` flag to the `args` array: + +```yaml +args: + - "kubernetes" + - "--push-to-cloud" + - "--namespace" + - "production,staging" +``` + +## Step 4: Deploy and Verify + +Apply the CronJob to the cluster: + +```console +kubectl apply -f prowler-cronjob.yaml +``` + +To trigger an immediate test run without waiting for the schedule: + +```console +kubectl create job prowler-test-run --from=cronjob/prowler-k8s-scan -n prowler-ns +``` + +Monitor the job execution: + +```console +kubectl get pods -n prowler-ns -l app=prowler --watch +``` + +Check the logs to confirm findings were pushed successfully: + +```console +kubectl logs -n prowler-ns -l app=prowler --tail=50 +``` + +A successful upload produces output similar to: + +``` +Pushing findings to Prowler Cloud, please wait... + +Findings successfully pushed to Prowler Cloud. Ingestion job: fa8bc8c5-4925-46a0-9fe0-f6575905e094 +See more details here: https://cloud.prowler.com/scans +``` + +## Step 5: View Findings in Prowler Cloud + +Once the job completes and findings are pushed: + +1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) +2. Open the "Scans" section to verify the ingestion job status +3. Browse findings under the Kubernetes provider + +For details on the ingestion workflow and status tracking, refer to the [Import Findings](/user-guide/tutorials/prowler-app-import-findings) documentation. + +## Tips and Troubleshooting + +* **Resource limits**: For large clusters, consider setting `resources.requests` and `resources.limits` on the container to prevent the scan from consuming excessive cluster resources. +* **Network policies**: Ensure the Prowler pod can reach `api.prowler.com` over HTTPS (port 443). Adjust NetworkPolicies or egress rules if needed. +* **Job history**: Kubernetes retains completed and failed jobs by default. Set `successfulJobsHistoryLimit` and `failedJobsHistoryLimit` in the CronJob spec to control cleanup: + + ```yaml + spec: + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 1 + ``` + +* **API key rotation**: When rotating the API key, update the Secret and restart any running jobs: + + ```console + kubectl delete secret prowler-cloud-api-key -n prowler-ns + kubectl create secret generic prowler-cloud-api-key \ + --from-literal=api-key=pk_new_api_key_here \ + --namespace prowler-ns + ``` + +* **Failed uploads**: If the push to Prowler Cloud fails, the scan still completes and findings are saved locally in the container. Check the [Import Findings troubleshooting section](/user-guide/tutorials/prowler-app-import-findings#troubleshooting) for common error messages. diff --git a/docs/user-guide/providers/kubernetes/getting-started-k8s.mdx b/docs/user-guide/providers/kubernetes/getting-started-k8s.mdx index 0ec8215776..c4f4822792 100644 --- a/docs/user-guide/providers/kubernetes/getting-started-k8s.mdx +++ b/docs/user-guide/providers/kubernetes/getting-started-k8s.mdx @@ -164,3 +164,7 @@ env: ``` + + +To set up a production-ready CronJob that runs Prowler on a schedule and sends findings to Prowler Cloud, see the [Run Kubernetes In-Cluster and Send Findings to Prowler Cloud](/user-guide/cookbooks/kubernetes-in-cluster) cookbook. + From c62ac6c71bbf5d3754504e45f8c6d5f858731085 Mon Sep 17 00:00:00 2001 From: Prowler Bot Date: Mon, 23 Mar 2026 15:26:29 +0100 Subject: [PATCH 052/366] feat(aws): Update regions for AWS services (#10076) Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com> From 1015f1379fbb4745097129599ae30e8e3090596a Mon Sep 17 00:00:00 2001 From: Prowler Bot Date: Mon, 23 Mar 2026 15:28:51 +0100 Subject: [PATCH 053/366] feat(aws): Update regions for AWS services (#10413) Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com> --- .../providers/aws/aws_regions_by_service.json | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/prowler/providers/aws/aws_regions_by_service.json b/prowler/providers/aws/aws_regions_by_service.json index 3a414e5f77..297921370d 100644 --- a/prowler/providers/aws/aws_regions_by_service.json +++ b/prowler/providers/aws/aws_regions_by_service.json @@ -1587,6 +1587,7 @@ "ap-northeast-1", "ap-south-1", "ap-southeast-2", + "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", @@ -1670,20 +1671,8 @@ "budgets": { "regions": { "aws": [ - "ap-northeast-1", - "ap-northeast-2", - "ap-south-1", - "ap-southeast-1", - "ap-southeast-2", "ca-central-1", - "eu-central-1", - "eu-west-1", - "eu-west-2", - "eu-west-3", - "sa-east-1", "us-east-1", - "us-east-2", - "us-west-1", "us-west-2" ], "aws-cn": [ @@ -3439,7 +3428,6 @@ "datazone": { "regions": { "aws": [ - "af-south-1", "ap-east-1", "ap-northeast-1", "ap-northeast-2", @@ -3452,7 +3440,6 @@ "eu-central-1", "eu-central-2", "eu-north-1", - "eu-south-2", "eu-west-1", "eu-west-2", "eu-west-3", @@ -6998,6 +6985,7 @@ "aws": [ "af-south-1", "ap-east-1", + "ap-east-2", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", @@ -7022,6 +7010,7 @@ "il-central-1", "me-central-1", "me-south-1", + "mx-central-1", "sa-east-1", "us-east-1", "us-east-2", @@ -7695,6 +7684,7 @@ "ap-southeast-1", "ap-southeast-2", "ap-southeast-4", + "ap-southeast-5", "ca-central-1", "eu-central-1", "eu-north-1", @@ -7932,6 +7922,7 @@ "aws": [ "ap-southeast-2", "eu-west-1", + "eu-west-2", "us-east-1", "us-west-2" ], @@ -8255,6 +8246,7 @@ "ap-east-1", "ap-northeast-1", "ap-northeast-2", + "ap-northeast-3", "ap-south-1", "ap-southeast-1", "ap-southeast-2", @@ -8270,6 +8262,7 @@ "sa-east-1", "us-east-1", "us-east-2", + "us-west-1", "us-west-2" ], "aws-cn": [], @@ -9877,6 +9870,7 @@ "eu-west-1", "eu-west-2", "il-central-1", + "sa-east-1", "us-east-1", "us-east-2", "us-west-1", From 114e86c0dc2f9b301d735a2f5c77d79f6681ec2c Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:21:31 +0000 Subject: [PATCH 054/366] fix(sdk): ignore disabled users in Entra MFA check (#10426) --- prowler/CHANGELOG.md | 1 + .../entra_non_privileged_user_has_mfa.py | 2 +- .../azure/services/entra/entra_service.py | 15 +++- .../entra_non_privileged_user_has_mfa_test.py | 80 +++++++++++++++++++ .../services/entra/entra_service_test.py | 14 +++- 5 files changed, 108 insertions(+), 4 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 2c8b47daf4..9582f7123a 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. - Azure MySQL flexible server checks now compare configuration values case-insensitively to avoid false negatives when Azure returns lowercase values [(#10396)](https://github.com/prowler-cloud/prowler/pull/10396) - Azure `vm_backup_enabled` and `vm_sufficient_daily_backup_retention_period` checks now compare VM names case-insensitively to avoid false negatives when Azure stores backup item names in a different case [(#10395)](https://github.com/prowler-cloud/prowler/pull/10395) +- `entra_non_privileged_user_has_mfa` skips disabled users to avoid false positives [(#10426)](https://github.com/prowler-cloud/prowler/pull/10426) --- diff --git a/prowler/providers/azure/services/entra/entra_non_privileged_user_has_mfa/entra_non_privileged_user_has_mfa.py b/prowler/providers/azure/services/entra/entra_non_privileged_user_has_mfa/entra_non_privileged_user_has_mfa.py index c86fc02da7..d231a7a6b1 100644 --- a/prowler/providers/azure/services/entra/entra_non_privileged_user_has_mfa/entra_non_privileged_user_has_mfa.py +++ b/prowler/providers/azure/services/entra/entra_non_privileged_user_has_mfa/entra_non_privileged_user_has_mfa.py @@ -11,7 +11,7 @@ class entra_non_privileged_user_has_mfa(Check): for tenant_domain, users in entra_client.users.items(): for user in users.values(): - if not is_privileged_user( + if user.account_enabled and not is_privileged_user( user, entra_client.directory_roles[tenant_domain] ): report = Check_Report_Azure(metadata=self.metadata(), resource=user) diff --git a/prowler/providers/azure/services/entra/entra_service.py b/prowler/providers/azure/services/entra/entra_service.py index 011ea675b0..eb1d62ac11 100644 --- a/prowler/providers/azure/services/entra/entra_service.py +++ b/prowler/providers/azure/services/entra/entra_service.py @@ -3,7 +3,9 @@ from asyncio import gather from typing import List, Optional from uuid import UUID +from kiota_abstractions.base_request_configuration import RequestConfiguration from msgraph import GraphServiceClient +from msgraph.generated.users.users_request_builder import UsersRequestBuilder from pydantic.v1 import BaseModel from prowler.lib.logger import logger @@ -65,9 +67,16 @@ class Entra(AzureService): logger.info("Entra - Getting users...") users = {} try: + request_configuration = RequestConfiguration( + query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters( + select=["id", "displayName", "accountEnabled"] + ) + ) for tenant, client in self.clients.items(): users.update({tenant: {}}) - users_response = await client.users.get() + users_response = await client.users.get( + request_configuration=request_configuration + ) registration_details = await self._get_user_registration_details(client) try: @@ -81,6 +90,9 @@ class Entra(AzureService): is_mfa_capable=registration_details.get( user.id, False ), + account_enabled=getattr( + user, "account_enabled", True + ), ) } ) @@ -409,6 +421,7 @@ class User(BaseModel): id: str name: str is_mfa_capable: bool = False + account_enabled: bool = True class DefaultUserRolePermissions(BaseModel): diff --git a/tests/providers/azure/services/entra/entra_non_privileged_user_has_mfa/entra_non_privileged_user_has_mfa_test.py b/tests/providers/azure/services/entra/entra_non_privileged_user_has_mfa/entra_non_privileged_user_has_mfa_test.py index 4667b665ed..4d2f289a90 100644 --- a/tests/providers/azure/services/entra/entra_non_privileged_user_has_mfa/entra_non_privileged_user_has_mfa_test.py +++ b/tests/providers/azure/services/entra/entra_non_privileged_user_has_mfa/entra_non_privileged_user_has_mfa_test.py @@ -142,6 +142,86 @@ class Test_entra_non_privileged_user_has_mfa: assert result[0].resource_id == user_id assert result[0].subscription == f"Tenant: {DOMAIN}" + def test_entra_disabled_user_no_privileged_no_mfa(self): + entra_client = mock.MagicMock + user_id = str(uuid4()) + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa.entra_client", + new=entra_client, + ), + ): + from prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa import ( + entra_non_privileged_user_has_mfa, + ) + from prowler.providers.azure.services.entra.entra_service import ( + DirectoryRole, + User, + ) + + user = User( + id=user_id, + name="foo", + is_mfa_capable=False, + account_enabled=False, + ) + + entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}} + entra_client.directory_roles = { + DOMAIN: { + "Global Administrator": DirectoryRole(id=str(uuid4()), members=[]) + } + } + + check = entra_non_privileged_user_has_mfa() + result = check.execute() + assert len(result) == 0 + + def test_entra_disabled_user_no_privileged_mfa(self): + entra_client = mock.MagicMock + user_id = str(uuid4()) + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), + mock.patch( + "prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa.entra_client", + new=entra_client, + ), + ): + from prowler.providers.azure.services.entra.entra_non_privileged_user_has_mfa.entra_non_privileged_user_has_mfa import ( + entra_non_privileged_user_has_mfa, + ) + from prowler.providers.azure.services.entra.entra_service import ( + DirectoryRole, + User, + ) + + user = User( + id=user_id, + name="foo", + is_mfa_capable=True, + account_enabled=False, + ) + + entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}} + entra_client.directory_roles = { + DOMAIN: { + "Global Administrator": DirectoryRole(id=str(uuid4()), members=[]) + } + } + + check = entra_non_privileged_user_has_mfa() + result = check.execute() + assert len(result) == 0 + def test_entra_user_privileged_no_mfa(self): entra_client = mock.MagicMock user_id = str(uuid4()) diff --git a/tests/providers/azure/services/entra/entra_service_test.py b/tests/providers/azure/services/entra/entra_service_test.py index 8e3a25e59f..75ef4f98c4 100644 --- a/tests/providers/azure/services/entra/entra_service_test.py +++ b/tests/providers/azure/services/entra/entra_service_test.py @@ -147,6 +147,7 @@ class Test_Entra_Service: assert entra_client.users[DOMAIN]["user-1@tenant1.es"].id == "id-1" assert entra_client.users[DOMAIN]["user-1@tenant1.es"].name == "User 1" assert entra_client.users[DOMAIN]["user-1@tenant1.es"].is_mfa_capable is False + assert entra_client.users[DOMAIN]["user-1@tenant1.es"].account_enabled is True def test_get_authorization_policy(self): entra_client = Entra(set_mocked_azure_provider()) @@ -229,8 +230,8 @@ def test_azure_entra__get_users_handles_pagination(): entra_service = Entra.__new__(Entra) users_page_one = [ - SimpleNamespace(id="user-1", display_name="User 1"), - SimpleNamespace(id="user-2", display_name="User 2"), + SimpleNamespace(id="user-1", display_name="User 1", account_enabled=False), + SimpleNamespace(id="user-2", display_name="User 2", account_enabled=True), ] users_page_two = [ SimpleNamespace(id="user-3", display_name="User 3"), @@ -288,9 +289,18 @@ def test_azure_entra__get_users_handles_pagination(): assert len(users["tenant-1"]) == 3 assert users_builder.get.await_count == 1 + request_configuration = users_builder.get.await_args.kwargs["request_configuration"] + assert request_configuration.query_parameters.select == [ + "id", + "displayName", + "accountEnabled", + ] with_url_mock.assert_called_once_with("next-link") registration_details_builder.get.assert_awaited() registration_details_builder.with_url.assert_not_called() assert users["tenant-1"]["user-1"].is_mfa_capable is True + assert users["tenant-1"]["user-1"].account_enabled is False assert users["tenant-1"]["user-2"].is_mfa_capable is True + assert users["tenant-1"]["user-2"].account_enabled is True assert users["tenant-1"]["user-3"].is_mfa_capable is False + assert users["tenant-1"]["user-3"].account_enabled is True From 41629137efdec1ade078e4386f738c8e0ffce94b Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:22:54 +0100 Subject: [PATCH 055/366] docs: remove cookbook from k8s section (#10427) --- docs/docs.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index a8fd6dedd6..6fcd387a07 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -225,8 +225,7 @@ "group": "Kubernetes", "pages": [ "user-guide/providers/kubernetes/getting-started-k8s", - "user-guide/providers/kubernetes/misc", - "user-guide/cookbooks/kubernetes-in-cluster" + "user-guide/providers/kubernetes/misc" ] }, { From 49ba25ba07e11f84a583a52cd542d915745a1a48 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:36:37 +0000 Subject: [PATCH 056/366] feat(ui): add custom attack paths queries (#10397) --- ui/CHANGELOG.md | 8 + .../attack-paths/queries.adapter.test.ts | 54 ++++++ ui/actions/attack-paths/queries.adapter.ts | 52 ++++++ ui/actions/attack-paths/queries.test.ts | 142 ++++++++++++++- ui/actions/attack-paths/queries.ts | 94 ++++++++++ .../query-builder/_components/index.ts | 2 + .../_components/query-description.test.tsx | 76 ++++++++ .../_components/query-description.tsx | 70 ++++++++ .../query-execution-error.test.tsx | 46 +++++ .../_components/query-execution-error.tsx | 30 ++++ .../query-parameters-form.test.tsx | 122 ++++++++++++- .../_components/query-parameters-form.tsx | 166 ++++++++++++------ .../_hooks/use-query-builder.test.tsx | 123 +++++++++++++ .../query-builder/_hooks/use-query-builder.ts | 27 ++- .../query-builder/attack-paths-page.tsx | 90 ++++------ ui/components/shadcn/index.ts | 1 + ui/lib/attack-paths/custom-query.ts | 42 +++++ ui/types/attack-paths.ts | 50 ++++++ 18 files changed, 1086 insertions(+), 109 deletions(-) create mode 100644 ui/actions/attack-paths/queries.adapter.test.ts create mode 100644 ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-description.test.tsx create mode 100644 ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-description.tsx create mode 100644 ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-execution-error.test.tsx create mode 100644 ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-execution-error.tsx create mode 100644 ui/lib/attack-paths/custom-query.ts diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 2c17bcc5d6..c20aa5bf99 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to the **Prowler UI** are documented in this file. +## [1.22.0] (Prowler UNRELEASED) + +### 🚀 Added + +- Attack Paths custom openCypher queries with Cartography schema guidance and clearer execution errors [(#10397)](https://github.com/prowler-cloud/prowler/pull/10397) + +--- + ## [1.21.0] (Prowler v5.21.0) ### 🚀 Added diff --git a/ui/actions/attack-paths/queries.adapter.test.ts b/ui/actions/attack-paths/queries.adapter.test.ts new file mode 100644 index 0000000000..cd6bafd206 --- /dev/null +++ b/ui/actions/attack-paths/queries.adapter.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from "vitest"; + +import { + ATTACK_PATH_QUERY_IDS, + type AttackPathCartographySchemaAttributes, + type AttackPathQuery, +} from "@/types/attack-paths"; + +import { buildAttackPathQueries } from "./queries.adapter"; + +const presetQuery: AttackPathQuery = { + type: "attack-paths-scans", + id: "preset-query", + attributes: { + name: "Preset Query", + short_description: "Returns privileged attack paths", + description: "Returns privileged attack paths.", + provider: "aws", + attribution: null, + parameters: [], + }, +}; + +describe("buildAttackPathQueries", () => { + it("prepends a custom query with a schema documentation link", () => { + // Given + const schema: AttackPathCartographySchemaAttributes = { + id: "aws-0.129.0", + provider: "aws", + cartography_version: "0.129.0", + schema_url: + "https://github.com/cartography-cncf/cartography/blob/0.129.0/docs/root/modules/aws/schema.md", + raw_schema_url: + "https://raw.githubusercontent.com/cartography-cncf/cartography/refs/tags/0.129.0/docs/root/modules/aws/schema.md", + }; + + // When + const result = buildAttackPathQueries([presetQuery], schema); + + // Then + expect(result[0]).toMatchObject({ + id: ATTACK_PATH_QUERY_IDS.CUSTOM, + attributes: { + name: "Custom openCypher query", + short_description: "Write and run your own read-only query", + documentation_link: { + text: "Cartography schema used by Prowler for AWS graphs", + link: schema.schema_url, + }, + }, + }); + expect(result[1]).toEqual(presetQuery); + }); +}); diff --git a/ui/actions/attack-paths/queries.adapter.ts b/ui/actions/attack-paths/queries.adapter.ts index fd256739e1..016abde60e 100644 --- a/ui/actions/attack-paths/queries.adapter.ts +++ b/ui/actions/attack-paths/queries.adapter.ts @@ -1,7 +1,10 @@ import { MetaDataProps } from "@/types"; import { + ATTACK_PATH_QUERY_IDS, + type AttackPathCartographySchemaAttributes, AttackPathQueriesResponse, AttackPathQuery, + QUERY_PARAMETER_INPUT_TYPES, } from "@/types/attack-paths"; /** @@ -53,3 +56,52 @@ export function adaptAttackPathQueriesResponse( return { data: enrichedData, metadata }; } + +const CUSTOM_QUERY_PLACEHOLDER = `MATCH (n) +RETURN n +LIMIT 25`; + +const formatSchemaDocumentationLinkText = ( + schema: AttackPathCartographySchemaAttributes, +): string => { + return `Cartography schema used by Prowler for ${schema.provider.toUpperCase()} graphs`; +}; + +const createCustomQuery = ( + schema?: AttackPathCartographySchemaAttributes, +): AttackPathQuery => ({ + type: "attack-paths-scans", + id: ATTACK_PATH_QUERY_IDS.CUSTOM, + attributes: { + name: "Custom openCypher query", + short_description: "Write and run your own read-only query", + description: + "Run a read-only openCypher query against the selected Attack Paths scan. Results are automatically scoped to the selected provider.", + provider: "custom", + attribution: null, + documentation_link: schema + ? { + text: formatSchemaDocumentationLinkText(schema), + link: schema.schema_url, + } + : null, + parameters: [ + { + name: "query", + label: "openCypher", + data_type: "string", + description: "", + placeholder: CUSTOM_QUERY_PLACEHOLDER, + required: true, + input_type: QUERY_PARAMETER_INPUT_TYPES.TEXTAREA, + }, + ], + }, +}); + +export const buildAttackPathQueries = ( + queries: AttackPathQuery[], + schema?: AttackPathCartographySchemaAttributes, +): AttackPathQuery[] => { + return [createCustomQuery(schema), ...queries]; +}; diff --git a/ui/actions/attack-paths/queries.test.ts b/ui/actions/attack-paths/queries.test.ts index 6c2be5f15d..ab3afc447f 100644 --- a/ui/actions/attack-paths/queries.test.ts +++ b/ui/actions/attack-paths/queries.test.ts @@ -17,7 +17,11 @@ vi.mock("@/lib/server-actions-helper", () => ({ handleApiResponse: handleApiResponseMock, })); -import { executeQuery } from "./queries"; +import { + executeCustomQuery, + executeQuery, + getCartographySchema, +} from "./queries"; describe("executeQuery", () => { beforeEach(() => { @@ -65,3 +69,139 @@ describe("executeQuery", () => { expect(handleApiResponseMock).not.toHaveBeenCalled(); }); }); + +describe("executeCustomQuery", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.stubGlobal("fetch", fetchMock); + getAuthHeadersMock.mockResolvedValue({ Authorization: "Bearer token" }); + handleApiResponseMock.mockResolvedValue({ + data: { + type: "attack-paths-query-run-requests", + id: null, + attributes: { + nodes: [], + relationships: [], + }, + }, + }); + }); + + it("posts the custom query to the dedicated endpoint", async () => { + // Given + fetchMock.mockResolvedValue(new Response(null, { status: 200 })); + + // When + await executeCustomQuery( + "550e8400-e29b-41d4-a716-446655440000", + "MATCH (n) RETURN n LIMIT 10", + ); + + // Then + expect(fetchMock).toHaveBeenCalledWith( + "https://api.example.com/api/v1/attack-paths-scans/550e8400-e29b-41d4-a716-446655440000/queries/custom", + expect.objectContaining({ + method: "POST", + body: JSON.stringify({ + data: { + type: "attack-paths-custom-query-run-requests", + attributes: { + query: "MATCH (n) RETURN n LIMIT 10", + }, + }, + }), + }), + ); + }); + + it("rejects empty custom queries before calling the API", async () => { + // When + const result = await executeCustomQuery( + "550e8400-e29b-41d4-a716-446655440000", + " ", + ); + + // Then + expect(result).toEqual({ + error: "Custom query cannot be empty", + status: 400, + }); + expect(fetchMock).not.toHaveBeenCalled(); + expect(handleApiResponseMock).not.toHaveBeenCalled(); + }); + + it("rejects custom queries longer than 10000 characters before calling the API", async () => { + // When + const result = await executeCustomQuery( + "550e8400-e29b-41d4-a716-446655440000", + "x".repeat(10001), + ); + + // Then + expect(result).toEqual({ + error: "Custom query must be 10000 characters or fewer", + status: 400, + }); + expect(fetchMock).not.toHaveBeenCalled(); + expect(handleApiResponseMock).not.toHaveBeenCalled(); + }); + + it("rejects custom queries with write operations before calling the API", async () => { + // When + const result = await executeCustomQuery( + "550e8400-e29b-41d4-a716-446655440000", + "MATCH (n) SET n.name = 'updated' RETURN n", + ); + + // Then + expect(result).toEqual({ + error: "Only read-only queries are allowed", + status: 400, + }); + expect(fetchMock).not.toHaveBeenCalled(); + expect(handleApiResponseMock).not.toHaveBeenCalled(); + }); +}); + +describe("getCartographySchema", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.stubGlobal("fetch", fetchMock); + getAuthHeadersMock.mockResolvedValue({ Authorization: "Bearer token" }); + }); + + it("fetches the schema metadata for the selected scan", async () => { + // Given + const apiResponse = { + data: { + type: "attack-paths-cartography-schemas", + id: "aws-0.129.0", + attributes: { + id: "aws-0.129.0", + provider: "aws", + cartography_version: "0.129.0", + schema_url: + "https://github.com/cartography-cncf/cartography/blob/0.129.0/docs/root/modules/aws/schema.md", + raw_schema_url: + "https://raw.githubusercontent.com/cartography-cncf/cartography/refs/tags/0.129.0/docs/root/modules/aws/schema.md", + }, + }, + }; + fetchMock.mockResolvedValue(new Response(null, { status: 200 })); + handleApiResponseMock.mockResolvedValue(apiResponse); + + // When + const result = await getCartographySchema( + "550e8400-e29b-41d4-a716-446655440000", + ); + + // Then + expect(fetchMock).toHaveBeenCalledWith( + "https://api.example.com/api/v1/attack-paths-scans/550e8400-e29b-41d4-a716-446655440000/schema", + expect.objectContaining({ + method: "GET", + }), + ); + expect(result).toEqual(apiResponse); + }); +}); diff --git a/ui/actions/attack-paths/queries.ts b/ui/actions/attack-paths/queries.ts index bc9068d52e..228c1cca0c 100644 --- a/ui/actions/attack-paths/queries.ts +++ b/ui/actions/attack-paths/queries.ts @@ -3,12 +3,16 @@ import { z } from "zod"; import { apiBaseUrl, getAuthHeaders } from "@/lib"; +import { customAttackPathQuerySchema } from "@/lib/attack-paths/custom-query"; import { handleApiResponse } from "@/lib/server-actions-helper"; import { + AttackPathCartographySchema, + AttackPathCartographySchemaResponse, AttackPathQueriesResponse, AttackPathQuery, AttackPathQueryError, AttackPathQueryResult, + ExecuteCustomQueryRequest, ExecuteQueryRequest, } from "@/types/attack-paths"; @@ -102,3 +106,93 @@ export const executeQuery = async ( }; } }; + +/** + * Execute a custom openCypher query on an attack path scan + */ +export const executeCustomQuery = async ( + scanId: string, + query: string, +): Promise => { + const validatedScanId = UUIDSchema.safeParse(scanId); + if (!validatedScanId.success) { + console.error("Invalid scan ID format"); + return undefined; + } + + const validatedQuery = customAttackPathQuerySchema.safeParse(query); + if (!validatedQuery.success) { + return { + error: + validatedQuery.error.issues[0]?.message ?? "Custom query is invalid.", + status: 400, + }; + } + + const headers = await getAuthHeaders({ contentType: true }); + + const requestBody: ExecuteCustomQueryRequest = { + data: { + type: "attack-paths-custom-query-run-requests", + attributes: { + query: validatedQuery.data, + }, + }, + }; + + try { + const response = await fetch( + `${apiBaseUrl}/attack-paths-scans/${validatedScanId.data}/queries/custom`, + { + headers, + method: "POST", + body: JSON.stringify(requestBody), + }, + ); + + return (await handleApiResponse(response)) as + | AttackPathQueryResult + | AttackPathQueryError; + } catch (error) { + console.error("Error executing custom query on scan:", error); + return { + error: + "Server is temporarily unavailable. Please try again in a few minutes.", + status: 503, + }; + } +}; + +/** + * Fetch cartography schema metadata for a specific attack path scan + */ +export const getCartographySchema = async ( + scanId: string, +): Promise<{ data: AttackPathCartographySchema } | undefined> => { + const validatedScanId = UUIDSchema.safeParse(scanId); + if (!validatedScanId.success) { + console.error("Invalid scan ID format"); + return undefined; + } + + const headers = await getAuthHeaders({ contentType: false }); + + try { + const response = await fetch( + `${apiBaseUrl}/attack-paths-scans/${validatedScanId.data}/schema`, + { + headers, + method: "GET", + }, + ); + + const apiResponse = (await handleApiResponse( + response, + )) as AttackPathCartographySchemaResponse; + + return { data: apiResponse.data }; + } catch (error) { + console.error("Error fetching cartography schema for scan:", error); + return undefined; + } +}; diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/index.ts b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/index.ts index eac86fccc7..83161cc4ef 100644 --- a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/index.ts +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/index.ts @@ -1,6 +1,8 @@ export { ExecuteButton } from "./execute-button"; export * from "./graph"; export * from "./node-detail"; +export { QueryDescription } from "./query-description"; +export { QueryExecutionError } from "./query-execution-error"; export { QueryParametersForm } from "./query-parameters-form"; export { QuerySelector } from "./query-selector"; export { ScanListTable } from "./scan-list-table"; diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-description.test.tsx b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-description.test.tsx new file mode 100644 index 0000000000..cc61c16a4d --- /dev/null +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-description.test.tsx @@ -0,0 +1,76 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import type { AttackPathQuery } from "@/types/attack-paths"; + +import { QueryDescription } from "./query-description"; + +const customQuery: AttackPathQuery = { + type: "attack-paths-scans", + id: "custom-query", + attributes: { + name: "Custom openCypher query", + short_description: "Write your own query", + description: + "Run a read-only openCypher query against the selected Attack Paths scan.", + provider: "aws", + attribution: null, + documentation_link: { + text: "Cartography schema used by Prowler for AWS graphs", + link: "https://example.com/schema", + }, + parameters: [], + }, +}; + +describe("QueryDescription", () => { + it("renders the schema documentation link inside an info alert", () => { + // Given + render(); + + // When + const alert = screen.getByRole("alert"); + const link = screen.getByRole("link", { + name: /cartography schema used by prowler for aws graphs/i, + }); + + // Then + expect(alert).toBeInTheDocument(); + expect(link).toHaveAttribute("href", "https://example.com/schema"); + }); + + it("does not render unsafe documentation or attribution URLs as clickable links", () => { + // Given + const queryWithUnsafeLinks: AttackPathQuery = { + ...customQuery, + attributes: { + ...customQuery.attributes, + documentation_link: { + text: "Cartography schema used by Prowler for AWS graphs", + link: "javascript:alert('xss')", + }, + attribution: { + text: "Unsafe source", + link: "javascript:alert('xss')", + }, + }, + }; + + // When + render(); + + // Then + expect( + screen.queryByRole("link", { + name: /cartography schema used by prowler for aws graphs/i, + }), + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("link", { name: /unsafe source/i }), + ).not.toBeInTheDocument(); + expect( + screen.getByText(/cartography schema used by prowler for aws graphs/i), + ).toBeInTheDocument(); + expect(screen.getByText(/unsafe source/i)).toBeInTheDocument(); + }); +}); diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-description.tsx b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-description.tsx new file mode 100644 index 0000000000..d1cb6e85fe --- /dev/null +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-description.tsx @@ -0,0 +1,70 @@ +import { Info } from "lucide-react"; + +import { Alert, AlertDescription } from "@/components/shadcn"; +import type { AttackPathQuery } from "@/types/attack-paths"; + +interface QueryDescriptionProps { + query: AttackPathQuery; +} + +const isSafeUrl = (url: string): boolean => { + try { + const parsedUrl = new URL(url); + return parsedUrl.protocol === "https:" || parsedUrl.protocol === "http:"; + } catch { + return false; + } +}; + +export const QueryDescription = ({ query }: QueryDescriptionProps) => { + const documentationLink = query.attributes.documentation_link; + const attribution = query.attributes.attribution; + + return ( + + + +

{query.attributes.description}

+ + {documentationLink && ( +

+ {isSafeUrl(documentationLink.link) ? ( + + {documentationLink.text} + + ) : ( + {documentationLink.text} + )} +

+ )} + + {attribution && ( +

+ {isSafeUrl(attribution.link) ? ( + <> + Source:{" "} + + {attribution.text} + + + ) : ( + <> + Source: {attribution.text} + + )} +

+ )} +
+
+ ); +}; diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-execution-error.test.tsx b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-execution-error.test.tsx new file mode 100644 index 0000000000..1c7fb29712 --- /dev/null +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-execution-error.test.tsx @@ -0,0 +1,46 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { QueryExecutionError } from "./query-execution-error"; + +describe("QueryExecutionError", () => { + it("renders the default title and the raw query error details without extra copy", () => { + // Given + const error = + "Invalid input 'WHERE': expected 'MATCH' or 'WITH' (line 1, column 1)"; + + // When + render(); + + // Then + expect(screen.getByRole("alert")).toBeInTheDocument(); + expect(screen.getByText(/query execution failed/i)).toBeInTheDocument(); + expect( + screen.queryByText(/the attack paths query could not be executed/i), + ).not.toBeInTheDocument(); + expect(screen.getByText(error)).toBeInTheDocument(); + }); + + it("renders custom title and description when provided", () => { + // Given + const error = "Failed to load available queries"; + + // When + render( + , + ); + + // Then + expect(screen.getByText(/failed to load queries/i)).toBeInTheDocument(); + expect( + screen.getByText( + /available attack paths queries could not be loaded for this scan/i, + ), + ).toBeInTheDocument(); + expect(screen.getByText(error)).toBeInTheDocument(); + }); +}); diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-execution-error.tsx b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-execution-error.tsx new file mode 100644 index 0000000000..084bcf6e16 --- /dev/null +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-execution-error.tsx @@ -0,0 +1,30 @@ +import { CircleAlert } from "lucide-react"; + +import { Alert, AlertDescription, AlertTitle } from "@/components/shadcn"; + +interface QueryExecutionErrorProps { + error: string; + title?: string; + description?: string; +} + +export const QueryExecutionError = ({ + error, + title = "Query execution failed", + description, +}: QueryExecutionErrorProps) => { + return ( + + + {title} + + {description ?

{description}

: null} +
+
+            {error}
+          
+
+
+
+ ); +}; diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-parameters-form.test.tsx b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-parameters-form.test.tsx index ac2b81be7f..694da8babe 100644 --- a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-parameters-form.test.tsx +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-parameters-form.test.tsx @@ -1,8 +1,12 @@ import { render, screen } from "@testing-library/react"; +import { useEffect } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { describe, expect, it } from "vitest"; -import type { AttackPathQuery } from "@/types/attack-paths"; +import { + ATTACK_PATH_QUERY_IDS, + type AttackPathQuery, +} from "@/types/attack-paths"; import { QueryParametersForm } from "./query-parameters-form"; @@ -42,6 +46,64 @@ function TestForm() { ); } +function TestCustomQueryForm() { + const customQuery: AttackPathQuery = { + type: "attack-paths-scans", + id: ATTACK_PATH_QUERY_IDS.CUSTOM, + attributes: { + name: "Custom openCypher query", + short_description: "Write your own query", + description: "Run a custom query against the graph.", + provider: "aws", + attribution: null, + parameters: [ + { + name: "query", + label: "openCypher", + data_type: "string", + input_type: "textarea", + placeholder: "MATCH (n) RETURN n LIMIT 25", + description: "", + required: true, + }, + ], + }, + }; + + const form = useForm({ + defaultValues: { + query: "", + }, + }); + + return ( + + + + ); +} + +function TestFormWithError() { + const form = useForm({ + defaultValues: { + tag_key: "", + }, + }); + + useEffect(() => { + form.setError("tag_key", { + type: "manual", + message: "Tag key is required", + }); + }, [form]); + + return ( + + + + ); +} + describe("QueryParametersForm", () => { it("uses the field description as the placeholder instead of rendering helper text below", () => { // Given @@ -70,4 +132,62 @@ describe("QueryParametersForm", () => { screen.queryByText("Tag key to filter the S3 bucket."), ).not.toBeInTheDocument(); }); + + it("renders a textarea when the parameter input type is textarea", () => { + // Given + render(); + + // When + const input = screen.getByRole("textbox", { name: /opencypher/i }); + const codeEditor = screen.getByTestId("query-code-editor"); + + // Then + expect(input.tagName).toBe("TEXTAREA"); + expect(input).toHaveAttribute("data-slot", "textarea"); + expect(input).toHaveAttribute("placeholder", "MATCH (n) RETURN n LIMIT 25"); + expect(input).toHaveAttribute("spellcheck", "false"); + expect(input).toHaveAttribute("autocomplete", "off"); + expect(input).toHaveAttribute("autocorrect", "off"); + expect(input).toHaveAttribute("autocapitalize", "none"); + expect(input).toHaveClass( + "minimal-scrollbar", + "min-h-[320px]", + "font-mono", + "leading-6", + ); + expect(codeEditor).toHaveClass( + "rounded-xl", + "border", + "bg-bg-neutral-primary", + ); + expect(screen.getByText("Read-only")).toBeInTheDocument(); + }); + + it("uses the design-system error token for field validation messages", async () => { + // Given + render(); + + // When + const errorMessage = await screen.findByText("Tag key is required"); + + // Then + expect(errorMessage).toHaveClass("text-text-error-primary", "text-xs"); + }); + + it("connects field errors to the input for accessibility", async () => { + // Given + render(); + + // When + const input = screen.getByRole("textbox", { name: /tag key/i }); + const errorMessage = await screen.findByText("Tag key is required"); + + // Then + expect(input).toHaveAttribute("aria-invalid", "true"); + expect(errorMessage).toHaveAttribute("id"); + expect(input).toHaveAttribute( + "aria-describedby", + expect.stringContaining(errorMessage.getAttribute("id") ?? ""), + ); + }); }); diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-parameters-form.tsx b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-parameters-form.tsx index 24937c0b7b..6ac5883e66 100644 --- a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-parameters-form.tsx +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_components/query-parameters-form.tsx @@ -1,9 +1,21 @@ "use client"; -import { Controller, useFormContext } from "react-hook-form"; +import { useFormContext } from "react-hook-form"; -import { Input } from "@/components/shadcn"; -import type { AttackPathQuery } from "@/types/attack-paths"; +import { Input, Textarea } from "@/components/shadcn"; +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { cn } from "@/lib/utils"; +import { + ATTACK_PATH_QUERY_IDS, + type AttackPathQuery, + QUERY_PARAMETER_INPUT_TYPES, +} from "@/types/attack-paths"; interface QueryParametersFormProps { selectedQuery: AttackPathQuery | null | undefined; @@ -16,10 +28,7 @@ interface QueryParametersFormProps { export const QueryParametersForm = ({ selectedQuery, }: QueryParametersFormProps) => { - const { - control, - formState: { errors }, - } = useFormContext(); + const { control } = useFormContext(); if (!selectedQuery || !selectedQuery.attributes.parameters.length) { return null; @@ -36,23 +45,26 @@ export const QueryParametersForm = ({ className="grid grid-cols-1 gap-4 md:grid-cols-2" > {selectedQuery.attributes.parameters.map((param) => ( - { + render={({ field, fieldState }) => { if (param.data_type === "boolean") { return ( -
+ -
+ + ); } - const errorMessage = (() => { - const error = errors[param.name]; - if (error && typeof error.message === "string") { - return error.message; - } - return undefined; - })(); + const placeholder = + param.description || + param.placeholder || + `Enter ${param.label.toLowerCase()}`; + + const isTextarea = + param.input_type === QUERY_PARAMETER_INPUT_TYPES.TEXTAREA; + const isCustomCodeEditor = + selectedQuery.id === ATTACK_PATH_QUERY_IDS.CUSTOM && + param.name === "query" && + isTextarea; return ( -
- - - {errorMessage && ( - {errorMessage} + + > + {!isCustomCodeEditor && ( + + {param.label} + {param.required && ( + * + )} + + )} + {isCustomCodeEditor ? ( +
+
+ + {param.label} + {param.required && ( + * + )} + + + Read-only + +
+ +