Compare commits

..

5 Commits

Author SHA1 Message Date
Rubén De la Torre Vico
809fd3dcf3 Merge branch 'master' into api-add-missing-map 2025-06-23 10:48:00 +02:00
Daniel Barranquero
eb5dbab86e feat(docs): update Azure and M365 docs with needed permissions (#8075) 2025-06-23 10:12:11 +02:00
Rubén De la Torre Vico
a470b7c9d8 docs(changelog): change fix from version 2025-06-20 10:00:39 +02:00
Rubén De la Torre Vico
eba5f8e621 docs(changelog): update PR link for new fix 2025-06-20 09:58:23 +02:00
Adrián Jesús Peña Rodríguez
56443518d6 fix(export): add missing m365 iso27001 mapping 2025-06-18 18:20:25 +02:00
20 changed files with 73 additions and 380 deletions

View File

@@ -1,82 +0,0 @@
name: UI - E2E Tests
on:
pull_request:
branches:
- master
- "v5.*"
paths:
- 'ui/**'
env:
# Temporary secret for CI test runs only replace with GitHub Secret later
AUTH_SECRET: "N/c6mnaS5+SWq81+819OrzQZlmx1Vxtp/orjttJSmw8="
API_BASE_URL: "http://localhost:8080/api/v1"
SERVICES_TO_START: "api-dev postgres valkey worker-beat worker-dev"
DOCKER_COMPOSE_FILE: "docker-compose-dev.yml"
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
cache-dependency-path: './ui/package-lock.json'
# - name: Cache Playwright Browsers
# uses: actions/cache@v4
# with:
# path: ~/.cache/ms-playwright
# key: playwright-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# playwright-${{ runner.os }}-
- name: Install dependencies
run: npm ci
working-directory: ./ui
- name: Install Playwright Browsers
run: npx playwright install --with-deps
working-directory: ./ui
- name: Set up Docker Compose
uses: docker/setup-compose-action@364cc21a5de5b1ee4a7f5f9d3fa374ce0ccde746 #v1.2.0
- name: Start Docker Compose
run: docker compose -f ${DOCKER_COMPOSE_FILE} up -d ${SERVICES_TO_START}
- name: Wait for API to be ready
run: |
for i in {1..30}; do
if curl -s http://localhost:8000/api/v1; then
echo "API is up!"
break
fi
echo "Waiting for API..."
sleep 5
done
- name: Run Playwright tests
run: npx playwright test
working-directory: ./ui
- name: Upload Playwright report
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: ./ui/playwright-report
- name: Upload Playwright videos
uses: actions/upload-artifact@v4
with:
name: test-videos
path: ./ui/test-results/**/*.webm
- name: Docker Compose Down
if: always()
run: docker compose -f ${DOCKER_COMPOSE_FILE} down

View File

@@ -13,6 +13,9 @@ All notable changes to the **Prowler API** are documented in this file.
### Changed
- Reworked `GET /compliance-overviews` to return proper requirement metrics [(#7877)](https://github.com/prowler-cloud/prowler/pull/7877)
### Fixed
- Add missing mapping for ISO 27001 compliance for M365 provider [(#8069)](https://github.com/prowler-cloud/prowler/pull/8069)
---
## [v1.8.5] (Prowler v5.7.5)

View File

@@ -31,6 +31,7 @@ from prowler.lib.outputs.compliance.iso27001.iso27001_gcp import GCPISO27001
from prowler.lib.outputs.compliance.iso27001.iso27001_kubernetes import (
KubernetesISO27001,
)
from prowler.lib.outputs.compliance.iso27001.iso27001_m365 import M365ISO27001
from prowler.lib.outputs.compliance.kisa_ismsp.kisa_ismsp_aws import AWSKISAISMSP
from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_aws import AWSMitreAttack
from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_azure import (
@@ -88,6 +89,7 @@ COMPLIANCE_CLASS_MAP = {
(lambda name: name.startswith("iso27001_"), KubernetesISO27001),
],
"m365": [
(lambda name: name.startswith("iso27001_"), M365ISO27001),
(lambda name: name.startswith("cis_"), M365CIS),
(lambda name: name == "prowler_threatscore_m365", ProwlerThreatScoreM365),
],

View File

@@ -70,9 +70,13 @@ The other three cases does not need additional configuration, `--az-cli-auth` an
Prowler for Azure needs two types of permission scopes to be set:
- **Microsoft Entra ID permissions**: used to retrieve metadata from the identity assumed by Prowler and specific Entra checks (not mandatory to have access to execute the tool). The permissions required by the tool are the following:
- `Domain.Read.All`
- `Directory.Read.All`
- `Policy.Read.All`
- `UserAuthenticationMethod.Read.All` (used only for the Entra checks related with multifactor authentication)
???+ note
You can replace `Directory.Read.All` with `Domain.Read.All` that is a more restrictive permission but you won't be able to run the Entra checks related with DirectoryRoles and GetUsers.
- **Subscription scope permissions**: required to launch the checks against your resources, mandatory to launch the tool. It is required to add the following RBAC builtin roles per subscription to the entity that is going to be assumed by the tool:
- `Reader`
- `ProwlerRole` (custom role with minimal permissions defined in [prowler-azure-custom-role](https://github.com/prowler-cloud/prowler/blob/master/permissions/prowler-azure-custom-role.json))
@@ -205,12 +209,17 @@ Prowler for M365 requires two types of permission scopes to be set (if you want
- **Service Principal Application Permissions**: These are set at the **application** level and are used to retrieve data from the identity being assessed:
- `AuditLog.Read.All`: Required for Entra service.
- `Domain.Read.All`: Required for all services.
- `Organization.Read.All`: Required for retrieving tenant information.
- `Directory.Read.All`: Required for all services.
- `Policy.Read.All`: Required for all services.
- `SharePointTenantSettings.Read.All`: Required for SharePoint service.
- `User.Read` (IMPORTANT: this must be set as **delegated**): Required for the sign-in.
???+ note
You can replace `Directory.Read.All` with `Domain.Read.All` is a more restrictive permission but you won't be able to run the Entra checks related with DirectoryRoles and GetUsers.
> If you do this you will need to add also the `Organization.Read.All` permission to the service principal application in order to authenticate.
- **Powershell Modules Permissions**: These are set at the `M365_USER` level, so the user used to run Prowler must have one of the following roles:
- `Global Reader` (recommended): this allows you to read all roles needed.

View File

@@ -90,11 +90,14 @@ A Service Principal is required to grant Prowler the necessary privileges.
Assign the following Microsoft Graph permissions:
- Domain.Read.All
- Directory.Read.All
- Policy.Read.All
- Policy.Read.All
- UserAuthenticationMethod.Read.All (optional, for MFA checks)
- UserAuthenticationMethod.Read.All (optional, for MFA checks)
???+ note
You can replace `Directory.Read.All` with `Domain.Read.All` that is a more restrictive permission but you won't be able to run the Entra checks related with DirectoryRoles and GetUsers.
1. Go to your App Registration > `API permissions`
@@ -107,7 +110,7 @@ Assign the following Microsoft Graph permissions:
3. Search and select:
- `Domain.Read.All`
- `Directory.Read.All`
- `Policy.Read.All`
- `UserAuthenticationMethod.Read.All`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -95,12 +95,18 @@ With this done you will have all the needed keys, summarized in the following ta
### Grant required API permissions
Assign the following Microsoft Graph permissions:
- `AuditLog.Read.All`: Required for Entra service.
- `Domain.Read.All`: Required for all services.
- `Directory.Read.All`: Required for all services.
- `Policy.Read.All`: Required for all services.
- `SharePointTenantSettings.Read.All`: Required for SharePoint service.
- `User.Read` (IMPORTANT: this is set as **delegated**): Required for the sign-in.
???+ note
You can replace `Directory.Read.All` with `Domain.Read.All` is a more restrictive permission but you won't be able to run the Entra checks related with DirectoryRoles and GetUsers.
> If you do this you will need to add also the `Organization.Read.All` permission to the service principal application in order to authenticate.
Follow these steps to assign the permissions:
1. Go to your App Registration > Select your Prowler App created before > click on `API permissions`
@@ -113,8 +119,7 @@ Follow these steps to assign the permissions:
3. Search and select every permission below and once all are selected click on `Add permissions`:
- `AuditLog.Read.All`: Required for Entra service.
- `Domain.Read.All`
- `Organization.Read.All`
- `Directory.Read.All`
- `Policy.Read.All`
- `SharePointTenantSettings.Read.All`
@@ -134,7 +139,7 @@ Follow these steps to assign the permissions:
![Permission Screenshots](./img/directory-permission-delegated.png)
6. Click `Add permissions`, then **grant admin consent**
6. After adding all the permissions, click on `Grant admin consent`
![Grant Admin Consent](./img/grant-admin-consent.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 181 KiB

View File

@@ -37,6 +37,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Azure Databricks service integration for Azure provider, including the `databricks_workspace_vnet_injection_enabled` check [(#8008)](https://github.com/prowler-cloud/prowler/pull/8008)
- Azure Databricks check `databricks_workspace_cmk_encryption_enabled` to ensure workspaces use customer-managed keys (CMK) for encryption at rest [(#8017)](https://github.com/prowler-cloud/prowler/pull/8017)
- Add `storage_account_default_to_entra_authorization_enabled` check for Azure provider. [(#7981)](https://github.com/prowler-cloud/prowler/pull/7981)
- Replace `Domain.Read.All` with `Directory.Read.All` in Azure and M365 docs [(#8075)](https://github.com/prowler-cloud/prowler/pull/8075)
### Removed
- OCSF version number references to point always to the latest [(#8064)](https://github.com/prowler-cloud/prowler/pull/8064)

View File

@@ -45,38 +45,45 @@ class Entra(AzureService):
for tenant, client in self.clients.items():
users_list = await client.users.get()
users.update({tenant: {}})
for user in users_list.value:
users[tenant].update(
{
user.id: User(
id=user.id,
name=user.display_name,
authentication_methods=[
AuthMethod(
id=auth_method.id,
type=getattr(auth_method, "odata_type", None),
)
for auth_method in (
await client.users.by_user_id(
user.id
).authentication.methods.get()
).value
],
)
}
)
try:
for user in users_list.value:
users[tenant].update(
{
user.id: User(
id=user.id,
name=user.display_name,
authentication_methods=[
AuthMethod(
id=auth_method.id,
type=getattr(
auth_method, "odata_type", None
),
)
for auth_method in (
await client.users.by_user_id(
user.id
).authentication.methods.get()
).value
],
)
}
)
except Exception as error:
if (
error.__class__.__name__ == "ODataError"
and error.__dict__.get("response_status_code", None) == 403
):
logger.error(
"You need 'UserAuthenticationMethod.Read.All' permission to access this information. It only can be granted through Service Principal authentication."
)
else:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
if (
error.__class__.__name__ == "ODataError"
and error.__dict__.get("response_status_code", None) == 403
):
logger.error(
"You need 'UserAuthenticationMethod.Read.All' permission to access this information. It only can be granted through Service Principal authentication."
)
else:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return users

4
ui/.gitignore vendored
View File

@@ -34,7 +34,3 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# Playwright test artifacts
playwright-report/
test-results/

View File

@@ -21,7 +21,6 @@ All notable changes to the **Prowler UI** are documented in this file.
- Improve `Scan ID` filter by adding more context and enhancing the UI/UX [(#7979)](https://github.com/prowler-cloud/prowler/pull/7979)
- Lighthouse chat interface [(#7878)](https://github.com/prowler-cloud/prowler/pull/7878)
- Google Tag Manager integration [(#8058)](https://github.com/prowler-cloud/prowler/pull/8058)
- Added initial Playwright configuration and sample test [(#8081)](https://github.com/prowler-cloud/prowler/pull/8081)
### 🔄 Changed

77
ui/package-lock.json generated
View File

@@ -62,7 +62,6 @@
},
"devDependencies": {
"@iconify/react": "^5.2.0",
"@playwright/test": "^1.53.1",
"@types/bcryptjs": "^2.4.6",
"@types/node": "20.5.7",
"@types/react": "18.3.3",
@@ -71,7 +70,6 @@
"@typescript-eslint/eslint-plugin": "^7.10.0",
"@typescript-eslint/parser": "^7.10.0",
"autoprefixer": "10.4.19",
"dotenv": "^16.5.0",
"eslint": "^8.56.0",
"eslint-config-next": "^14.2.23",
"eslint-config-prettier": "^10.0.1",
@@ -4718,22 +4716,6 @@
"url": "https://opencollective.com/unts"
}
},
"node_modules/@playwright/test": {
"version": "1.53.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz",
"integrity": "sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.53.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@radix-ui/number": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
@@ -10097,19 +10079,6 @@
"csstype": "^3.0.2"
}
},
"node_modules/dotenv": {
"version": "16.5.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -11404,20 +11373,6 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -14671,38 +14626,6 @@
"node": ">= 6"
}
},
"node_modules/playwright": {
"version": "1.53.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz",
"integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.53.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.53.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.1.tgz",
"integrity": "sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",

View File

@@ -54,7 +54,6 @@
},
"devDependencies": {
"@iconify/react": "^5.2.0",
"@playwright/test": "^1.53.1",
"@types/bcryptjs": "^2.4.6",
"@types/node": "20.5.7",
"@types/react": "18.3.3",
@@ -63,7 +62,6 @@
"@typescript-eslint/eslint-plugin": "^7.10.0",
"@typescript-eslint/parser": "^7.10.0",
"autoprefixer": "10.4.19",
"dotenv": "^16.5.0",
"eslint": "^8.56.0",
"eslint-config-next": "^14.2.23",
"eslint-config-prettier": "^10.0.1",
@@ -98,8 +96,7 @@
"lint:fix": "eslint . --ext .ts,.tsx -c .eslintrc.cjs --fix",
"format:check": "./node_modules/.bin/prettier --check ./app",
"format:write": "./node_modules/.bin/prettier --config .prettierrc.json --write ./app",
"prepare": "husky",
"test:e2e": "npx playwright test && npx playwright show-report"
"prepare": "husky"
},
"overrides": {
"@react-types/shared": "3.26.0"

View File

@@ -1,70 +0,0 @@
import { defineConfig, devices } from "@playwright/test";
import * as dotenv from "dotenv";
dotenv.config();
const isLocal = process.env.LOCAL === "true";
export default defineConfig({
timeout: 90 * 1000,
testDir: "./tests/e2e",
fullyParallel: false,
forbidOnly: !isLocal,
retries: isLocal ? 0 : 2,
workers: isLocal ? undefined : 1,
reporter: "html",
use: {
baseURL: "http://localhost:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
navigationTimeout: 60 * 1000,
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: isLocal
? undefined // Skip web server in local runs
: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: true,
timeout: 120 * 1000, // wait up to 2 minutes for frontend to boot
},
});

View File

@@ -1,24 +0,0 @@
# Playwright E2E Testing
## 📦 Installation
Playwright is already set up. To install dependencies:
```bash
cd ui
npm install
# Run all tests (headless)
npm run test:e2e
# Run specific file (headless)
npx playwright test tests/e2e/root.spec.ts
# Run all tests with UI (headed mode)
npx playwright test --headed
# Run specific file with UI (headed mode)
npx playwright test tests/e2e/root.spec.ts --headed
# Open the HTML report from last test run
npx playwright show-report

View File

@@ -1,69 +0,0 @@
import { test, expect, request, Page } from '@playwright/test';
// Test credentials
const testEmail = 'test@gmail.com';
const testPassword = 'Testt@123456';
// Helper login function
const login = async (page: Page, email: string, password: string) => {
await page.goto('/sign-in');
await page.fill('input[name="email"]', email);
await page.fill('input[name="password"]', password);
await page.getByRole('button', { name: /log in/i }).click();
};
test.beforeAll(async () => {
const apiContext = await request.newContext();
const response = await apiContext.post(`${process.env.API_BASE_URL}/users`, {
headers: {
'Content-Type': 'application/vnd.api+json',
'Accept': 'application/vnd.api+json',
},
data: {
data: {
type: 'users',
attributes: {
name: 'testuser',
email: testEmail,
password: testPassword,
company_name: 'test',
},
},
},
});
if (!response.ok()) {
console.warn(`User creation may have failed: ${response.status()} - ${await response.text()}`);
}
await apiContext.dispose();
});
// Test invalid login
test('should show error for invalid credentials', async ({ page }) => {
await login(page, 'wrong@gmail.com', 'WrongPassword123');
await page.waitForTimeout(7000);
await expect(page.getByText(/invalid email or password/i)).toBeVisible({ timeout: 10000 });
});
// Test valid login and redirection
test('should sign in successfully', async ({ page }) => {
await login(page, testEmail, testPassword);
await page.waitForTimeout(7000);
await page.waitForURL((url) => !url.pathname.includes('sign-in'), {
timeout: 15000,
});
});
// Test session persistence after reload
test('should persist session after login', async ({ page }) => {
await login(page, testEmail, testPassword);
await page.waitForTimeout(7000);
await page.waitForURL((url) => !url.pathname.includes('sign-in'), { timeout: 15000 });
await page.reload();
await expect(page.getByRole('button', { name: /sign out/i })).toBeVisible();
await page.goto("/findings")
await expect(page.getByText(/Browse all findings/i).first()).toBeVisible({ timeout: 10000 });
});

View File

@@ -1,7 +0,0 @@
import { test, expect } from '@playwright/test';
test('Unauthenticated users are redirected to sign-in and can navigate to sign-up', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveURL(/\/sign-in/);
await expect(page.getByText('Sign In')).toBeVisible();
});