mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-05-14 16:25:13 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 124d1b9c65 | |||
| 3410fc927a | |||
| dcf91ef252 | |||
| bf4fd8fabd | |||
| 3d65208fd3 | |||
| 7d3ed62e90 | |||
| 5f92989492 | |||
| 6befa78978 | |||
| 78af0c24fe | |||
| 1bb547e5e1 | |||
| 1f39b01fb2 | |||
| fb0ef391f2 | |||
| f2e6a3264d |
+5
-6
@@ -2,20 +2,19 @@
|
||||
# Runs automatically on `wt switch --create`.
|
||||
|
||||
# Block 1: setup + copy gitignored env files (.envrc, ui/.env.local)
|
||||
# from the primary worktree — patterns selected via .worktreeinclude.
|
||||
# from the primary worktree - patterns selected via .worktreeinclude.
|
||||
[[pre-start]]
|
||||
skills = "./skills/setup.sh --claude"
|
||||
python = "poetry env use python3.12"
|
||||
envs = "wt step copy-ignored"
|
||||
|
||||
# Block 2: install Python deps (requires `poetry env use` from block 1).
|
||||
# Block 2: install Python deps (uv manages the venv on `uv sync`).
|
||||
[[pre-start]]
|
||||
deps = "poetry install --with dev"
|
||||
deps = "uv sync"
|
||||
|
||||
# Block 3: reminder — last visible output before `wt switch` returns.
|
||||
# Block 3: reminder - last visible output before `wt switch` returns.
|
||||
# Hooks can't mutate the parent shell, so venv activation is manual.
|
||||
[[pre-start]]
|
||||
reminder = "echo '>> Reminder: activate the venv in this shell with: eval $(poetry env activate)'"
|
||||
reminder = "echo '>> Reminder: activate the venv in this shell with: source .venv/bin/activate'"
|
||||
|
||||
# Background: pnpm install runs while you start working.
|
||||
# Tail logs via `wt config state logs`.
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
name: 'OSV-Scanner'
|
||||
description: 'Install osv-scanner and scan a lockfile, failing on HIGH/CRITICAL/UNKNOWN severity findings. Posts/updates a PR comment with findings on pull_request events (requires pull-requests: write).'
|
||||
author: 'Prowler'
|
||||
|
||||
inputs:
|
||||
lockfile:
|
||||
description: 'Path to the lockfile to scan, relative to the repository root (e.g. uv.lock, api/uv.lock, ui/pnpm-lock.yaml).'
|
||||
required: true
|
||||
severity-levels:
|
||||
description: 'Comma-separated severity levels that fail the scan. Default: HIGH,CRITICAL,UNKNOWN.'
|
||||
required: false
|
||||
default: 'HIGH,CRITICAL,UNKNOWN'
|
||||
version:
|
||||
description: 'osv-scanner release tag to install. When overriding, you MUST also override binary-sha256.'
|
||||
required: false
|
||||
default: 'v2.3.8'
|
||||
binary-sha256:
|
||||
description: 'Expected SHA256 of osv-scanner_linux_amd64 for the given version. Default tracks v2.3.8. See https://github.com/google/osv-scanner/releases/download/<version>/osv-scanner_SHA256SUMS.'
|
||||
required: false
|
||||
default: 'bc98e15319ed0d515e3f9235287ba53cdc5535d576d24fd573978ecfe9ab92dc'
|
||||
post-pr-comment:
|
||||
description: 'Post or update a PR comment with the scan report. Only effective on pull_request events. Requires pull-requests: write permission on the caller job.'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Install osv-scanner
|
||||
shell: bash
|
||||
env:
|
||||
OSV_SCANNER_VERSION: ${{ inputs.version }}
|
||||
# Download the binary AND the published SHA256SUMS file, then verify the
|
||||
# binary checksum against the upstream-signed manifest. Aborts on mismatch.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v osv-scanner >/dev/null 2>&1; then
|
||||
INSTALLED="$(osv-scanner --version 2>&1 | awk '/scanner version/ {print $NF; exit}')"
|
||||
if [ "v${INSTALLED}" = "${OSV_SCANNER_VERSION}" ]; then
|
||||
echo "osv-scanner ${OSV_SCANNER_VERSION} already installed."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
BASE="https://github.com/google/osv-scanner/releases/download/${OSV_SCANNER_VERSION}"
|
||||
BIN_NAME="osv-scanner_linux_amd64"
|
||||
curl -fSL --retry 3 "${BASE}/${BIN_NAME}" -o "${RUNNER_TEMP}/${BIN_NAME}"
|
||||
curl -fSL --retry 3 "${BASE}/osv-scanner_SHA256SUMS" -o "${RUNNER_TEMP}/osv-scanner_SHA256SUMS"
|
||||
(cd "${RUNNER_TEMP}" && sha256sum --check --ignore-missing osv-scanner_SHA256SUMS)
|
||||
chmod +x "${RUNNER_TEMP}/${BIN_NAME}"
|
||||
sudo mv "${RUNNER_TEMP}/${BIN_NAME}" /usr/local/bin/osv-scanner
|
||||
rm -f "${RUNNER_TEMP}/osv-scanner_SHA256SUMS"
|
||||
osv-scanner --version
|
||||
|
||||
- name: Run osv-scanner
|
||||
id: scan
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
OSV_LOCKFILE: ${{ inputs.lockfile }}
|
||||
OSV_SEVERITY_LEVELS: ${{ inputs.severity-levels }}
|
||||
OSV_REPORT_FILE: ${{ runner.temp }}/osv-scanner-findings.json
|
||||
# Per-vulnerability ignores (reason + expiry) live in osv-scanner.toml at the repo root, if present.
|
||||
# Severity filter is enforced in the wrapper via OSV_SEVERITY_LEVELS.
|
||||
# `continue-on-error: true` lets the PR-comment step run even when findings exist;
|
||||
# the gate step below re-fails the job from the wrapper exit code.
|
||||
continue-on-error: true
|
||||
run: ./.github/scripts/osv-scan.sh --lockfile="${OSV_LOCKFILE}"
|
||||
|
||||
- name: Post osv-scanner report on PR
|
||||
if: >-
|
||||
always()
|
||||
&& inputs.post-pr-comment == 'true'
|
||||
&& github.event_name == 'pull_request'
|
||||
&& github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
OSV_REPORT_FILE: ${{ runner.temp }}/osv-scanner-findings.json
|
||||
OSV_LOCKFILE: ${{ inputs.lockfile }}
|
||||
OSV_SEVERITY_LEVELS: ${{ inputs.severity-levels }}
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const lockfile = process.env.OSV_LOCKFILE;
|
||||
const severityLevels = process.env.OSV_SEVERITY_LEVELS;
|
||||
const reportFile = process.env.OSV_REPORT_FILE;
|
||||
const marker = `<!-- osv-scanner-report:${lockfile} -->`;
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
|
||||
let findings = [];
|
||||
if (fs.existsSync(reportFile)) {
|
||||
try {
|
||||
findings = JSON.parse(fs.readFileSync(reportFile, 'utf8'));
|
||||
} catch (err) {
|
||||
core.warning(`Could not parse ${reportFile}: ${err.message}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
const existing = comments.find(c => c.body?.includes(marker));
|
||||
|
||||
if (findings.length === 0) {
|
||||
if (existing) {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id,
|
||||
});
|
||||
core.info(`Deleted stale osv-scanner comment for ${lockfile}.`);
|
||||
} else {
|
||||
core.info(`No findings and no stale comment for ${lockfile}.`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const sevIcon = (s) => ({
|
||||
CRITICAL: '🔴', HIGH: '🟠', MEDIUM: '🟡', LOW: '🟢', UNKNOWN: '⚪',
|
||||
}[s] || '⚪');
|
||||
const escape = (s) => String(s ?? '').replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
||||
const rows = findings.map(f =>
|
||||
`| ${sevIcon(f.severity)} ${f.severity}${f.score ? ` (${f.score})` : ''} | \`${escape(f.id)}\` | \`${escape(f.ecosystem)}/${escape(f.package)}\` | \`${escape(f.version)}\` | ${escape(f.summary || '(no summary)')} |`
|
||||
);
|
||||
|
||||
const body = [
|
||||
marker,
|
||||
`## 🔒 osv-scanner: ${findings.length} finding(s) in \`${lockfile}\``,
|
||||
'',
|
||||
`Severity gate: \`${severityLevels}\``,
|
||||
'',
|
||||
'| Severity | ID | Package | Version | Summary |',
|
||||
'|----------|----|---------|---------|---------|',
|
||||
...rows,
|
||||
'',
|
||||
`To accept a finding, add an \`[[IgnoredVulns]]\` entry to \`osv-scanner.toml\` at the repo root with a reason and \`ignoreUntil\`.`,
|
||||
'',
|
||||
`<sub>[View run](${runUrl})</sub>`,
|
||||
].join('\n');
|
||||
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
core.info(`Updated osv-scanner comment for ${lockfile}.`);
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body,
|
||||
});
|
||||
core.info(`Posted new osv-scanner comment for ${lockfile}.`);
|
||||
}
|
||||
|
||||
- name: Enforce osv-scanner severity gate
|
||||
shell: bash
|
||||
env:
|
||||
SCAN_OUTCOME: ${{ steps.scan.outcome }}
|
||||
run: |
|
||||
if [ "${SCAN_OUTCOME}" != "success" ]; then
|
||||
echo "osv-scanner gate: scan reported findings (outcome=${SCAN_OUTCOME})" >&2
|
||||
exit 1
|
||||
fi
|
||||
+30
-40
@@ -1,5 +1,5 @@
|
||||
name: 'Setup Python with Poetry'
|
||||
description: 'Setup Python environment with Poetry and install dependencies'
|
||||
name: 'Setup Python with uv'
|
||||
description: 'Setup Python environment with uv and install dependencies'
|
||||
author: 'Prowler'
|
||||
|
||||
inputs:
|
||||
@@ -7,23 +7,15 @@ inputs:
|
||||
description: 'Python version to use'
|
||||
required: true
|
||||
working-directory:
|
||||
description: 'Working directory for Poetry'
|
||||
description: 'Working directory for uv'
|
||||
required: false
|
||||
default: '.'
|
||||
poetry-version:
|
||||
description: 'Poetry version to install'
|
||||
uv-version:
|
||||
description: 'uv version to install'
|
||||
required: false
|
||||
default: '2.3.4'
|
||||
default: '0.11.14'
|
||||
install-dependencies:
|
||||
description: 'Install Python dependencies with Poetry'
|
||||
required: false
|
||||
default: 'true'
|
||||
update-lock:
|
||||
description: 'Run `poetry lock` during setup. Only enable when a prior step mutates pyproject.toml (e.g. API `@master` VCS rewrite). Default: false.'
|
||||
required: false
|
||||
default: 'false'
|
||||
enable-cache:
|
||||
description: 'Whether to enable Poetry dependency caching via actions/setup-python'
|
||||
description: 'Install Python dependencies with uv'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
@@ -47,54 +39,52 @@ runs:
|
||||
sed -i "s|\(git+https://github.com/prowler-cloud/prowler[^@]*\)@master|\1@$BRANCH_NAME|g" pyproject.toml
|
||||
fi
|
||||
|
||||
- name: Install poetry
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pipx install poetry==${INPUTS_POETRY_VERSION}
|
||||
env:
|
||||
INPUTS_POETRY_VERSION: ${{ inputs.poetry-version }}
|
||||
|
||||
- name: Update poetry.lock with latest Prowler commit
|
||||
- name: Update uv.lock with latest Prowler commit
|
||||
if: github.repository_owner == 'prowler-cloud' && github.repository != 'prowler-cloud/prowler'
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
LATEST_COMMIT=$(curl -s "https://api.github.com/repos/prowler-cloud/prowler/commits/master" | jq -r '.sha')
|
||||
echo "Latest commit hash: $LATEST_COMMIT"
|
||||
sed -i '/url = "https:\/\/github\.com\/prowler-cloud\/prowler\.git"/,/resolved_reference = / {
|
||||
s/resolved_reference = "[a-f0-9]\{40\}"/resolved_reference = "'"$LATEST_COMMIT"'"/
|
||||
}' poetry.lock
|
||||
echo "Updated resolved_reference:"
|
||||
grep -A2 -B2 "resolved_reference" poetry.lock
|
||||
sed -i "s|\(git = \"https://github\.com/prowler-cloud/prowler\.git?rev=master\)#[a-f0-9]\{40\}\"|\1#${LATEST_COMMIT}\"|g" uv.lock
|
||||
echo "Updated uv.lock entry:"
|
||||
grep "prowler-cloud/prowler" uv.lock
|
||||
|
||||
- name: Update poetry.lock (prowler repo only)
|
||||
if: github.repository == 'prowler-cloud/prowler' && inputs.update-lock == 'true'
|
||||
- name: Update uv.lock SDK commit (prowler repo on push)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'prowler-cloud/prowler'
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: poetry lock
|
||||
run: |
|
||||
LATEST_COMMIT=$(curl -s "https://api.github.com/repos/prowler-cloud/prowler/commits/master" | jq -r '.sha')
|
||||
echo "Latest commit hash: $LATEST_COMMIT"
|
||||
sed -i "s|\(git = \"https://github\.com/prowler-cloud/prowler\.git?rev=master\)#[a-f0-9]\{40\}\"|\1#${LATEST_COMMIT}\"|g" uv.lock
|
||||
echo "Updated uv.lock entry:"
|
||||
grep "prowler-cloud/prowler" uv.lock
|
||||
|
||||
- name: Install uv
|
||||
shell: bash
|
||||
env:
|
||||
UV_VERSION: ${{ inputs.uv-version }}
|
||||
run: pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir "uv==${UV_VERSION}"
|
||||
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
# Disable cache when callers skip dependency install: Poetry 2.3.4 creates
|
||||
# the venv in a path setup-python can't hash, breaking the post-step save-cache.
|
||||
cache: ${{ inputs.enable-cache == 'true' && 'poetry' || '' }}
|
||||
cache-dependency-path: ${{ inputs.enable-cache == 'true' && format('{0}/poetry.lock', inputs.working-directory) || '' }}
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install Python dependencies
|
||||
if: inputs.install-dependencies == 'true'
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
poetry install --no-root
|
||||
poetry run pip list
|
||||
uv sync --no-install-project
|
||||
uv run pip list
|
||||
|
||||
- name: Update Prowler Cloud API Client
|
||||
if: github.repository_owner == 'prowler-cloud' && github.repository != 'prowler-cloud/prowler'
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
poetry remove prowler-cloud-api-client
|
||||
poetry add ./prowler-cloud-api-client
|
||||
uv remove prowler-cloud-api-client
|
||||
uv add ./prowler-cloud-api-client
|
||||
@@ -49,7 +49,7 @@ Please add a detailed description of how to review this PR.
|
||||
- [ ] Performance test results (if applicable)
|
||||
- [ ] Any other relevant evidence of the implementation (if applicable)
|
||||
- [ ] Verify if API specs need to be regenerated.
|
||||
- [ ] Check if version updates are required (e.g., specs, Poetry, etc.).
|
||||
- [ ] Check if version updates are required (e.g., specs, uv, etc.).
|
||||
- [ ] Ensure new entries are added to [CHANGELOG.md](https://github.com/prowler-cloud/prowler/blob/master/api/CHANGELOG.md), if applicable.
|
||||
|
||||
### License
|
||||
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run osv-scanner and fail when findings match the configured severity levels.
|
||||
#
|
||||
# Replaces `safety check --policy-file .safety-policy.yml`. Used by:
|
||||
# - .github/actions/osv-scanner/action.yml (composite action)
|
||||
# - .github/workflows/api-security.yml, sdk-security.yml, ui-security.yml
|
||||
#
|
||||
# Severity levels (comma-separated) are read from OSV_SEVERITY_LEVELS.
|
||||
# Default: HIGH,CRITICAL,UNKNOWN — preserves prior .safety-policy.yml policy
|
||||
# (ignore-cvss-severity-below: 7 + ignore-cvss-unknown-severity: False).
|
||||
# osv-scanner has no native CVSS threshold (google/osv-scanner#1400, closed
|
||||
# not-planned). Severity is derived from $group.max_severity (numeric CVSS
|
||||
# score string) which osv-scanner emits per group.
|
||||
#
|
||||
# CVSS v3 score → categorical label:
|
||||
# CRITICAL >= 9.0
|
||||
# HIGH >= 7.0
|
||||
# MEDIUM >= 4.0
|
||||
# LOW > 0.0
|
||||
# UNKNOWN no score available
|
||||
#
|
||||
# Per-vulnerability ignores (with reason + expiry) live in osv-scanner.toml at
|
||||
# the repo root, if it exists. Without that file, osv-scanner uses defaults.
|
||||
#
|
||||
# Usage:
|
||||
# osv-scan.sh [osv-scanner pass-through args...]
|
||||
# Examples:
|
||||
# osv-scan.sh --lockfile=uv.lock
|
||||
# osv-scan.sh --recursive .
|
||||
# OSV_SEVERITY_LEVELS=CRITICAL osv-scan.sh --lockfile=uv.lock
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel)"
|
||||
CONFIG="${ROOT}/osv-scanner.toml"
|
||||
SEVERITY_LEVELS="${OSV_SEVERITY_LEVELS:-HIGH,CRITICAL,UNKNOWN}"
|
||||
|
||||
for bin in osv-scanner jq; do
|
||||
if ! command -v "${bin}" >/dev/null 2>&1; then
|
||||
echo "error: ${bin} not found in PATH" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
SCAN_ARGS=()
|
||||
if [ -f "${CONFIG}" ]; then
|
||||
SCAN_ARGS+=(--config="${CONFIG}")
|
||||
fi
|
||||
|
||||
# Exit codes: 0=clean, 1=findings, 127=no supported files, 128=internal error.
|
||||
STDERR="$(mktemp)"
|
||||
trap 'rm -f "${STDERR}"' EXIT
|
||||
|
||||
set +e
|
||||
OUTPUT="$(osv-scanner scan source "${SCAN_ARGS[@]}" --format=json "$@" 2>"${STDERR}")"
|
||||
RC=$?
|
||||
set -e
|
||||
|
||||
case "${RC}" in
|
||||
0|1) ;;
|
||||
127) echo "osv-scanner: no supported lockfiles in scan target"; exit 0 ;;
|
||||
*)
|
||||
echo "osv-scanner: exited with code ${RC}" >&2
|
||||
[ -s "${STDERR}" ] && cat "${STDERR}" >&2
|
||||
exit "${RC}"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Build a JSON array of normalized severity levels for jq.
|
||||
SEVERITY_JSON="$(printf '%s' "${SEVERITY_LEVELS}" | jq -Rc '
|
||||
split(",") | map(ascii_upcase | sub("^\\s+"; "") | sub("\\s+$"; ""))
|
||||
')"
|
||||
|
||||
# Walk each vulnerability, look up its group's max_severity (numeric CVSS),
|
||||
# map to a categorical label, then filter by OSV_SEVERITY_LEVELS.
|
||||
FINDINGS="$(printf '%s' "${OUTPUT}" | jq --argjson sevs "${SEVERITY_JSON}" '
|
||||
[ .results[]?.packages[]?
|
||||
| . as $pkg
|
||||
| ($pkg.groups // []) as $groups
|
||||
| ($pkg.vulnerabilities // [])[]
|
||||
| . as $vuln
|
||||
| ([ $groups[] | select((.ids // []) | index($vuln.id)) ][0] // {}) as $group
|
||||
| (($group.max_severity // "") | tonumber? // null) as $score
|
||||
| (if $score == null then "UNKNOWN"
|
||||
elif $score >= 9.0 then "CRITICAL"
|
||||
elif $score >= 7.0 then "HIGH"
|
||||
elif $score >= 4.0 then "MEDIUM"
|
||||
elif $score > 0 then "LOW"
|
||||
else "UNKNOWN"
|
||||
end) as $label
|
||||
| {
|
||||
id: $vuln.id,
|
||||
severity: $label,
|
||||
score: $score,
|
||||
summary: ($vuln.summary // null),
|
||||
package: $pkg.package.name,
|
||||
version: $pkg.package.version,
|
||||
ecosystem: $pkg.package.ecosystem
|
||||
}
|
||||
| select(.severity as $s | $sevs | any(. == $s))
|
||||
]
|
||||
')"
|
||||
|
||||
COUNT="$(printf '%s' "${FINDINGS}" | jq 'length')"
|
||||
|
||||
# Write the findings JSON to OSV_REPORT_FILE so callers (e.g. the composite
|
||||
# action's PR-comment step) can consume the same data the gate decision uses.
|
||||
if [ -n "${OSV_REPORT_FILE:-}" ]; then
|
||||
printf '%s' "${FINDINGS}" > "${OSV_REPORT_FILE}"
|
||||
fi
|
||||
|
||||
if [ "${COUNT}" -gt 0 ]; then
|
||||
echo "osv-scanner: ${COUNT} finding(s) at severity ${SEVERITY_LEVELS}"
|
||||
printf '%s' "${FINDINGS}" | jq -r '
|
||||
.[] | " [\(.severity)\(if .score then " \(.score)" else "" end)] \(.id) \(.ecosystem)/\(.package)@\(.version) — \(.summary // "(no summary)")"
|
||||
'
|
||||
echo
|
||||
echo "To accept a finding, create osv-scanner.toml at the repo root with a reason and ignoreUntil."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "osv-scanner: no findings at severity levels: ${SEVERITY_LEVELS}"
|
||||
@@ -43,6 +43,7 @@ jobs:
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
api.github.com:443
|
||||
raw.githubusercontent.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -63,26 +64,25 @@ jobs:
|
||||
api/CHANGELOG.md
|
||||
api/AGENTS.md
|
||||
|
||||
- name: Setup Python with Poetry
|
||||
- name: Setup Python with uv
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/setup-python-poetry
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
working-directory: ./api
|
||||
update-lock: 'true'
|
||||
|
||||
- name: Poetry check
|
||||
- name: uv lock check
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry check --lock
|
||||
run: uv lock --check
|
||||
|
||||
- name: Ruff lint
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry run ruff check . --exclude contrib
|
||||
run: uv run ruff check . --exclude contrib
|
||||
|
||||
- name: Ruff format
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry run ruff format --check . --exclude contrib
|
||||
run: uv run ruff format --check . --exclude contrib
|
||||
|
||||
- name: Pylint
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry run pylint --disable=W,C,R,E -j 0 -rn -sn src/
|
||||
run: uv run pylint --disable=W,C,R,E -j 0 -rn -sn src/
|
||||
|
||||
@@ -9,7 +9,9 @@ on:
|
||||
- 'api/**'
|
||||
- '.github/workflows/api-tests.yml'
|
||||
- '.github/workflows/api-security.yml'
|
||||
- '.github/actions/setup-python-poetry/**'
|
||||
- '.github/actions/setup-python-uv/**'
|
||||
- '.github/actions/osv-scanner/**'
|
||||
- '.github/scripts/osv-scan.sh'
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
@@ -18,7 +20,9 @@ on:
|
||||
- 'api/**'
|
||||
- '.github/workflows/api-tests.yml'
|
||||
- '.github/workflows/api-security.yml'
|
||||
- '.github/actions/setup-python-poetry/**'
|
||||
- '.github/actions/setup-python-uv/**'
|
||||
- '.github/actions/osv-scanner/**'
|
||||
- '.github/scripts/osv-scan.sh'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -35,6 +39,7 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write # osv-scanner action posts/updates a PR comment with findings
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
@@ -52,10 +57,12 @@ jobs:
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
github.com:443
|
||||
auth.safetycli.com:443
|
||||
pyup.io:443
|
||||
data.safetycli.com:443
|
||||
api.github.com:443
|
||||
objects.githubusercontent.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
api.osv.dev:443
|
||||
api.deps.dev:443
|
||||
osv-vulnerabilities.storage.googleapis.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -70,30 +77,34 @@ jobs:
|
||||
files: |
|
||||
api/**
|
||||
.github/workflows/api-security.yml
|
||||
.safety-policy.yml
|
||||
.github/actions/osv-scanner/**
|
||||
.github/scripts/osv-scan.sh
|
||||
files_ignore: |
|
||||
api/docs/**
|
||||
api/README.md
|
||||
api/CHANGELOG.md
|
||||
api/AGENTS.md
|
||||
|
||||
- name: Setup Python with Poetry
|
||||
- name: Setup Python with uv
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/setup-python-poetry
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
working-directory: ./api
|
||||
update-lock: 'true'
|
||||
|
||||
- name: Bandit
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry run bandit -q -lll -x '*_test.py,./contrib/' -r .
|
||||
# Exclude .venv because uv places the project venv inside ./api; otherwise
|
||||
# bandit would recurse into installed third-party packages.
|
||||
run: uv run bandit -q -lll -x '*_test.py,./contrib/,./.venv/' -r .
|
||||
|
||||
- name: Safety
|
||||
- name: Dependency vulnerability scan with osv-scanner
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
# Accepted CVEs, severity threshold, and ignore expirations live in ../.safety-policy.yml
|
||||
run: poetry run safety check --policy-file ../.safety-policy.yml
|
||||
uses: ./.github/actions/osv-scanner
|
||||
with:
|
||||
lockfile: api/uv.lock
|
||||
|
||||
- name: Vulture
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry run vulture --exclude "contrib,tests,conftest.py" --min-confidence 100 .
|
||||
# Run even when osv-scanner reports findings so dead-code signal isn't masked by SCA failures.
|
||||
if: ${{ !cancelled() && steps.check-changes.outputs.any_changed == 'true' }}
|
||||
run: uv run vulture --exclude "contrib,tests,conftest.py,.venv" --min-confidence 100 .
|
||||
|
||||
@@ -87,6 +87,7 @@ jobs:
|
||||
files.pythonhosted.org:443
|
||||
cli.codecov.io:443
|
||||
keybase.io:443
|
||||
raw.githubusercontent.com:443
|
||||
ingest.codecov.io:443
|
||||
storage.googleapis.com:443
|
||||
o26192.ingest.us.sentry.io:443
|
||||
@@ -112,17 +113,16 @@ jobs:
|
||||
api/CHANGELOG.md
|
||||
api/AGENTS.md
|
||||
|
||||
- name: Setup Python with Poetry
|
||||
- name: Setup Python with uv
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/setup-python-poetry
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
working-directory: ./api
|
||||
update-lock: 'true'
|
||||
|
||||
- name: Run tests with pytest
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry run pytest --cov=./src/backend --cov-report=xml src/backend
|
||||
run: uv run pytest --cov=./src/backend --cov-report=xml src/backend
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
api.github.com:443
|
||||
|
||||
- name: Comment and lock issue
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
Generated
+24
-24
@@ -75,7 +75,7 @@ jobs:
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Check workflow file timestamps
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_WORKFLOW_FILE: "issue-triage.lock.yml"
|
||||
with:
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
await main();
|
||||
- name: Compute current body text
|
||||
id: compute-text
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
- name: Add comment with workflow run link
|
||||
id: add-comment
|
||||
if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.id == github.repository_id)
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_WORKFLOW_NAME: "Issue Triage"
|
||||
GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🤖 Generated by [Prowler Issue Triage]({run_url}) [Experimental]\"}"
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Merge remote .github folder
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_FILE: ".github/agents/issue-triage.md"
|
||||
GH_AW_AGENT_IMPORT_SPEC: "../agents/issue-triage.md"
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
id: checkout-pr
|
||||
if: |
|
||||
github.event.pull_request
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -187,7 +187,7 @@ jobs:
|
||||
await main();
|
||||
- name: Generate agentic run info
|
||||
id: generate_aw_info
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
@@ -511,7 +511,7 @@ jobs:
|
||||
}
|
||||
GH_AW_MCP_CONFIG_EOF
|
||||
- name: Generate workflow overview
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
|
||||
@@ -606,7 +606,7 @@ jobs:
|
||||
{{#runtime-import .github/workflows/issue-triage.md}}
|
||||
GH_AW_PROMPT_EOF
|
||||
- name: Substitute placeholders
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
|
||||
@@ -640,7 +640,7 @@ jobs:
|
||||
}
|
||||
});
|
||||
- name: Interpolate variables and render templates
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
@@ -757,7 +757,7 @@ jobs:
|
||||
bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
|
||||
- name: Redact secrets in logs
|
||||
if: always()
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
||||
@@ -779,7 +779,7 @@ jobs:
|
||||
if-no-files-found: warn
|
||||
- name: Ingest agent output
|
||||
id: collect_output
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
|
||||
GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,mcp.context7.com,mcp.prowler.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
|
||||
@@ -808,7 +808,7 @@ jobs:
|
||||
if-no-files-found: ignore
|
||||
- name: Parse agent logs for step summary
|
||||
if: always()
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
|
||||
with:
|
||||
@@ -819,7 +819,7 @@ jobs:
|
||||
await main();
|
||||
- name: Parse MCP gateway logs for step summary
|
||||
if: always()
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
||||
@@ -891,7 +891,7 @@ jobs:
|
||||
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
|
||||
- name: Process No-Op Messages
|
||||
id: noop
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_NOOP_MAX: 1
|
||||
@@ -905,7 +905,7 @@ jobs:
|
||||
await main();
|
||||
- name: Record Missing Tool
|
||||
id: missing_tool
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_WORKFLOW_NAME: "Issue Triage"
|
||||
@@ -918,7 +918,7 @@ jobs:
|
||||
await main();
|
||||
- name: Handle Agent Failure
|
||||
id: handle_agent_failure
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_WORKFLOW_NAME: "Issue Triage"
|
||||
@@ -937,7 +937,7 @@ jobs:
|
||||
await main();
|
||||
- name: Handle No-Op Message
|
||||
id: handle_noop_message
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_WORKFLOW_NAME: "Issue Triage"
|
||||
@@ -954,7 +954,7 @@ jobs:
|
||||
await main();
|
||||
- name: Update reaction comment with completion status
|
||||
id: conclusion
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
|
||||
@@ -1008,7 +1008,7 @@ jobs:
|
||||
run: |
|
||||
echo "Agent output-types: $AGENT_OUTPUT_TYPES"
|
||||
- name: Setup threat detection
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
WORKFLOW_NAME: "Issue Triage"
|
||||
WORKFLOW_DESCRIPTION: "[Experimental] AI-powered issue triage for Prowler - produces coding-agent-ready fix plans"
|
||||
@@ -1062,7 +1062,7 @@ jobs:
|
||||
XDG_CONFIG_HOME: /home/runner
|
||||
- name: Parse threat detection results
|
||||
id: parse_results
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
|
||||
@@ -1102,7 +1102,7 @@ jobs:
|
||||
- name: Add eyes reaction for immediate feedback
|
||||
id: react
|
||||
if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.id == github.repository_id)
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_REACTION: "eyes"
|
||||
with:
|
||||
@@ -1114,7 +1114,7 @@ jobs:
|
||||
await main();
|
||||
- name: Check team membership for workflow
|
||||
id: check_membership
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_REQUIRED_ROLES: admin,maintainer,write
|
||||
with:
|
||||
@@ -1126,7 +1126,7 @@ jobs:
|
||||
await main();
|
||||
- name: Check user rate limit
|
||||
id: check_rate_limit
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_RATE_LIMIT_MAX: "5"
|
||||
GH_AW_RATE_LIMIT_WINDOW: "60"
|
||||
@@ -1185,7 +1185,7 @@ jobs:
|
||||
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
|
||||
- name: Process Safe Outputs
|
||||
id: process_safe_outputs
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}"
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
ui/**
|
||||
prowler/**
|
||||
mcp_server/**
|
||||
poetry.lock
|
||||
uv.lock
|
||||
pyproject.toml
|
||||
|
||||
- name: Check for folder changes and changelog presence
|
||||
@@ -84,9 +84,9 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
# Check root-level dependency files (poetry.lock, pyproject.toml)
|
||||
# Check root-level dependency files (uv.lock, pyproject.toml)
|
||||
# These are associated with the prowler folder changelog
|
||||
root_deps_changed=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n' | grep -E "^(poetry\.lock|pyproject\.toml)$" || true)
|
||||
root_deps_changed=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n' | grep -E "^(uv\.lock|pyproject\.toml)$" || true)
|
||||
if [ -n "$root_deps_changed" ]; then
|
||||
echo "Detected changes in root dependency files: $root_deps_changed"
|
||||
# Check if prowler/CHANGELOG.md was already updated (might have been caught above)
|
||||
|
||||
@@ -40,12 +40,11 @@ jobs:
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Python with Poetry
|
||||
uses: ./.github/actions/setup-python-poetry
|
||||
- name: Setup Python with uv
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
with:
|
||||
python-version: '3.12'
|
||||
install-dependencies: 'false'
|
||||
enable-cache: 'false'
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
@@ -339,10 +338,11 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update poetry lock file
|
||||
echo "Updating poetry.lock file..."
|
||||
# Update uv lock file
|
||||
echo "Updating uv.lock file..."
|
||||
pip install --no-cache-dir uv==0.11.14
|
||||
cd api
|
||||
poetry lock
|
||||
uv lock
|
||||
cd ..
|
||||
|
||||
echo "✓ Prepared prowler dependency update to: $UPDATED_PROWLER_REF"
|
||||
@@ -357,7 +357,7 @@ jobs:
|
||||
base: ${{ env.BRANCH_NAME }}
|
||||
add-paths: |
|
||||
api/pyproject.toml
|
||||
api/poetry.lock
|
||||
api/uv.lock
|
||||
title: "chore(api): Update prowler dependency to ${{ env.BRANCH_NAME }} for release ${{ env.PROWLER_VERSION }}"
|
||||
body: |
|
||||
### Description
|
||||
@@ -366,7 +366,7 @@ jobs:
|
||||
|
||||
**Changes:**
|
||||
- Updates `api/pyproject.toml` prowler dependency from `@master` to `@${{ env.BRANCH_NAME }}`
|
||||
- Updates `api/poetry.lock` file with resolved dependencies
|
||||
- Updates `api/uv.lock` file with resolved dependencies
|
||||
|
||||
This PR should be merged into the `${{ env.BRANCH_NAME }}` release branch.
|
||||
|
||||
|
||||
@@ -71,24 +71,26 @@ jobs:
|
||||
contrib/**
|
||||
**/AGENTS.md
|
||||
|
||||
- name: Setup Python with Poetry
|
||||
- name: Setup Python with uv
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/setup-python-poetry
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Check Poetry lock file
|
||||
- name: Check uv lock file
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry check --lock
|
||||
run: uv lock --check
|
||||
|
||||
- name: Lint with flake8
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry run flake8 . --ignore=E266,W503,E203,E501,W605,E128 --exclude contrib,ui,api,skills
|
||||
run: uv run flake8 . --ignore=E266,W503,E203,E501,W605,E128 --exclude .venv,contrib,ui,api,skills,mcp_server
|
||||
|
||||
- name: Check format with black
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry run black --exclude "api|ui|skills" --check .
|
||||
# mcp_server has its own pyproject and uses ruff format, exclude it so SDK black
|
||||
# does not fight ruff over rules it never formatted.
|
||||
run: uv run black --exclude "\.venv|api|ui|skills|mcp_server" --check .
|
||||
|
||||
- name: Lint with pylint
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry run pylint --disable=W,C,R,E -j 0 -rn -sn prowler/
|
||||
run: uv run pylint --disable=W,C,R,E -j 0 -rn -sn prowler/
|
||||
|
||||
@@ -73,20 +73,10 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Python with Poetry
|
||||
uses: ./.github/actions/setup-python-poetry
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
install-dependencies: 'false'
|
||||
enable-cache: 'false'
|
||||
|
||||
- name: Inject poetry-bumpversion plugin
|
||||
run: pipx inject poetry poetry-bumpversion
|
||||
|
||||
- name: Get Prowler version and set tags
|
||||
id: get-prowler-version
|
||||
run: |
|
||||
PROWLER_VERSION="$(poetry version -s 2>/dev/null)"
|
||||
PROWLER_VERSION="$(grep -E '^version = ' pyproject.toml | sed -E 's/version = "([^"]+)"/\1/' | tr -d '[:space:]')"
|
||||
echo "prowler_version=${PROWLER_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
PROWLER_VERSION_MAJOR="${PROWLER_VERSION%%.*}"
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
- 'prowler/**'
|
||||
- 'Dockerfile*'
|
||||
- 'pyproject.toml'
|
||||
- 'poetry.lock'
|
||||
- 'uv.lock'
|
||||
- '.github/workflows/sdk-container-checks.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -19,7 +19,7 @@ on:
|
||||
- 'prowler/**'
|
||||
- 'Dockerfile*'
|
||||
- 'pyproject.toml'
|
||||
- 'poetry.lock'
|
||||
- 'uv.lock'
|
||||
- '.github/workflows/sdk-container-checks.yml'
|
||||
|
||||
concurrency:
|
||||
|
||||
@@ -75,15 +75,14 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Python with Poetry
|
||||
uses: ./.github/actions/setup-python-poetry
|
||||
- name: Setup Python with uv
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
install-dependencies: 'false'
|
||||
enable-cache: 'false'
|
||||
|
||||
- name: Build Prowler package
|
||||
run: poetry build
|
||||
run: uv build
|
||||
|
||||
- name: Publish Prowler package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
@@ -112,12 +111,11 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Python with Poetry
|
||||
uses: ./.github/actions/setup-python-poetry
|
||||
- name: Setup Python with uv
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
install-dependencies: 'false'
|
||||
enable-cache: 'false'
|
||||
|
||||
- name: Install toml package
|
||||
run: pip install toml
|
||||
@@ -128,7 +126,7 @@ jobs:
|
||||
python util/replicate_pypi_package.py
|
||||
|
||||
- name: Build prowler-cloud package
|
||||
run: poetry build
|
||||
run: uv build
|
||||
|
||||
- name: Publish prowler-cloud package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
|
||||
@@ -9,10 +9,12 @@ on:
|
||||
- 'prowler/**'
|
||||
- 'tests/**'
|
||||
- 'pyproject.toml'
|
||||
- 'poetry.lock'
|
||||
- 'uv.lock'
|
||||
- '.github/workflows/sdk-tests.yml'
|
||||
- '.github/workflows/sdk-security.yml'
|
||||
- '.github/actions/setup-python-poetry/**'
|
||||
- '.github/actions/setup-python-uv/**'
|
||||
- '.github/actions/osv-scanner/**'
|
||||
- '.github/scripts/osv-scan.sh'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
@@ -21,10 +23,12 @@ on:
|
||||
- 'prowler/**'
|
||||
- 'tests/**'
|
||||
- 'pyproject.toml'
|
||||
- 'poetry.lock'
|
||||
- 'uv.lock'
|
||||
- '.github/workflows/sdk-tests.yml'
|
||||
- '.github/workflows/sdk-security.yml'
|
||||
- '.github/actions/setup-python-poetry/**'
|
||||
- '.github/actions/setup-python-uv/**'
|
||||
- '.github/actions/osv-scanner/**'
|
||||
- '.github/scripts/osv-scan.sh'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -39,6 +43,7 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write # osv-scanner action posts/updates a PR comment with findings
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
@@ -49,10 +54,12 @@ jobs:
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
github.com:443
|
||||
auth.safetycli.com:443
|
||||
pyup.io:443
|
||||
data.safetycli.com:443
|
||||
api.github.com:443
|
||||
objects.githubusercontent.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
api.osv.dev:443
|
||||
api.deps.dev:443
|
||||
osv-vulnerabilities.storage.googleapis.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -87,21 +94,23 @@ jobs:
|
||||
contrib/**
|
||||
**/AGENTS.md
|
||||
|
||||
- name: Setup Python with Poetry
|
||||
- name: Setup Python with uv
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/setup-python-poetry
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Security scan with Bandit
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry run bandit -q -lll -x '*_test.py,./contrib/,./api/,./ui' -r .
|
||||
run: uv run bandit -q -lll -x '*_test.py,./.venv/,./contrib/,./api/,./ui' -r .
|
||||
|
||||
- name: Security scan with Safety
|
||||
- name: Dependency vulnerability scan with osv-scanner
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
# Accepted CVEs, severity threshold, and ignore expirations live in .safety-policy.yml
|
||||
run: poetry run safety check -r pyproject.toml --policy-file .safety-policy.yml
|
||||
uses: ./.github/actions/osv-scanner
|
||||
with:
|
||||
lockfile: uv.lock
|
||||
|
||||
- name: Dead code detection with Vulture
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: poetry run vulture --exclude "contrib,api,ui" --min-confidence 100 .
|
||||
# Run even when osv-scanner reports findings so dead-code signal isn't masked by SCA failures.
|
||||
if: ${{ !cancelled() && steps.check-changes.outputs.any_changed == 'true' }}
|
||||
run: uv run vulture --exclude ".venv,contrib,api,ui" --min-confidence 100 .
|
||||
|
||||
@@ -92,9 +92,9 @@ jobs:
|
||||
contrib/**
|
||||
**/AGENTS.md
|
||||
|
||||
- name: Setup Python with Poetry
|
||||
- name: Setup Python with uv
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/setup-python-poetry
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/aws/**
|
||||
./tests/**/aws/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Resolve AWS services under test
|
||||
if: steps.changed-aws.outputs.any_changed == 'true'
|
||||
@@ -209,11 +209,11 @@ jobs:
|
||||
echo "AWS service_paths='${STEPS_AWS_SERVICES_OUTPUTS_SERVICE_PATHS}'"
|
||||
|
||||
if [ "${STEPS_AWS_SERVICES_OUTPUTS_RUN_ALL}" = "true" ]; then
|
||||
poetry run pytest -n auto --cov=./prowler/providers/aws --cov-report=xml:aws_coverage.xml tests/providers/aws
|
||||
uv run pytest -n auto --cov=./prowler/providers/aws --cov-report=xml:aws_coverage.xml tests/providers/aws
|
||||
elif [ -z "${STEPS_AWS_SERVICES_OUTPUTS_SERVICE_PATHS}" ]; then
|
||||
echo "No AWS service paths detected; skipping AWS tests."
|
||||
else
|
||||
poetry run pytest -n auto --cov=./prowler/providers/aws --cov-report=xml:aws_coverage.xml ${STEPS_AWS_SERVICES_OUTPUTS_SERVICE_PATHS}
|
||||
uv run pytest -n auto --cov=./prowler/providers/aws --cov-report=xml:aws_coverage.xml ${STEPS_AWS_SERVICES_OUTPUTS_SERVICE_PATHS}
|
||||
fi
|
||||
env:
|
||||
STEPS_AWS_SERVICES_OUTPUTS_RUN_ALL: ${{ steps.aws-services.outputs.run_all }}
|
||||
@@ -237,11 +237,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/azure/**
|
||||
./tests/**/azure/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run Azure tests
|
||||
if: steps.changed-azure.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/azure --cov-report=xml:azure_coverage.xml tests/providers/azure
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/azure --cov-report=xml:azure_coverage.xml tests/providers/azure
|
||||
|
||||
- name: Upload Azure coverage to Codecov
|
||||
if: steps.changed-azure.outputs.any_changed == 'true'
|
||||
@@ -261,11 +261,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/gcp/**
|
||||
./tests/**/gcp/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run GCP tests
|
||||
if: steps.changed-gcp.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/gcp --cov-report=xml:gcp_coverage.xml tests/providers/gcp
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/gcp --cov-report=xml:gcp_coverage.xml tests/providers/gcp
|
||||
|
||||
- name: Upload GCP coverage to Codecov
|
||||
if: steps.changed-gcp.outputs.any_changed == 'true'
|
||||
@@ -285,11 +285,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/kubernetes/**
|
||||
./tests/**/kubernetes/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run Kubernetes tests
|
||||
if: steps.changed-kubernetes.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/kubernetes --cov-report=xml:kubernetes_coverage.xml tests/providers/kubernetes
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/kubernetes --cov-report=xml:kubernetes_coverage.xml tests/providers/kubernetes
|
||||
|
||||
- name: Upload Kubernetes coverage to Codecov
|
||||
if: steps.changed-kubernetes.outputs.any_changed == 'true'
|
||||
@@ -309,11 +309,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/github/**
|
||||
./tests/**/github/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run GitHub tests
|
||||
if: steps.changed-github.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/github --cov-report=xml:github_coverage.xml tests/providers/github
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/github --cov-report=xml:github_coverage.xml tests/providers/github
|
||||
|
||||
- name: Upload GitHub coverage to Codecov
|
||||
if: steps.changed-github.outputs.any_changed == 'true'
|
||||
@@ -333,11 +333,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/okta/**
|
||||
./tests/**/okta/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run Okta tests
|
||||
if: steps.changed-okta.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/okta --cov-report=xml:okta_coverage.xml tests/providers/okta
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/okta --cov-report=xml:okta_coverage.xml tests/providers/okta
|
||||
|
||||
- name: Upload Okta coverage to Codecov
|
||||
if: steps.changed-okta.outputs.any_changed == 'true'
|
||||
@@ -357,11 +357,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/nhn/**
|
||||
./tests/**/nhn/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run NHN tests
|
||||
if: steps.changed-nhn.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/nhn --cov-report=xml:nhn_coverage.xml tests/providers/nhn
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/nhn --cov-report=xml:nhn_coverage.xml tests/providers/nhn
|
||||
|
||||
- name: Upload NHN coverage to Codecov
|
||||
if: steps.changed-nhn.outputs.any_changed == 'true'
|
||||
@@ -381,11 +381,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/m365/**
|
||||
./tests/**/m365/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run M365 tests
|
||||
if: steps.changed-m365.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/m365 --cov-report=xml:m365_coverage.xml tests/providers/m365
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/m365 --cov-report=xml:m365_coverage.xml tests/providers/m365
|
||||
|
||||
- name: Upload M365 coverage to Codecov
|
||||
if: steps.changed-m365.outputs.any_changed == 'true'
|
||||
@@ -405,11 +405,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/iac/**
|
||||
./tests/**/iac/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run IaC tests
|
||||
if: steps.changed-iac.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/iac --cov-report=xml:iac_coverage.xml tests/providers/iac
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/iac --cov-report=xml:iac_coverage.xml tests/providers/iac
|
||||
|
||||
- name: Upload IaC coverage to Codecov
|
||||
if: steps.changed-iac.outputs.any_changed == 'true'
|
||||
@@ -429,11 +429,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/mongodbatlas/**
|
||||
./tests/**/mongodbatlas/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run MongoDB Atlas tests
|
||||
if: steps.changed-mongodbatlas.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/mongodbatlas --cov-report=xml:mongodbatlas_coverage.xml tests/providers/mongodbatlas
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/mongodbatlas --cov-report=xml:mongodbatlas_coverage.xml tests/providers/mongodbatlas
|
||||
|
||||
- name: Upload MongoDB Atlas coverage to Codecov
|
||||
if: steps.changed-mongodbatlas.outputs.any_changed == 'true'
|
||||
@@ -453,11 +453,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/oraclecloud/**
|
||||
./tests/**/oraclecloud/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run OCI tests
|
||||
if: steps.changed-oraclecloud.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/oraclecloud --cov-report=xml:oraclecloud_coverage.xml tests/providers/oraclecloud
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/oraclecloud --cov-report=xml:oraclecloud_coverage.xml tests/providers/oraclecloud
|
||||
|
||||
- name: Upload OCI coverage to Codecov
|
||||
if: steps.changed-oraclecloud.outputs.any_changed == 'true'
|
||||
@@ -477,11 +477,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/openstack/**
|
||||
./tests/**/openstack/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run OpenStack tests
|
||||
if: steps.changed-openstack.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/openstack --cov-report=xml:openstack_coverage.xml tests/providers/openstack
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/openstack --cov-report=xml:openstack_coverage.xml tests/providers/openstack
|
||||
|
||||
- name: Upload OpenStack coverage to Codecov
|
||||
if: steps.changed-openstack.outputs.any_changed == 'true'
|
||||
@@ -501,11 +501,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/googleworkspace/**
|
||||
./tests/**/googleworkspace/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run Google Workspace tests
|
||||
if: steps.changed-googleworkspace.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/googleworkspace --cov-report=xml:googleworkspace_coverage.xml tests/providers/googleworkspace
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/googleworkspace --cov-report=xml:googleworkspace_coverage.xml tests/providers/googleworkspace
|
||||
|
||||
- name: Upload Google Workspace coverage to Codecov
|
||||
if: steps.changed-googleworkspace.outputs.any_changed == 'true'
|
||||
@@ -525,11 +525,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/**/vercel/**
|
||||
./tests/**/vercel/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run Vercel tests
|
||||
if: steps.changed-vercel.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/vercel --cov-report=xml:vercel_coverage.xml tests/providers/vercel
|
||||
run: uv run pytest -n auto --cov=./prowler/providers/vercel --cov-report=xml:vercel_coverage.xml tests/providers/vercel
|
||||
|
||||
- name: Upload Vercel coverage to Codecov
|
||||
if: steps.changed-vercel.outputs.any_changed == 'true'
|
||||
@@ -549,11 +549,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/lib/**
|
||||
./tests/lib/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run Lib tests
|
||||
if: steps.changed-lib.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/lib --cov-report=xml:lib_coverage.xml tests/lib
|
||||
run: uv run pytest -n auto --cov=./prowler/lib --cov-report=xml:lib_coverage.xml tests/lib
|
||||
|
||||
- name: Upload Lib coverage to Codecov
|
||||
if: steps.changed-lib.outputs.any_changed == 'true'
|
||||
@@ -573,11 +573,11 @@ jobs:
|
||||
files: |
|
||||
./prowler/config/**
|
||||
./tests/config/**
|
||||
./poetry.lock
|
||||
./uv.lock
|
||||
|
||||
- name: Run Config tests
|
||||
if: steps.changed-config.outputs.any_changed == 'true'
|
||||
run: poetry run pytest -n auto --cov=./prowler/config --cov-report=xml:config_coverage.xml tests/config
|
||||
run: uv run pytest -n auto --cov=./prowler/config --cov-report=xml:config_coverage.xml tests/config
|
||||
|
||||
- name: Upload Config coverage to Codecov
|
||||
if: steps.changed-config.outputs.any_changed == 'true'
|
||||
|
||||
@@ -130,6 +130,12 @@ jobs:
|
||||
echo "AWS_ACCESS_KEY_ID=${{ secrets.E2E_AWS_PROVIDER_ACCESS_KEY }}" >> .env
|
||||
echo "AWS_SECRET_ACCESS_KEY=${{ secrets.E2E_AWS_PROVIDER_SECRET_KEY }}" >> .env
|
||||
|
||||
- name: Build API image from current code
|
||||
# docker-compose.yml references prowlercloud/prowler-api:latest from the registry,
|
||||
# which lags behind PR changes; build locally so E2E exercises the API image
|
||||
# produced by this PR.
|
||||
run: docker build -t prowlercloud/prowler-api:latest ./api
|
||||
|
||||
- name: Start API services
|
||||
run: |
|
||||
export PROWLER_API_VERSION=latest
|
||||
@@ -158,7 +164,7 @@ jobs:
|
||||
for fixture in api/fixtures/dev/*.json; do
|
||||
if [ -f "$fixture" ]; then
|
||||
echo "Loading $fixture"
|
||||
poetry run python manage.py loaddata "$fixture" --database admin
|
||||
uv run python manage.py loaddata "$fixture" --database admin
|
||||
fi
|
||||
done
|
||||
'
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
name: 'UI: Security'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v5.*'
|
||||
paths:
|
||||
- 'ui/package.json'
|
||||
- 'ui/pnpm-lock.yaml'
|
||||
- '.github/workflows/ui-security.yml'
|
||||
- '.github/actions/osv-scanner/**'
|
||||
- '.github/scripts/osv-scan.sh'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v5.*'
|
||||
paths:
|
||||
- 'ui/package.json'
|
||||
- 'ui/pnpm-lock.yaml'
|
||||
- '.github/workflows/ui-security.yml'
|
||||
- '.github/actions/osv-scanner/**'
|
||||
- '.github/scripts/osv-scan.sh'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
ui-security-scans:
|
||||
if: github.repository == 'prowler-cloud/prowler'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write # osv-scanner action posts/updates a PR comment with findings
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
github.com:443
|
||||
api.github.com:443
|
||||
objects.githubusercontent.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
api.osv.dev:443
|
||||
api.deps.dev:443
|
||||
osv-vulnerabilities.storage.googleapis.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
|
||||
- name: Check for UI dependency changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
with:
|
||||
files: |
|
||||
ui/package.json
|
||||
ui/pnpm-lock.yaml
|
||||
.github/workflows/ui-security.yml
|
||||
.github/actions/osv-scanner/**
|
||||
.github/scripts/osv-scan.sh
|
||||
|
||||
- name: Dependency vulnerability scan with osv-scanner
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/osv-scanner
|
||||
with:
|
||||
lockfile: ui/pnpm-lock.yaml
|
||||
+11
-35
@@ -107,35 +107,21 @@ repos:
|
||||
files: { glob: ["{api,mcp_server}/**/*.py"] }
|
||||
priority: 20
|
||||
|
||||
## PYTHON — Poetry
|
||||
- repo: https://github.com/python-poetry/poetry
|
||||
rev: 2.3.4
|
||||
## PYTHON — uv (API + SDK)
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: 0.11.14
|
||||
hooks:
|
||||
- id: poetry-check
|
||||
name: API - poetry-check
|
||||
args: ["--directory=./api"]
|
||||
files: { glob: ["api/{pyproject.toml,poetry.lock}"] }
|
||||
- id: uv-lock
|
||||
name: API - uv-lock
|
||||
args: ["--check", "--project=./api"]
|
||||
files: { glob: ["api/{pyproject.toml,uv.lock}"] }
|
||||
pass_filenames: false
|
||||
priority: 50
|
||||
|
||||
- id: poetry-lock
|
||||
name: API - poetry-lock
|
||||
args: ["--directory=./api"]
|
||||
files: { glob: ["api/{pyproject.toml,poetry.lock}"] }
|
||||
pass_filenames: false
|
||||
priority: 50
|
||||
|
||||
- id: poetry-check
|
||||
name: SDK - poetry-check
|
||||
args: ["--directory=./"]
|
||||
files: { glob: ["{pyproject.toml,poetry.lock}"] }
|
||||
pass_filenames: false
|
||||
priority: 50
|
||||
|
||||
- id: poetry-lock
|
||||
name: SDK - poetry-lock
|
||||
args: ["--directory=./"]
|
||||
files: { glob: ["{pyproject.toml,poetry.lock}"] }
|
||||
- id: uv-lock
|
||||
name: SDK - uv-lock
|
||||
args: ["--check", "--project=./"]
|
||||
files: { glob: ["{pyproject.toml,uv.lock}"] }
|
||||
pass_filenames: false
|
||||
priority: 50
|
||||
|
||||
@@ -179,16 +165,6 @@ repos:
|
||||
exclude: { glob: ["{contrib,skills}/**", "**/.venv/**", "**/*_test.py"] }
|
||||
priority: 40
|
||||
|
||||
- id: safety
|
||||
name: safety
|
||||
description: "Safety is a tool that checks your installed dependencies for known security vulnerabilities"
|
||||
# Accepted CVEs, severity threshold, and ignore expirations live in .safety-policy.yml
|
||||
entry: safety check --policy-file .safety-policy.yml
|
||||
language: system
|
||||
pass_filenames: false
|
||||
files: { glob: ["**/pyproject.toml", "**/poetry.lock", "**/requirements*.txt", ".safety-policy.yml"] }
|
||||
priority: 40
|
||||
|
||||
- id: vulture
|
||||
name: vulture
|
||||
description: "Vulture finds unused code in Python programs."
|
||||
|
||||
+2
-6
@@ -11,15 +11,11 @@ build:
|
||||
python: "3.11"
|
||||
jobs:
|
||||
post_create_environment:
|
||||
# Install poetry
|
||||
# https://python-poetry.org/docs/#installing-manually
|
||||
- python -m pip install poetry==2.3.4
|
||||
- python -m pip install uv==0.11.14
|
||||
post_install:
|
||||
# Install dependencies with 'docs' dependency group
|
||||
# https://python-poetry.org/docs/managing-dependencies/#dependency-groups
|
||||
# VIRTUAL_ENV needs to be set manually for now.
|
||||
# See https://github.com/readthedocs/readthedocs.org/pull/11152/
|
||||
- VIRTUAL_ENV=${READTHEDOCS_VIRTUALENV_PATH} python -m poetry install --only=docs
|
||||
- VIRTUAL_ENV=${READTHEDOCS_VIRTUALENV_PATH} uv sync --group docs --no-install-project
|
||||
|
||||
mkdocs:
|
||||
configuration: mkdocs.yml
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# Safety policy for `safety check` (Safety CLI 3.x, v2 schema).
|
||||
# Applied in: .pre-commit-config.yaml, .github/workflows/api-security.yml,
|
||||
# .github/workflows/sdk-security.yml via `--policy-file`.
|
||||
#
|
||||
# Validate: poetry run safety validate policy_file --path .safety-policy.yml
|
||||
|
||||
security:
|
||||
# Scan unpinned requirements too. Prowler pins via poetry.lock, so this is
|
||||
# defensive against accidental unpinned entries.
|
||||
ignore-unpinned-requirements: False
|
||||
|
||||
# CVSS severity filter. 7 = report only HIGH (7.0–8.9) and CRITICAL (9.0–10.0).
|
||||
# Reference: 9=CRITICAL only, 7=CRITICAL+HIGH, 4=CRITICAL+HIGH+MEDIUM.
|
||||
ignore-cvss-severity-below: 7
|
||||
|
||||
# Unknown severity is unrated, not safe. Keep False so unrated CVEs still fail
|
||||
# the build and get a human eye. Flip to True only if noise is unmanageable.
|
||||
ignore-cvss-unknown-severity: False
|
||||
|
||||
# Fail the build when a non-ignored vulnerability is found.
|
||||
continue-on-vulnerability-error: False
|
||||
|
||||
# Explicit accepted vulnerabilities. Each entry MUST have a reason and an
|
||||
# expiry. Expired entries fail the scan, forcing re-audit.
|
||||
ignore-vulnerabilities:
|
||||
77744:
|
||||
reason: "Botocore requires urllib3 1.X. Remove once upgraded to urllib3 2.X."
|
||||
expires: '2026-10-22'
|
||||
77745:
|
||||
reason: "Botocore requires urllib3 1.X. Remove once upgraded to urllib3 2.X."
|
||||
expires: '2026-10-22'
|
||||
79023:
|
||||
reason: "knack ReDoS; blocked until azure-cli-core (via cartography) allows knack >=0.13.0."
|
||||
expires: '2026-10-22'
|
||||
79027:
|
||||
reason: "knack ReDoS; blocked until azure-cli-core (via cartography) allows knack >=0.13.0."
|
||||
expires: '2026-10-22'
|
||||
86217:
|
||||
reason: "alibabacloud-tea-openapi==0.4.3 blocks upgrade to cryptography >=46.0.0."
|
||||
expires: '2026-10-22'
|
||||
71600:
|
||||
reason: "CVE-2024-1135 false positive. Fixed in gunicorn 22.0.0; project uses 23.0.0."
|
||||
expires: '2026-10-22'
|
||||
70612:
|
||||
reason: "TBD - audit required. Reason not documented in prior --ignore list."
|
||||
expires: '2026-07-22'
|
||||
66963:
|
||||
reason: "TBD - audit required. Reason not documented in prior --ignore list."
|
||||
expires: '2026-07-22'
|
||||
74429:
|
||||
reason: "TBD - audit required. Reason not documented in prior --ignore list."
|
||||
expires: '2026-07-22'
|
||||
76352:
|
||||
reason: "TBD - audit required. Reason not documented in prior --ignore list."
|
||||
expires: '2026-07-22'
|
||||
76353:
|
||||
reason: "TBD - audit required. Reason not documented in prior --ignore list."
|
||||
expires: '2026-07-22'
|
||||
@@ -148,7 +148,7 @@ Prowler is an open-source cloud security assessment tool supporting AWS, Azure,
|
||||
|
||||
| Component | Location | Tech Stack |
|
||||
|-----------|----------|------------|
|
||||
| SDK | `prowler/` | Python 3.10+, Poetry 2.3+ |
|
||||
| SDK | `prowler/` | Python 3.10+, uv |
|
||||
| API | `api/` | Django 5.1, DRF, Celery |
|
||||
| UI | `ui/` | Next.js 16, React 19, Tailwind 4 |
|
||||
| MCP Server | `mcp_server/` | FastMCP, Python 3.12+ |
|
||||
@@ -160,13 +160,13 @@ Prowler is an open-source cloud security assessment tool supporting AWS, Azure,
|
||||
|
||||
```bash
|
||||
# Setup
|
||||
poetry install --with dev
|
||||
poetry run prek install
|
||||
uv sync
|
||||
uv run prek install
|
||||
|
||||
# Code quality
|
||||
poetry run make lint
|
||||
poetry run make format
|
||||
poetry run prek run --all-files
|
||||
uv run make lint
|
||||
uv run make format
|
||||
uv run prek run --all-files
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
+6
-6
@@ -78,7 +78,7 @@ WORKDIR /home/prowler
|
||||
# Copy necessary files
|
||||
COPY prowler/ /home/prowler/prowler/
|
||||
COPY dashboard/ /home/prowler/dashboard/
|
||||
COPY pyproject.toml /home/prowler
|
||||
COPY pyproject.toml uv.lock /home/prowler/
|
||||
COPY README.md /home/prowler/
|
||||
COPY prowler/providers/m365/lib/powershell/m365_powershell.py /home/prowler/prowler/providers/m365/lib/powershell/m365_powershell.py
|
||||
|
||||
@@ -87,17 +87,17 @@ ENV HOME='/home/prowler'
|
||||
ENV PATH="${HOME}/.local/bin:${PATH}"
|
||||
#hadolint ignore=DL3013
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir poetry==2.3.4
|
||||
pip install --no-cache-dir uv==0.11.14
|
||||
|
||||
RUN poetry install --compile && \
|
||||
rm -rf ~/.cache/pip
|
||||
RUN uv sync --compile-bytecode && \
|
||||
rm -rf ~/.cache/uv
|
||||
|
||||
# Install PowerShell modules
|
||||
RUN poetry run python prowler/providers/m365/lib/powershell/m365_powershell.py
|
||||
RUN .venv/bin/python prowler/providers/m365/lib/powershell/m365_powershell.py
|
||||
|
||||
# Remove deprecated dash dependencies
|
||||
RUN pip uninstall dash-html-components -y && \
|
||||
pip uninstall dash-core-components -y
|
||||
|
||||
USER prowler
|
||||
ENTRYPOINT ["poetry", "run", "prowler"]
|
||||
ENTRYPOINT [".venv/bin/prowler"]
|
||||
|
||||
@@ -23,7 +23,7 @@ format: ## Format Code
|
||||
|
||||
lint: ## Lint Code
|
||||
@echo "Running flake8..."
|
||||
flake8 . --ignore=E266,W503,E203,E501,W605,E128 --exclude contrib
|
||||
flake8 . --ignore=E266,W503,E203,E501,W605,E128 --exclude .venv,contrib
|
||||
@echo "Running black... "
|
||||
black --check .
|
||||
@echo "Running pylint..."
|
||||
@@ -35,7 +35,7 @@ pypi-clean: ## Delete the distribution files
|
||||
|
||||
pypi-build: ## Build package
|
||||
$(MAKE) pypi-clean && \
|
||||
poetry build
|
||||
uv build
|
||||
|
||||
pypi-upload: ## Upload package
|
||||
python3 -m twine upload --repository pypi dist/*
|
||||
@@ -56,4 +56,3 @@ run-api-dev: ## Start development environment with API, PostgreSQL, Valkey, MCP,
|
||||
|
||||
##@ Development Environment
|
||||
build-and-run-api-dev: build-no-cache-dev run-api-dev
|
||||
|
||||
|
||||
@@ -121,7 +121,6 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
|
||||
| OpenStack | 34 | 5 | 0 | 9 | Official | UI, API, CLI |
|
||||
| Vercel | 26 | 6 | 0 | 5 | Official | UI, API, CLI |
|
||||
| Okta | 1 | 1 | 0 | 1 | Official | CLI |
|
||||
| Scaleway [Contact us](https://prowler.com/contact) | 1 | 1 | 0 | 1 | Unofficial | CLI |
|
||||
| NHN | 6 | 2 | 1 | 0 | Unofficial | CLI |
|
||||
|
||||
> [!Note]
|
||||
@@ -178,7 +177,7 @@ You can find more information in the [Troubleshooting](./docs/troubleshooting.md
|
||||
**Requirements**
|
||||
|
||||
* `git` installed.
|
||||
* `poetry` v2 installed: [poetry installation](https://python-poetry.org/docs/#installation).
|
||||
* `uv` installed: [uv installation](https://docs.astral.sh/uv/getting-started/installation/).
|
||||
* `pnpm` installed: [pnpm installation](https://pnpm.io/installation).
|
||||
* `Docker Compose` installed: https://docs.docker.com/compose/install/.
|
||||
|
||||
@@ -187,8 +186,8 @@ You can find more information in the [Troubleshooting](./docs/troubleshooting.md
|
||||
``` console
|
||||
git clone https://github.com/prowler-cloud/prowler
|
||||
cd prowler/api
|
||||
poetry install
|
||||
eval $(poetry env activate)
|
||||
uv sync
|
||||
source .venv/bin/activate
|
||||
set -a
|
||||
source .env
|
||||
docker compose up postgres valkey -d
|
||||
@@ -196,11 +195,6 @@ cd src/backend
|
||||
python manage.py migrate --database admin
|
||||
gunicorn -c config/guniconf.py config.wsgi:application
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> As of Poetry v2.0.0, the `poetry shell` command has been deprecated. Use `poetry env activate` instead for environment activation.
|
||||
>
|
||||
> If your Poetry version is below v2.0.0, continue using `poetry shell` to activate your environment.
|
||||
> For further guidance, refer to the Poetry Environment Activation Guide https://python-poetry.org/docs/managing-environments/#activating-the-environment.
|
||||
|
||||
> After completing the setup, access the API documentation at http://localhost:8080/api/v1/docs.
|
||||
|
||||
@@ -209,8 +203,8 @@ gunicorn -c config/guniconf.py config.wsgi:application
|
||||
``` console
|
||||
git clone https://github.com/prowler-cloud/prowler
|
||||
cd prowler/api
|
||||
poetry install
|
||||
eval $(poetry env activate)
|
||||
uv sync
|
||||
source .venv/bin/activate
|
||||
set -a
|
||||
source .env
|
||||
cd src/backend
|
||||
@@ -222,8 +216,8 @@ python -m celery -A config.celery worker -l info -E
|
||||
``` console
|
||||
git clone https://github.com/prowler-cloud/prowler
|
||||
cd prowler/api
|
||||
poetry install
|
||||
eval $(poetry env activate)
|
||||
uv sync
|
||||
source .venv/bin/activate
|
||||
set -a
|
||||
source .env
|
||||
cd src/backend
|
||||
@@ -284,24 +278,18 @@ The container images are available here:
|
||||
|
||||
### From GitHub
|
||||
|
||||
Python >=3.10, <3.13 is required with pip and Poetry:
|
||||
Python >=3.10, <3.13 is required with [uv](https://docs.astral.sh/uv/):
|
||||
|
||||
``` console
|
||||
git clone https://github.com/prowler-cloud/prowler
|
||||
cd prowler
|
||||
eval $(poetry env activate)
|
||||
poetry install
|
||||
uv sync
|
||||
source .venv/bin/activate
|
||||
python prowler-cli.py -v
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> To clone Prowler on Windows, configure Git to support long file paths by running the following command: `git config core.longpaths true`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> As of Poetry v2.0.0, the `poetry shell` command has been deprecated. Use `poetry env activate` instead for environment activation.
|
||||
>
|
||||
> If your Poetry version is below v2.0.0, continue using `poetry shell` to activate your environment.
|
||||
> For further guidance, refer to the Poetry Environment Activation Guide https://python-poetry.org/docs/managing-environments/#activating-the-environment.
|
||||
|
||||
# 🛡️ GitHub Action
|
||||
|
||||
The official **Prowler GitHub Action** runs Prowler scans in your GitHub workflows using the official [`prowlercloud/prowler`](https://hub.docker.com/r/prowlercloud/prowler) Docker image. Scans run on any [supported provider](https://docs.prowler.com/user-guide/providers/), with optional [`--push-to-cloud`](https://docs.prowler.com/user-guide/tutorials/prowler-app-import-findings) to send findings to Prowler Cloud and optional SARIF upload so findings show up in the repo's **Security → Code scanning** tab and as inline PR annotations.
|
||||
|
||||
+1
-1
@@ -167,7 +167,7 @@ runs:
|
||||
|
||||
- name: Upload SARIF to GitHub Code Scanning
|
||||
if: always() && inputs.upload-sarif == 'true' && steps.find-sarif.outputs.sarif_path != ''
|
||||
uses: github/codeql-action/upload-sarif@d4b3ca9fa7f69d38bfcd667bdc45bc373d16277e # v4
|
||||
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
sarif_file: ${{ steps.find-sarif.outputs.sarif_path }}
|
||||
category: ${{ inputs.sarif-category }}
|
||||
|
||||
+8
-8
@@ -124,24 +124,24 @@ api/src/backend/
|
||||
|
||||
```bash
|
||||
# Development
|
||||
poetry run python src/backend/manage.py runserver
|
||||
poetry run celery -A config.celery worker -l INFO
|
||||
uv run python src/backend/manage.py runserver
|
||||
uv run celery -A config.celery worker -l INFO
|
||||
|
||||
# Database
|
||||
poetry run python src/backend/manage.py makemigrations
|
||||
poetry run python src/backend/manage.py migrate
|
||||
uv run python src/backend/manage.py makemigrations
|
||||
uv run python src/backend/manage.py migrate
|
||||
|
||||
# Testing & Linting
|
||||
poetry run pytest -x --tb=short
|
||||
poetry run make lint
|
||||
uv run pytest -x --tb=short
|
||||
uv run make lint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## QA CHECKLIST
|
||||
|
||||
- [ ] `poetry run pytest` passes
|
||||
- [ ] `poetry run make lint` passes
|
||||
- [ ] `uv run pytest` passes
|
||||
- [ ] `uv run make lint` passes
|
||||
- [ ] Migrations created if models changed
|
||||
- [ ] New endpoints have `@extend_schema` decorators
|
||||
- [ ] RLS properly applied for tenant data
|
||||
|
||||
@@ -10,6 +10,7 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
- Replace `poetry` with `uv` (`0.11.14`) as the API package manager; migrate `pyproject.toml` to `[dependency-groups]` and regenerate as `uv.lock` [(#10775)](https://github.com/prowler-cloud/prowler/pull/10775)
|
||||
- Remove orphaned `gin_resources_search_idx` declaration from `Resource.Meta.indexes` (DB index dropped in `0072_drop_unused_indexes`) [(#11001)](https://github.com/prowler-cloud/prowler/pull/11001)
|
||||
|
||||
---
|
||||
|
||||
+7
-6
@@ -14,6 +14,7 @@ ENV ZIZMOR_VERSION=${ZIZMOR_VERSION}
|
||||
# hadolint ignore=DL3008
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
wget \
|
||||
git \
|
||||
libicu72 \
|
||||
gcc \
|
||||
g++ \
|
||||
@@ -88,18 +89,18 @@ WORKDIR /home/prowler
|
||||
# Ensure output directory exists
|
||||
RUN mkdir -p /tmp/prowler_api_output
|
||||
|
||||
COPY pyproject.toml ./
|
||||
COPY pyproject.toml uv.lock ./
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir poetry==2.3.4
|
||||
pip install --no-cache-dir uv==0.11.14
|
||||
|
||||
ENV PATH="/home/prowler/.local/bin:$PATH"
|
||||
|
||||
# Add `--no-root` to avoid installing the current project as a package
|
||||
RUN poetry install --no-root && \
|
||||
rm -rf ~/.cache/pip
|
||||
# Add `--no-install-project` to avoid installing the current project as a package
|
||||
RUN uv sync --no-install-project && \
|
||||
rm -rf ~/.cache/uv
|
||||
|
||||
RUN poetry run python "$(poetry env info --path)/src/prowler/prowler/providers/m365/lib/powershell/m365_powershell.py"
|
||||
RUN .venv/bin/python .venv/lib/python3.12/site-packages/prowler/providers/m365/lib/powershell/m365_powershell.py
|
||||
|
||||
COPY src/backend/ ./backend/
|
||||
COPY docker-entrypoint.sh ./docker-entrypoint.sh
|
||||
|
||||
+12
-19
@@ -25,12 +25,11 @@ If you don’t set `DJANGO_TOKEN_SIGNING_KEY` or `DJANGO_TOKEN_VERIFYING_KEY`, t
|
||||
**Important note**: Every Prowler version (or repository branches and tags) could have different variables set in its `.env` file. Please use the `.env` file that corresponds with each version.
|
||||
|
||||
## Local deployment
|
||||
Keep in mind if you export the `.env` file to use it with local deployment that you will have to do it within the context of the Poetry interpreter, not before. Otherwise, variables will not be loaded properly.
|
||||
Keep in mind if you export the `.env` file to use it with local deployment that you will have to do it within the context of the virtual environment, not before. Otherwise, variables will not be loaded properly.
|
||||
|
||||
To do this, you can run:
|
||||
|
||||
```console
|
||||
poetry shell
|
||||
set -a
|
||||
source .env
|
||||
```
|
||||
@@ -78,7 +77,7 @@ docker logs -f $(docker ps --format "{{.Names}}" | grep 'api-')
|
||||
|
||||
## Local deployment
|
||||
|
||||
To use this method, you'll need to set up a Python virtual environment (version ">=3.11,<3.13") and keep dependencies updated. Additionally, ensure that `poetry` and `docker compose` are installed.
|
||||
To use this method, you'll need to set up a Python virtual environment (version ">=3.11,<3.13") and keep dependencies updated. Additionally, ensure that `uv` and `docker compose` are installed.
|
||||
|
||||
### Clone the repository
|
||||
|
||||
@@ -90,11 +89,10 @@ git clone https://github.com/prowler-cloud/api.git
|
||||
git clone git@github.com:prowler-cloud/api.git
|
||||
|
||||
```
|
||||
### Install all dependencies with Poetry
|
||||
### Install all dependencies with uv
|
||||
|
||||
```console
|
||||
poetry install
|
||||
poetry shell
|
||||
uv sync
|
||||
```
|
||||
|
||||
## Start the PostgreSQL Database and Valkey
|
||||
@@ -139,7 +137,7 @@ gunicorn -c config/guniconf.py config.wsgi:application
|
||||
|
||||
## Local deployment
|
||||
|
||||
To use this method, you'll need to set up a Python virtual environment (version ">=3.11,<3.13") and keep dependencies updated. Additionally, ensure that `poetry` and `docker compose` are installed.
|
||||
To use this method, you'll need to set up a Python virtual environment (version ">=3.11,<3.13") and keep dependencies updated. Additionally, ensure that `uv` and `docker compose` are installed.
|
||||
|
||||
### Clone the repository
|
||||
|
||||
@@ -165,11 +163,10 @@ docker compose up postgres valkey -d
|
||||
|
||||
### Install the Python dependencies
|
||||
|
||||
> You must have Poetry installed
|
||||
> You must have uv installed
|
||||
|
||||
```console
|
||||
poetry install
|
||||
poetry shell
|
||||
uv sync
|
||||
```
|
||||
|
||||
### Apply migrations
|
||||
@@ -246,9 +243,8 @@ docker logs -f $(docker ps --format "{{.Names}}" | grep 'api-')
|
||||
For migrations, you need to force the `admin` database router. Assuming you have the correct environment variables and Python virtual environment, run:
|
||||
|
||||
```console
|
||||
poetry shell
|
||||
cd src/backend
|
||||
python manage.py migrate --database admin
|
||||
uv run python manage.py migrate --database admin
|
||||
```
|
||||
|
||||
## Apply fixtures
|
||||
@@ -256,9 +252,8 @@ python manage.py migrate --database admin
|
||||
Fixtures are used to populate the database with initial development data.
|
||||
|
||||
```console
|
||||
poetry shell
|
||||
cd src/backend
|
||||
python manage.py loaddata api/fixtures/0_dev_users.json --database admin
|
||||
uv run python manage.py loaddata api/fixtures/0_dev_users.json --database admin
|
||||
```
|
||||
|
||||
> The default credentials are `dev@prowler.com:Thisisapassword123@` or `dev2@prowler.com:Thisisapassword123@`
|
||||
@@ -270,9 +265,8 @@ Note that the tests will fail if you use the same `.env` file as the development
|
||||
For best results, run in a new shell with no environment variables set.
|
||||
|
||||
```console
|
||||
poetry shell
|
||||
cd src/backend
|
||||
pytest
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
# Custom commands
|
||||
@@ -284,8 +278,7 @@ Django provides a way to create custom commands that can be run from the command
|
||||
To run a custom command, you need to be in the `prowler/api/src/backend` directory and run:
|
||||
|
||||
```console
|
||||
poetry shell
|
||||
python manage.py <command_name>
|
||||
uv run python manage.py <command_name>
|
||||
```
|
||||
|
||||
## Generate dummy data
|
||||
@@ -308,7 +301,7 @@ This command creates, for a given tenant, a provider, scan and a set of findings
|
||||
### Example
|
||||
|
||||
```console
|
||||
~/backend $ poetry run python manage.py findings --tenant
|
||||
~/backend $ uv run python manage.py findings --tenant
|
||||
fffb1893-3fc7-4623-a5d9-fae47da1c528 --findings 25000 --re
|
||||
sources 1000 --batch 5000 --alias test-script
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ apply_migrations() {
|
||||
echo "Applying database migrations..."
|
||||
|
||||
# Fix Inconsistent migration history after adding sites app
|
||||
poetry run python manage.py check_and_fix_socialaccount_sites_migration --database admin
|
||||
uv run python manage.py check_and_fix_socialaccount_sites_migration --database admin
|
||||
|
||||
poetry run python manage.py migrate --database admin
|
||||
uv run python manage.py migrate --database admin
|
||||
}
|
||||
|
||||
apply_fixtures() {
|
||||
@@ -15,19 +15,19 @@ apply_fixtures() {
|
||||
for fixture in api/fixtures/dev/*.json; do
|
||||
if [ -f "$fixture" ]; then
|
||||
echo "Loading $fixture"
|
||||
poetry run python manage.py loaddata "$fixture" --database admin
|
||||
uv run python manage.py loaddata "$fixture" --database admin
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
start_dev_server() {
|
||||
echo "Starting the development server..."
|
||||
poetry run python manage.py runserver 0.0.0.0:"${DJANGO_PORT:-8080}"
|
||||
uv run python manage.py runserver 0.0.0.0:"${DJANGO_PORT:-8080}"
|
||||
}
|
||||
|
||||
start_prod_server() {
|
||||
echo "Starting the Gunicorn server..."
|
||||
poetry run gunicorn -c config/guniconf.py config.wsgi:application
|
||||
uv run gunicorn -c config/guniconf.py config.wsgi:application
|
||||
}
|
||||
|
||||
resolve_worker_hostname() {
|
||||
@@ -47,7 +47,7 @@ resolve_worker_hostname() {
|
||||
|
||||
start_worker() {
|
||||
echo "Starting the worker..."
|
||||
poetry run python -m celery -A config.celery worker \
|
||||
uv run python -m celery -A config.celery worker \
|
||||
-n "$(resolve_worker_hostname)" \
|
||||
-l "${DJANGO_LOGGING_LEVEL:-info}" \
|
||||
-Q celery,scans,scan-reports,deletion,backfill,overview,integrations,compliance,attack-paths-scans \
|
||||
@@ -56,7 +56,7 @@ start_worker() {
|
||||
|
||||
start_worker_beat() {
|
||||
echo "Starting the worker-beat..."
|
||||
poetry run python -m celery -A config.celery beat -l "${DJANGO_LOGGING_LEVEL:-info}" --scheduler django_celery_beat.schedulers:DatabaseScheduler
|
||||
uv run python -m celery -A config.celery beat -l "${DJANGO_LOGGING_LEVEL:-info}" --scheduler django_celery_beat.schedulers:DatabaseScheduler
|
||||
}
|
||||
|
||||
manage_db_partitions() {
|
||||
@@ -64,7 +64,7 @@ manage_db_partitions() {
|
||||
echo "Managing DB partitions..."
|
||||
# For now we skip the deletion of partitions until we define the data retention policy
|
||||
# --yes auto approves the operation without the need of an interactive terminal
|
||||
poetry run python manage.py pgpartition --using admin --skip-delete --yes
|
||||
uv run python manage.py pgpartition --using admin --skip-delete --yes
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
Generated
-9427
File diff suppressed because it is too large
Load Diff
+392
-26
@@ -1,6 +1,24 @@
|
||||
[build-system]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
requires = ["poetry-core"]
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"bandit==1.7.9",
|
||||
"coverage==7.5.4",
|
||||
"django-silk==5.3.2",
|
||||
"docker==7.1.0",
|
||||
"filelock==3.20.3",
|
||||
"freezegun==1.5.1",
|
||||
"mypy==1.10.1",
|
||||
"pylint==3.2.5",
|
||||
"pytest==9.0.3",
|
||||
"pytest-cov==5.0.0",
|
||||
"pytest-django==4.8.0",
|
||||
"pytest-env==1.1.3",
|
||||
"pytest-randomly==3.15.0",
|
||||
"pytest-xdist==3.6.1",
|
||||
"ruff==0.5.0",
|
||||
"tqdm==4.67.1",
|
||||
"vulture==2.14",
|
||||
"prek==0.3.9"
|
||||
]
|
||||
|
||||
[project]
|
||||
authors = [{name = "Prowler Engineering", email = "engineering@prowler.com"}]
|
||||
@@ -52,26 +70,374 @@ package-mode = false
|
||||
requires-python = ">=3.11,<3.13"
|
||||
version = "1.28.0"
|
||||
|
||||
[project.scripts]
|
||||
celery = "src.backend.config.settings.celery"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
bandit = "1.7.9"
|
||||
coverage = "7.5.4"
|
||||
django-silk = "5.3.2"
|
||||
docker = "7.1.0"
|
||||
filelock = "3.20.3"
|
||||
freezegun = "1.5.1"
|
||||
mypy = "1.10.1"
|
||||
prek = "0.3.9"
|
||||
pylint = "3.2.5"
|
||||
pytest = "9.0.3"
|
||||
pytest-cov = "5.0.0"
|
||||
pytest-django = "4.8.0"
|
||||
pytest-env = "1.1.3"
|
||||
pytest-randomly = "3.15.0"
|
||||
pytest-xdist = "3.6.1"
|
||||
ruff = "0.5.0"
|
||||
safety = "3.7.0"
|
||||
tqdm = "4.67.1"
|
||||
vulture = "2.14"
|
||||
[tool.uv]
|
||||
# Transitive pins matching master to avoid silent drift; bump deliberately.
|
||||
constraint-dependencies = [
|
||||
"about-time==4.2.1",
|
||||
"adal==1.2.7",
|
||||
"aioboto3==15.5.0",
|
||||
"aiobotocore==2.25.1",
|
||||
"aiofiles==24.1.0",
|
||||
"aiohappyeyeballs==2.6.1",
|
||||
"aiohttp==3.13.5",
|
||||
"aioitertools==0.13.0",
|
||||
"aiosignal==1.4.0",
|
||||
"alibabacloud-actiontrail20200706==2.4.1",
|
||||
"alibabacloud-credentials==1.0.3",
|
||||
"alibabacloud-credentials-api==1.0.0",
|
||||
"alibabacloud-cs20151215==6.1.0",
|
||||
"alibabacloud-darabonba-array==0.1.0",
|
||||
"alibabacloud-darabonba-encode-util==0.0.2",
|
||||
"alibabacloud-darabonba-map==0.0.1",
|
||||
"alibabacloud-darabonba-signature-util==0.0.4",
|
||||
"alibabacloud-darabonba-string==0.0.4",
|
||||
"alibabacloud-darabonba-time==0.0.1",
|
||||
"alibabacloud-ecs20140526==7.2.5",
|
||||
"alibabacloud-endpoint-util==0.0.4",
|
||||
"alibabacloud-gateway-oss==0.0.17",
|
||||
"alibabacloud-gateway-oss-util==0.0.3",
|
||||
"alibabacloud-gateway-sls==0.4.0",
|
||||
"alibabacloud-gateway-sls-util==0.4.0",
|
||||
"alibabacloud-gateway-spi==0.0.3",
|
||||
"alibabacloud-openapi-util==0.2.4",
|
||||
"alibabacloud-oss-util==0.0.6",
|
||||
"alibabacloud-oss20190517==1.0.6",
|
||||
"alibabacloud-ram20150501==1.2.0",
|
||||
"alibabacloud-rds20140815==12.0.0",
|
||||
"alibabacloud-sas20181203==6.1.0",
|
||||
"alibabacloud-sls20201230==5.9.0",
|
||||
"alibabacloud-sts20150401==1.1.6",
|
||||
"alibabacloud-tea==0.4.3",
|
||||
"alibabacloud-tea-openapi==0.4.4",
|
||||
"alibabacloud-tea-util==0.3.14",
|
||||
"alibabacloud-tea-xml==0.0.3",
|
||||
"alibabacloud-vpc20160428==6.13.0",
|
||||
"alive-progress==3.3.0",
|
||||
"aliyun-log-fastpb==0.2.0",
|
||||
"amqp==5.3.1",
|
||||
"annotated-types==0.7.0",
|
||||
"anyio==4.12.1",
|
||||
"applicationinsights==0.11.10",
|
||||
"apscheduler==3.11.2",
|
||||
"argcomplete==3.5.3",
|
||||
"asgiref==3.11.0",
|
||||
"astroid==3.2.4",
|
||||
"async-timeout==5.0.1",
|
||||
"attrs==25.4.0",
|
||||
"authlib==1.6.9",
|
||||
"autopep8==2.3.2",
|
||||
"awsipranges==0.3.3",
|
||||
"azure-cli-core==2.83.0",
|
||||
"azure-cli-telemetry==1.1.0",
|
||||
"azure-common==1.1.28",
|
||||
"azure-core==1.38.1",
|
||||
"azure-identity==1.21.0",
|
||||
"azure-keyvault-certificates==4.10.0",
|
||||
"azure-keyvault-keys==4.10.0",
|
||||
"azure-keyvault-secrets==4.10.0",
|
||||
"azure-mgmt-apimanagement==5.0.0",
|
||||
"azure-mgmt-applicationinsights==4.1.0",
|
||||
"azure-mgmt-authorization==4.0.0",
|
||||
"azure-mgmt-compute==34.0.0",
|
||||
"azure-mgmt-containerinstance==10.1.0",
|
||||
"azure-mgmt-containerregistry==12.0.0",
|
||||
"azure-mgmt-containerservice==34.1.0",
|
||||
"azure-mgmt-core==1.6.0",
|
||||
"azure-mgmt-cosmosdb==9.7.0",
|
||||
"azure-mgmt-databricks==2.0.0",
|
||||
"azure-mgmt-datafactory==9.2.0",
|
||||
"azure-mgmt-eventgrid==10.4.0",
|
||||
"azure-mgmt-eventhub==11.2.0",
|
||||
"azure-mgmt-keyvault==10.3.1",
|
||||
"azure-mgmt-loganalytics==12.0.0",
|
||||
"azure-mgmt-logic==10.0.0",
|
||||
"azure-mgmt-monitor==6.0.2",
|
||||
"azure-mgmt-network==28.1.0",
|
||||
"azure-mgmt-postgresqlflexibleservers==1.1.0",
|
||||
"azure-mgmt-rdbms==10.1.0",
|
||||
"azure-mgmt-recoveryservices==3.1.0",
|
||||
"azure-mgmt-recoveryservicesbackup==9.2.0",
|
||||
"azure-mgmt-resource==24.0.0",
|
||||
"azure-mgmt-search==9.1.0",
|
||||
"azure-mgmt-security==7.0.0",
|
||||
"azure-mgmt-sql==3.0.1",
|
||||
"azure-mgmt-storage==22.1.1",
|
||||
"azure-mgmt-subscription==3.1.1",
|
||||
"azure-mgmt-synapse==2.0.0",
|
||||
"azure-mgmt-web==8.0.0",
|
||||
"azure-monitor-query==2.0.0",
|
||||
"azure-storage-blob==12.24.1",
|
||||
"azure-synapse-artifacts==0.21.0",
|
||||
"backoff==2.2.1",
|
||||
"bandit==1.7.9",
|
||||
"billiard==4.2.4",
|
||||
"blinker==1.9.0",
|
||||
"boto3==1.40.61",
|
||||
"botocore==1.40.61",
|
||||
"cartography==0.135.0",
|
||||
"celery==5.6.2",
|
||||
"certifi==2026.1.4",
|
||||
"cffi==2.0.0",
|
||||
"charset-normalizer==3.4.4",
|
||||
"circuitbreaker==2.1.3",
|
||||
"click==8.3.1",
|
||||
"click-didyoumean==0.3.1",
|
||||
"click-plugins==1.1.1.2",
|
||||
"click-repl==0.3.0",
|
||||
"cloudflare==4.3.1",
|
||||
"colorama==0.4.6",
|
||||
"contextlib2==21.6.0",
|
||||
"contourpy==1.3.3",
|
||||
"coverage==7.5.4",
|
||||
"cron-descriptor==1.4.5",
|
||||
"crowdstrike-falconpy==1.6.0",
|
||||
"cryptography==46.0.7",
|
||||
"cycler==0.12.1",
|
||||
"darabonba-core==1.0.5",
|
||||
"dash==3.1.1",
|
||||
"dash-bootstrap-components==2.0.3",
|
||||
"debugpy==1.8.20",
|
||||
"decorator==5.2.1",
|
||||
"defusedxml==0.7.1",
|
||||
"detect-secrets==1.5.0",
|
||||
"dill==0.4.1",
|
||||
"distro==1.9.0",
|
||||
"dj-rest-auth==7.0.1",
|
||||
"django==5.1.15",
|
||||
"django-allauth==65.15.0",
|
||||
"django-celery-beat==2.9.0",
|
||||
"django-celery-results==2.6.0",
|
||||
"django-cors-headers==4.4.0",
|
||||
"django-environ==0.11.2",
|
||||
"django-filter==24.3",
|
||||
"django-guid==3.5.0",
|
||||
"django-postgres-extra==2.0.9",
|
||||
"django-silk==5.3.2",
|
||||
"django-timezone-field==7.2.1",
|
||||
"djangorestframework==3.15.2",
|
||||
"djangorestframework-jsonapi==7.0.2",
|
||||
"djangorestframework-simplejwt==5.5.1",
|
||||
"dnspython==2.8.0",
|
||||
"docker==7.1.0",
|
||||
"dogpile-cache==1.5.0",
|
||||
"dparse==0.6.4",
|
||||
"drf-extensions==0.8.0",
|
||||
"drf-nested-routers==0.95.0",
|
||||
"drf-simple-apikey==2.2.1",
|
||||
"drf-spectacular==0.27.2",
|
||||
"drf-spectacular-jsonapi==0.5.1",
|
||||
"dulwich==0.23.0",
|
||||
"duo-client==5.5.0",
|
||||
"durationpy==0.10",
|
||||
"email-validator==2.2.0",
|
||||
"execnet==2.1.2",
|
||||
"filelock==3.20.3",
|
||||
"flask==3.1.3",
|
||||
"fonttools==4.62.1",
|
||||
"freezegun==1.5.1",
|
||||
"frozenlist==1.8.0",
|
||||
"gevent==25.9.1",
|
||||
"google-api-core==2.29.0",
|
||||
"google-api-python-client==2.163.0",
|
||||
"google-auth==2.48.0",
|
||||
"google-auth-httplib2==0.2.0",
|
||||
"google-cloud-access-context-manager==0.3.0",
|
||||
"google-cloud-asset==4.2.0",
|
||||
"google-cloud-org-policy==1.16.0",
|
||||
"google-cloud-os-config==1.23.0",
|
||||
"google-cloud-resource-manager==1.16.0",
|
||||
"googleapis-common-protos==1.72.0",
|
||||
"gprof2dot==2025.4.14",
|
||||
"graphemeu==0.7.2",
|
||||
"greenlet==3.3.1",
|
||||
"grpc-google-iam-v1==0.14.3",
|
||||
"grpcio==1.76.0",
|
||||
"grpcio-status==1.76.0",
|
||||
"gunicorn==23.0.0",
|
||||
"h11==0.16.0",
|
||||
"h2==4.3.0",
|
||||
"hpack==4.1.0",
|
||||
"httpcore==1.0.9",
|
||||
"httplib2==0.31.2",
|
||||
"httpx==0.28.1",
|
||||
"humanfriendly==10.0",
|
||||
"hyperframe==6.1.0",
|
||||
"iamdata==0.1.202602021",
|
||||
"idna==3.11",
|
||||
"importlib-metadata==8.7.1",
|
||||
"inflection==0.5.1",
|
||||
"iniconfig==2.3.0",
|
||||
"iso8601==2.1.0",
|
||||
"isodate==0.7.2",
|
||||
"isort==5.13.2",
|
||||
"itsdangerous==2.2.0",
|
||||
"jinja2==3.1.6",
|
||||
"jiter==0.13.0",
|
||||
"jmespath==1.1.0",
|
||||
"joblib==1.5.3",
|
||||
"jsonpatch==1.33",
|
||||
"jsonpickle==4.1.1",
|
||||
"jsonpointer==3.0.0",
|
||||
"jsonschema==4.23.0",
|
||||
"jsonschema-specifications==2025.9.1",
|
||||
"keystoneauth1==5.13.0",
|
||||
"kiwisolver==1.4.9",
|
||||
"knack==0.11.0",
|
||||
"kombu==5.6.2",
|
||||
"kubernetes==32.0.1",
|
||||
"lxml==5.3.2",
|
||||
"lz4==4.4.5",
|
||||
"markdown==3.10.2",
|
||||
"markdown-it-py==4.0.0",
|
||||
"markupsafe==3.0.3",
|
||||
"marshmallow==4.3.0",
|
||||
"matplotlib==3.10.8",
|
||||
"mccabe==0.7.0",
|
||||
"mdurl==0.1.2",
|
||||
"microsoft-kiota-abstractions==1.9.2",
|
||||
"microsoft-kiota-authentication-azure==1.9.2",
|
||||
"microsoft-kiota-http==1.9.2",
|
||||
"microsoft-kiota-serialization-form==1.9.2",
|
||||
"microsoft-kiota-serialization-json==1.9.2",
|
||||
"microsoft-kiota-serialization-multipart==1.9.2",
|
||||
"microsoft-kiota-serialization-text==1.9.2",
|
||||
"microsoft-security-utilities-secret-masker==1.0.0b4",
|
||||
"msal==1.35.0b1",
|
||||
"msal-extensions==1.2.0",
|
||||
"msgraph-core==1.3.8",
|
||||
"msgraph-sdk==1.55.0",
|
||||
"msrest==0.7.1",
|
||||
"msrestazure==0.6.4.post1",
|
||||
"multidict==6.7.1",
|
||||
"mypy==1.10.1",
|
||||
"mypy-extensions==1.1.0",
|
||||
"narwhals==2.16.0",
|
||||
"neo4j==6.1.0",
|
||||
"nest-asyncio==1.6.0",
|
||||
"nltk==3.9.4",
|
||||
"numpy==2.0.2",
|
||||
"oauthlib==3.3.1",
|
||||
"oci==2.169.0",
|
||||
"openai==1.109.1",
|
||||
"openstacksdk==4.2.0",
|
||||
"opentelemetry-api==1.39.1",
|
||||
"opentelemetry-sdk==1.39.1",
|
||||
"opentelemetry-semantic-conventions==0.60b1",
|
||||
"os-service-types==1.8.2",
|
||||
"packageurl-python==0.17.6",
|
||||
"packaging==26.0",
|
||||
"pagerduty==6.1.0",
|
||||
"pandas==2.2.3",
|
||||
"pbr==7.0.3",
|
||||
"pillow==12.2.0",
|
||||
"pkginfo==1.12.1.2",
|
||||
"platformdirs==4.5.1",
|
||||
"plotly==6.5.2",
|
||||
"pluggy==1.6.0",
|
||||
"policyuniverse==1.5.1.20231109",
|
||||
"portalocker==2.10.1",
|
||||
"prek==0.3.9",
|
||||
"prompt-toolkit==3.0.52",
|
||||
"propcache==0.4.1",
|
||||
"proto-plus==1.27.0",
|
||||
"protobuf==6.33.5",
|
||||
"psutil==7.2.2",
|
||||
"psycopg2-binary==2.9.9",
|
||||
"py-deviceid==0.1.1",
|
||||
"py-iam-expand==0.1.0",
|
||||
"py-ocsf-models==0.8.1",
|
||||
"pyasn1==0.6.3",
|
||||
"pyasn1-modules==0.4.2",
|
||||
"pycodestyle==2.14.0",
|
||||
"pycparser==3.0",
|
||||
"pydantic==2.12.5",
|
||||
"pydantic-core==2.41.5",
|
||||
"pygithub==2.8.0",
|
||||
"pygments==2.20.0",
|
||||
"pyjwt==2.12.1",
|
||||
"pylint==3.2.5",
|
||||
"pymsalruntime==0.18.1",
|
||||
"pynacl==1.6.2",
|
||||
"pyopenssl==26.0.0",
|
||||
"pyparsing==3.3.2",
|
||||
"pyreadline3==3.5.4",
|
||||
"pysocks==1.7.1",
|
||||
"pytest==9.0.3",
|
||||
"pytest-celery==1.3.0",
|
||||
"pytest-cov==5.0.0",
|
||||
"pytest-django==4.8.0",
|
||||
"pytest-docker-tools==3.1.9",
|
||||
"pytest-env==1.1.3",
|
||||
"pytest-randomly==3.15.0",
|
||||
"pytest-xdist==3.6.1",
|
||||
"python-crontab==3.3.0",
|
||||
"python-dateutil==2.9.0.post0",
|
||||
"python-digitalocean==1.17.0",
|
||||
"python3-saml==1.16.0",
|
||||
"pytz==2025.1",
|
||||
"pywin32==311",
|
||||
"pyyaml==6.0.3",
|
||||
"redis==7.1.0",
|
||||
"referencing==0.37.0",
|
||||
"regex==2026.1.15",
|
||||
"reportlab==4.4.10",
|
||||
"requests==2.33.1",
|
||||
"requests-file==3.0.1",
|
||||
"requests-oauthlib==2.0.0",
|
||||
"requestsexceptions==1.4.0",
|
||||
"retrying==1.4.2",
|
||||
"rich==14.3.2",
|
||||
"rpds-py==0.30.0",
|
||||
"rsa==4.9.1",
|
||||
"ruamel-yaml==0.19.1",
|
||||
"ruff==0.5.0",
|
||||
"s3transfer==0.14.0",
|
||||
"scaleway==2.10.3",
|
||||
"scaleway-core==2.10.3",
|
||||
"schema==0.7.5",
|
||||
"sentry-sdk==2.56.0",
|
||||
"setuptools==80.10.2",
|
||||
"shellingham==1.5.4",
|
||||
"shodan==1.31.0",
|
||||
"six==1.17.0",
|
||||
"slack-sdk==3.39.0",
|
||||
"sniffio==1.3.1",
|
||||
"sqlparse==0.5.5",
|
||||
"statsd==4.0.1",
|
||||
"std-uritemplate==2.0.8",
|
||||
"stevedore==5.6.0",
|
||||
"tabulate==0.9.0",
|
||||
"tenacity==9.1.2",
|
||||
"tldextract==5.3.1",
|
||||
"tomlkit==0.14.0",
|
||||
"tqdm==4.67.1",
|
||||
"typer==0.21.1",
|
||||
"types-aiobotocore-ecr==3.1.1",
|
||||
"typing-extensions==4.15.0",
|
||||
"typing-inspection==0.4.2",
|
||||
"tzdata==2025.3",
|
||||
"tzlocal==5.3.1",
|
||||
"uritemplate==4.2.0",
|
||||
"urllib3==2.6.3",
|
||||
"uuid6==2024.7.10",
|
||||
"vine==5.1.0",
|
||||
"vulture==2.14",
|
||||
"wcwidth==0.5.3",
|
||||
"websocket-client==1.9.0",
|
||||
"werkzeug==3.1.7",
|
||||
"workos==6.0.4",
|
||||
"wrapt==1.17.3",
|
||||
"xlsxwriter==3.2.9",
|
||||
"xmlsec==1.3.14",
|
||||
"xmltodict==1.0.2",
|
||||
"yarl==1.22.0",
|
||||
"zipp==3.23.0",
|
||||
"zope-event==6.1",
|
||||
"zope-interface==8.2",
|
||||
"zstd==1.5.7.3"
|
||||
]
|
||||
# prowler@master needs okta==3.4.2; cartography 0.135.0 declares okta<1.0.0 for an
|
||||
# integration prowler does not import.
|
||||
override-dependencies = [
|
||||
"okta==3.4.2"
|
||||
]
|
||||
|
||||
Generated
+5983
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,9 @@
|
||||
#!/bin/bash
|
||||
# Run Prowler against All AWS Accounts in an AWS Organization
|
||||
|
||||
# Activate Poetry Environment
|
||||
eval "$(poetry env activate)"
|
||||
# Activate uv-managed virtualenv
|
||||
# shellcheck disable=SC1091
|
||||
source .venv/bin/activate
|
||||
|
||||
# Show Prowler Version
|
||||
prowler -v
|
||||
|
||||
@@ -73,7 +73,7 @@ secrets:
|
||||
DJANGO_SECRETS_ENCRYPTION_KEY:
|
||||
DJANGO_BROKER_VISIBILITY_TIMEOUT: 86400
|
||||
|
||||
releaseConfigRoot: /home/prowler/.cache/pypoetry/virtualenvs/prowler-api-NnJNioq7-py3.12/lib/python3.12/site-packages/
|
||||
releaseConfigRoot: /home/prowler/.venv/lib/python3.12/site-packages/
|
||||
releaseConfigPath: prowler/config/config.yaml
|
||||
|
||||
mainConfig:
|
||||
|
||||
+7
-1
@@ -48,10 +48,16 @@ services:
|
||||
- path: .env
|
||||
required: false
|
||||
ports:
|
||||
- ${UI_PORT:-3000}:${UI_PORT:-3000}
|
||||
- ${UI_PORT:-3000}:3000
|
||||
depends_on:
|
||||
mcp-server:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:3000/api/health || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
postgres:
|
||||
image: postgres:16.3-alpine3.20
|
||||
|
||||
+1
-1
@@ -467,7 +467,7 @@ Effective headers and section titles enhance document readability and structure,
|
||||
|
||||
* **Example:**
|
||||
* How to Clone and Install Prowler from GitHub (header: Title case)
|
||||
* How to install poetry dependencies (subheading: Sentence case)
|
||||
* How to install uv dependencies (subheading: Sentence case)
|
||||
5. **Using Keywords in Headers**
|
||||
Headers should include relevant keywords to improve document searchability:
|
||||
* **Good:** Scanning AWS Accounts in Parallel
|
||||
|
||||
@@ -20,8 +20,8 @@ The most common high level steps to create a new check are:
|
||||
3. Create a check-specific folder. The path should follow this pattern: `prowler/providers/<provider>/services/<service>/<check_name_want_to_implement>`. Adhere to the [Naming Format for Checks](#naming-format-for-checks).
|
||||
4. Populate the folder with files as specified in [File Creation](#file-creation).
|
||||
5. Run the check locally to ensure it works as expected. For checking you can use the CLI in the next way:
|
||||
- To ensure the check has been detected by Prowler: `poetry run python prowler-cli.py <provider> --list-checks | grep <check_name>`.
|
||||
- To run the check, to find possible issues: `poetry run python prowler-cli.py <provider> --log-level ERROR --verbose --check <check_name>`.
|
||||
- To ensure the check has been detected by Prowler: `uv run python prowler-cli.py <provider> --list-checks | grep <check_name>`.
|
||||
- To run the check, to find possible issues: `uv run python prowler-cli.py <provider> --log-level ERROR --verbose --check <check_name>`.
|
||||
6. Create comprehensive tests for the check that cover multiple scenarios including both PASS (compliant) and FAIL (non-compliant) cases. For detailed information about test structure and implementation guidelines, refer to the [Testing](/developer-guide/unit-testing) documentation.
|
||||
7. If the check and its corresponding tests are working as expected, you can submit a PR to Prowler.
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ Before proceeding, ensure the following:
|
||||
|
||||
- Git is installed.
|
||||
- Python 3.10 or higher is installed.
|
||||
- `poetry` is installed to manage dependencies.
|
||||
- `uv` is installed to manage dependencies.
|
||||
|
||||
### Forking the Prowler Repository
|
||||
|
||||
@@ -97,28 +97,21 @@ cd prowler
|
||||
|
||||
### Dependency Management and Environment Isolation
|
||||
|
||||
To prevent conflicts between environments, we recommend using `poetry`, a Python dependency management solution. Install it by following the [instructions](https://python-poetry.org/docs/#installation).
|
||||
To prevent conflicts between environments, we recommend using [`uv`](https://docs.astral.sh/uv/), a fast Python package and project manager. Install it by following the [official instructions](https://docs.astral.sh/uv/getting-started/installation/).
|
||||
|
||||
### Installing Dependencies
|
||||
|
||||
To install all required dependencies, including those needed for development, run:
|
||||
|
||||
```
|
||||
poetry install --with dev
|
||||
eval $(poetry env activate)
|
||||
uv sync
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Starting from Poetry v2.0.0, `poetry shell` has been deprecated in favor of `poetry env activate`.
|
||||
If your poetry version is below 2.0.0 you must keep using `poetry shell` to activate your environment.
|
||||
In case you have any doubts, consult the [Poetry environment activation guide](https://python-poetry.org/docs/managing-environments/#activating-the-environment).
|
||||
|
||||
</Warning>
|
||||
|
||||
|
||||
### Pre-Commit Hooks
|
||||
|
||||
This repository uses Git pre-commit hooks managed by the [prek](https://prek.j178.dev/) tool, it is installed with `poetry install --with dev`. Next, run the following command in the root of this repository:
|
||||
This repository uses Git pre-commit hooks managed by the [prek](https://prek.j178.dev/) tool, it is installed with `uv sync`. Next, run the following command in the root of this repository:
|
||||
|
||||
```shell
|
||||
prek install
|
||||
@@ -155,7 +148,7 @@ Once installed, TruffleHog runs before each push and blocks the operation when v
|
||||
Before merging pull requests, several automated checks and utilities ensure code security and updated dependencies:
|
||||
|
||||
<Note>
|
||||
These should have been already installed if `poetry install --with dev` was already run.
|
||||
These should have been already installed if `uv sync` was already run.
|
||||
|
||||
</Note>
|
||||
- [`bandit`](https://pypi.org/project/bandit/) for code security review.
|
||||
@@ -183,7 +176,7 @@ These resources help ensure that AI-assisted contributions maintain consistency
|
||||
|
||||
All dependencies are listed in the `pyproject.toml` file.
|
||||
|
||||
The SDK keeps direct dependencies pinned to exact versions, while `poetry.lock` records the full resolved dependency tree and the artifact hashes for every package. Use `poetry install` from the lock file instead of ad-hoc `pip` installs when you need a reproducible environment.
|
||||
The SDK keeps direct dependencies pinned to exact versions, while `uv.lock` records the full resolved dependency tree and the artifact hashes for every package. Use `uv sync` from the lock file instead of ad-hoc `pip` installs when you need a reproducible environment.
|
||||
|
||||
For proper code documentation, refer to the following and follow the code documentation practices presented there: [Google Python Style Guide - Comments and Docstrings](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings).
|
||||
|
||||
@@ -209,8 +202,8 @@ prowler/
|
||||
├── contrib/ # Community-contributed scripts or modules
|
||||
├── kubernetes/ # Kubernetes deployment files
|
||||
├── .github/ # GitHub-related files (workflows, issue templates, etc.)
|
||||
├── pyproject.toml # Python project configuration (Poetry)
|
||||
├── poetry.lock # Poetry lock file
|
||||
├── pyproject.toml # Python project configuration (uv)
|
||||
├── uv.lock # uv lock file
|
||||
├── README.md # Project overview and getting started
|
||||
├── Makefile # Common development commands
|
||||
├── Dockerfile # SDK Docker container
|
||||
|
||||
@@ -1277,10 +1277,12 @@ Dependencies ensure that your provider's required libraries are available when P
|
||||
**File:** `pyproject.toml`
|
||||
|
||||
```toml
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.10,<3.13"
|
||||
# ... other dependencies
|
||||
your-sdk-library = "^1.0.0" # Add your SDK dependency
|
||||
[project]
|
||||
requires-python = ">=3.10,<3.13"
|
||||
dependencies = [
|
||||
# ... other dependencies
|
||||
"your-sdk-library>=1.0.0,<2.0.0", # Add your SDK dependency
|
||||
]
|
||||
```
|
||||
|
||||
#### Step 18: Create Tests
|
||||
|
||||
@@ -228,7 +228,7 @@ Each requirement links to the Prowler checks that, together, produce a PASS or F
|
||||
To discover available checks, run:
|
||||
|
||||
```bash
|
||||
poetry run python prowler-cli.py <provider> --list-checks
|
||||
uv run python prowler-cli.py <provider> --list-checks
|
||||
```
|
||||
|
||||
## Supporting Multiple Providers
|
||||
@@ -295,7 +295,7 @@ Follow the steps below before opening a pull request.
|
||||
### 1. Run the Compliance Model Validator
|
||||
|
||||
```bash
|
||||
poetry run python prowler-cli.py <provider> --list-compliance
|
||||
uv run python prowler-cli.py <provider> --list-compliance
|
||||
```
|
||||
|
||||
The framework must appear in the output. A validation error indicates a schema mismatch between the JSON file and the attribute model.
|
||||
@@ -303,7 +303,7 @@ The framework must appear in the output. A validation error indicates a schema m
|
||||
### 2. Run a Scan Filtered by the Framework
|
||||
|
||||
```bash
|
||||
poetry run python prowler-cli.py <provider> \
|
||||
uv run python prowler-cli.py <provider> \
|
||||
--compliance <framework>_<version>_<provider> \
|
||||
--log-level ERROR
|
||||
```
|
||||
@@ -336,7 +336,7 @@ Compliance contributions require two layers of tests.
|
||||
Run the suite with:
|
||||
|
||||
```bash
|
||||
poetry run pytest -n auto tests/lib/check/universal_compliance_models_test.py \
|
||||
uv run pytest -n auto tests/lib/check/universal_compliance_models_test.py \
|
||||
tests/lib/outputs/compliance/
|
||||
```
|
||||
|
||||
@@ -348,8 +348,8 @@ Before opening the pull request:
|
||||
|
||||
1. Run the complete QA pipeline:
|
||||
```bash
|
||||
poetry run pre-commit run --all-files
|
||||
poetry run pytest -n auto
|
||||
uv run pre-commit run --all-files
|
||||
uv run pytest -n auto
|
||||
```
|
||||
2. Add a changelog entry under the `### 🚀 Added` section of `prowler/CHANGELOG.md`, describing the new framework and the providers it covers.
|
||||
3. Follow the [Pull Request Template](https://github.com/prowler-cloud/prowler/blob/master/.github/pull_request_template.md) and set the PR title using Conventional Commits, for example `feat(compliance): add My Framework 1.0 for AWS`.
|
||||
|
||||
@@ -23,7 +23,7 @@ Within this folder the following files are also to be created:
|
||||
- `<new_service_name>_service.py` – Contains all the logic and API calls of the service.
|
||||
- `<new_service_name>_client_.py` – Contains the initialization of the freshly created service's class so that the checks can use it.
|
||||
|
||||
Once the files are create, you can check that the service has been created by running the following command: `poetry run python prowler-cli.py <provider> --list-services | grep <new_service_name>`.
|
||||
Once the files are create, you can check that the service has been created by running the following command: `uv run python prowler-cli.py <provider> --list-services | grep <new_service_name>`.
|
||||
|
||||
## Service Structure and Initialisation
|
||||
|
||||
|
||||
@@ -326,12 +326,6 @@
|
||||
"user-guide/providers/openstack/authentication"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Scaleway",
|
||||
"pages": [
|
||||
"user-guide/providers/scaleway/getting-started-scaleway"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Vercel",
|
||||
"pages": [
|
||||
|
||||
@@ -37,7 +37,7 @@ Refer to the [Prowler App Tutorial](/user-guide/tutorials/prowler-app) for detai
|
||||
_Requirements_:
|
||||
|
||||
- `git` installed.
|
||||
- `poetry` installed: [poetry installation](https://python-poetry.org/docs/#installation).
|
||||
- `uv` installed: [uv installation](https://docs.astral.sh/uv/getting-started/installation/).
|
||||
- `pnpm` installed through [Corepack](https://pnpm.io/installation#using-corepack) or the standalone [pnpm installation](https://pnpm.io/installation).
|
||||
- `Docker Compose` installed: https://docs.docker.com/compose/install/.
|
||||
|
||||
@@ -49,8 +49,8 @@ Refer to the [Prowler App Tutorial](/user-guide/tutorials/prowler-app) for detai
|
||||
```bash
|
||||
git clone https://github.com/prowler-cloud/prowler \
|
||||
cd prowler/api \
|
||||
poetry install \
|
||||
eval $(poetry env activate) \
|
||||
uv sync \
|
||||
source .venv/bin/activate \
|
||||
set -a \
|
||||
source .env \
|
||||
docker compose up postgres valkey -d \
|
||||
@@ -59,11 +59,6 @@ Refer to the [Prowler App Tutorial](/user-guide/tutorials/prowler-app) for detai
|
||||
gunicorn -c config/guniconf.py config.wsgi:application
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Starting from Poetry v2.0.0, `poetry shell` has been deprecated in favor of `poetry env activate`.
|
||||
|
||||
If your poetry version is below 2.0.0 you must keep using `poetry shell` to activate your environment. In case you have any doubts, consult the Poetry environment activation guide: https://python-poetry.org/docs/managing-environments/#activating-the-environment
|
||||
</Warning>
|
||||
> Now, you can access the API documentation at http://localhost:8080/api/v1/docs.
|
||||
|
||||
_Commands to run the API Worker_:
|
||||
@@ -71,8 +66,8 @@ Refer to the [Prowler App Tutorial](/user-guide/tutorials/prowler-app) for detai
|
||||
```bash
|
||||
git clone https://github.com/prowler-cloud/prowler \
|
||||
cd prowler/api \
|
||||
poetry install \
|
||||
eval $(poetry env activate) \
|
||||
uv sync \
|
||||
source .venv/bin/activate \
|
||||
set -a \
|
||||
source .env \
|
||||
cd src/backend \
|
||||
@@ -84,8 +79,8 @@ Refer to the [Prowler App Tutorial](/user-guide/tutorials/prowler-app) for detai
|
||||
```bash
|
||||
git clone https://github.com/prowler-cloud/prowler \
|
||||
cd prowler/api \
|
||||
poetry install \
|
||||
eval $(poetry env activate) \
|
||||
uv sync \
|
||||
source .venv/bin/activate \
|
||||
set -a \
|
||||
source .env \
|
||||
cd src/backend \
|
||||
|
||||
@@ -68,7 +68,7 @@ To install Prowler as a Python package, use `Python >= 3.10, <= 3.12`. Prowler i
|
||||
_Requirements for Developers_:
|
||||
|
||||
* `git`
|
||||
* `poetry` installed: [poetry installation](https://python-poetry.org/docs/#installation).
|
||||
* `uv` installed: [uv installation](https://docs.astral.sh/uv/getting-started/installation/).
|
||||
* AWS, GCP, Azure and/or Kubernetes credentials
|
||||
|
||||
_Commands_:
|
||||
@@ -76,8 +76,8 @@ To install Prowler as a Python package, use `Python >= 3.10, <= 3.12`. Prowler i
|
||||
```bash
|
||||
git clone https://github.com/prowler-cloud/prowler
|
||||
cd prowler
|
||||
poetry install
|
||||
poetry run python prowler-cli.py -v
|
||||
uv sync
|
||||
uv run python prowler-cli.py -v
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
||||
@@ -35,7 +35,6 @@ Prowler supports a wide range of providers organized by category:
|
||||
| **NHN** | Unofficial | Tenants | CLI |
|
||||
| [OpenStack](/user-guide/providers/openstack/getting-started-openstack) | Official | Projects | UI, API, CLI |
|
||||
| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | Tenancies / Compartments | UI, API, CLI |
|
||||
| [Scaleway](/user-guide/providers/scaleway/getting-started-scaleway) [Contact us](https://prowler.com/contact) | Unofficial | Organizations | CLI |
|
||||
|
||||
### Infrastructure as Code Providers
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ To download results from AWS CloudShell:
|
||||
|
||||
## Cloning Prowler from GitHub
|
||||
|
||||
Due to the limited storage in AWS CloudShell's home directory, installing Poetry dependencies for running Prowler from GitHub can be problematic.
|
||||
Due to the limited storage in AWS CloudShell's home directory, installing uv dependencies for running Prowler from GitHub can be problematic.
|
||||
|
||||
The following workaround ensures successful installation:
|
||||
|
||||
@@ -37,17 +37,9 @@ adduser prowler
|
||||
su prowler
|
||||
git clone https://github.com/prowler-cloud/prowler.git
|
||||
cd prowler
|
||||
pip install poetry
|
||||
mkdir /tmp/poetry
|
||||
poetry config cache-dir /tmp/poetry
|
||||
eval $(poetry env activate)
|
||||
poetry install
|
||||
pip install uv
|
||||
mkdir /tmp/uv-cache
|
||||
UV_CACHE_DIR=/tmp/uv-cache uv sync
|
||||
source .venv/bin/activate
|
||||
python prowler-cli.py -v
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Starting from Poetry v2.0.0, `poetry shell` has been deprecated in favor of `poetry env activate`.
|
||||
|
||||
If your Poetry version is below v2.0.0, continue using `poetry shell` to activate your environment. For further guidance, refer to the Poetry Environment Activation Guide https://python-poetry.org/docs/managing-environments/#activating-the-environment.
|
||||
|
||||
</Warning>
|
||||
|
||||
@@ -44,6 +44,15 @@ User API Tokens are the recommended authentication method because they:
|
||||
Create a **User API Token**, not an Account API Token. User API Tokens are created from the profile settings and offer finer permission control.
|
||||
</Note>
|
||||
|
||||
**Quick Setup:** Use these pre-configured links to open the Cloudflare Dashboard with the required permissions already selected:
|
||||
|
||||
- [Create User API Token](https://dash.cloudflare.com/profile/api-tokens?permissionGroupKeys=%5B%7B%22key%22%3A%22account_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22dns%22%2C%22type%22%3A%22read%22%7D%5D&accountId=%2A&zoneId=all&name=Prowler%20Security%20Scanner) — creates a **User API Token** (recommended). Opens the **Create Custom Token** form prefilled with the four required read-only scopes (`Account Settings`, `Zone`, `Zone Settings`, `DNS`) and the name `Prowler Security Scanner`. Adjust **Account Resources** and **Zone Resources** to match the accounts and zones you want to scan, then click **Create Token**.
|
||||
- [Create Account-Owned API Token](https://dash.cloudflare.com/?to=/:account/api-tokens&permissionGroupKeys=%5B%7B%22key%22%3A%22account_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22dns%22%2C%22type%22%3A%22read%22%7D%5D&name=Prowler%20Security%20Scanner) — creates an [account-owned token](https://developers.cloudflare.com/fundamentals/api/how-to/account-owned-token-template/) instead. Use this for automation or CI/CD where the token should not depend on a specific user account remaining active. Requires the **Super Administrator** or **Administrator** role on the account.
|
||||
|
||||
<Note>
|
||||
Template URLs only pre-fill the token creation form. Review the permissions, configure resources, and click **Create Token** to complete the process.
|
||||
</Note>
|
||||
|
||||
### Step 1: Create a User API Token
|
||||
|
||||
1. Log into the [Cloudflare Dashboard](https://dash.cloudflare.com).
|
||||
|
||||
@@ -14,6 +14,15 @@ Set up authentication for Cloudflare with the [Cloudflare Authentication](/user-
|
||||
- Grant the required read-only permissions (`Account Settings:Read`, `Zone:Read`, `Zone Settings:Read`, `DNS:Read`)
|
||||
- Identify the Cloudflare Account ID to use as the provider identifier
|
||||
|
||||
<Note>
|
||||
**Quick Setup:** Use these pre-configured links to create a token with the required permissions already selected:
|
||||
|
||||
- [Create User API Token](https://dash.cloudflare.com/profile/api-tokens?permissionGroupKeys=%5B%7B%22key%22%3A%22account_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22dns%22%2C%22type%22%3A%22read%22%7D%5D&accountId=%2A&zoneId=all&name=Prowler%20Security%20Scanner) — creates a User API Token (recommended).
|
||||
- [Create Account-Owned API Token](https://dash.cloudflare.com/?to=/:account/api-tokens&permissionGroupKeys=%5B%7B%22key%22%3A%22account_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22dns%22%2C%22type%22%3A%22read%22%7D%5D&name=Prowler%20Security%20Scanner) — creates an [account-owned token](https://developers.cloudflare.com/fundamentals/api/how-to/account-owned-token-template/), better suited for automation and CI/CD.
|
||||
|
||||
Both links open the Cloudflare Dashboard with the four required read-only scopes (`Account Settings`, `Zone`, `Zone Settings`, `DNS`) and the name `Prowler Security Scanner` prefilled. See [Cloudflare Authentication](/user-guide/providers/cloudflare/authentication#api-token-recommended) for the equivalent manual steps.
|
||||
</Note>
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Prowler Cloud" icon="cloud" href="#prowler-cloud">
|
||||
Onboard Cloudflare using Prowler Cloud
|
||||
|
||||
@@ -153,8 +153,8 @@ Before running Prowler CLI for GitHub, ensure you have:
|
||||
# Install via pip
|
||||
pip install prowler
|
||||
|
||||
# Or via poetry
|
||||
poetry install
|
||||
# Or via uv (from the cloned repo)
|
||||
uv sync
|
||||
```
|
||||
|
||||
2. **Authentication Credentials**
|
||||
|
||||
@@ -46,7 +46,7 @@ Before you begin, ensure you have:
|
||||
```bash
|
||||
pip install prowler
|
||||
# or for development:
|
||||
poetry install
|
||||
uv sync
|
||||
```
|
||||
|
||||
2. **OCI Python SDK** (automatically installed with Prowler):
|
||||
|
||||
@@ -133,7 +133,7 @@ export OKTA_PRIVATE_KEY="$(cat /secure/path/to/prowler-okta.pem)"
|
||||
# Optional — defaults to "okta.policies.read"
|
||||
export OKTA_SCOPES="okta.policies.read"
|
||||
|
||||
poetry run python prowler-cli.py okta
|
||||
uv run python prowler-cli.py okta
|
||||
```
|
||||
|
||||
### Non-Secret CLI Flags
|
||||
@@ -149,7 +149,7 @@ Non-secret values are also available as CLI flags for ergonomic overrides:
|
||||
Run a single check directly:
|
||||
|
||||
```bash
|
||||
poetry run python prowler-cli.py okta --check signon_global_session_idle_timeout_15min
|
||||
uv run python prowler-cli.py okta --check signon_global_session_idle_timeout_15min
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
title: "Getting Started With Scaleway on Prowler"
|
||||
---
|
||||
|
||||
Prowler for Scaleway scans IAM resources in your Scaleway organization for security misconfigurations. The current release ships one check that flags API keys still owned by the account root user.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. A Scaleway organization with IAM access.
|
||||
2. A Scaleway API key with at least the `IAMReadOnly` policy bound to a dedicated IAM user (do not use the account root user).
|
||||
3. Your organization ID (visible at the top right of the Scaleway console).
|
||||
|
||||
## Authentication
|
||||
|
||||
Prowler reads credentials from the standard Scaleway environment variables:
|
||||
|
||||
| Variable | Purpose |
|
||||
|---|---|
|
||||
| `SCW_ACCESS_KEY` | API key access key |
|
||||
| `SCW_SECRET_KEY` | API key secret key |
|
||||
| `SCW_DEFAULT_ORGANIZATION_ID` | Optional, required when the key bearer is an application |
|
||||
| `SCW_DEFAULT_PROJECT_ID` | Optional, default project for project-scoped resources |
|
||||
| `SCW_DEFAULT_REGION` | Optional, defaults to `fr-par` |
|
||||
|
||||
Alternatively, pass them as CLI flags (`--access-key`, `--secret-key`, `--organization-id`, `--project-id`, `--region`). The CLI emits a warning when secrets are passed via the command line; environment variables are preferred.
|
||||
|
||||
## Run a scan
|
||||
|
||||
```bash
|
||||
export SCW_ACCESS_KEY="SCW..."
|
||||
export SCW_SECRET_KEY="..."
|
||||
export SCW_DEFAULT_ORGANIZATION_ID="..."
|
||||
|
||||
prowler scaleway
|
||||
```
|
||||
|
||||
To run only the IAM root-key check:
|
||||
|
||||
```bash
|
||||
prowler scaleway --check iam_no_root_api_keys
|
||||
```
|
||||
|
||||
## Checks shipped
|
||||
|
||||
| Check ID | Severity | Description |
|
||||
|---|---|---|
|
||||
| `iam_no_root_api_keys` | Critical | Fails when any Scaleway IAM API key is still owned by the account root user. |
|
||||
|
||||
## Required Scaleway permissions
|
||||
|
||||
The API key bearer needs read access to the IAM API in order to list users and API keys. The `IAMReadOnly` policy is sufficient. Refer to the [Scaleway IAM policy reference](https://www.scaleway.com/en/docs/identity-and-access-management/iam/reference-content/permission-sets/) for the full list of permissions.
|
||||
@@ -76,7 +76,9 @@ class SimplifiedFindingGroup(MinimalSerializerMixin):
|
||||
)
|
||||
muted_count: int = Field(description="Total muted findings in this group", ge=0)
|
||||
new_count: int = Field(description="Number of new non-muted findings", ge=0)
|
||||
changed_count: int = Field(description="Number of changed non-muted findings", ge=0)
|
||||
changed_count: int = Field(
|
||||
description="Number of changed non-muted findings", ge=0
|
||||
)
|
||||
first_seen_at: str | None = Field(
|
||||
default=None, description="First time this group was detected"
|
||||
)
|
||||
@@ -107,12 +109,18 @@ class DetailedFindingGroup(SimplifiedFindingGroup):
|
||||
new_pass_count: int = Field(description="New non-muted PASS findings", ge=0)
|
||||
new_pass_muted_count: int = Field(description="New muted PASS findings", ge=0)
|
||||
new_manual_count: int = Field(description="New non-muted MANUAL findings", ge=0)
|
||||
new_manual_muted_count: int = Field(description="New muted MANUAL findings", ge=0)
|
||||
changed_fail_count: int = Field(description="Changed non-muted FAIL findings", ge=0)
|
||||
new_manual_muted_count: int = Field(
|
||||
description="New muted MANUAL findings", ge=0
|
||||
)
|
||||
changed_fail_count: int = Field(
|
||||
description="Changed non-muted FAIL findings", ge=0
|
||||
)
|
||||
changed_fail_muted_count: int = Field(
|
||||
description="Changed muted FAIL findings", ge=0
|
||||
)
|
||||
changed_pass_count: int = Field(description="Changed non-muted PASS findings", ge=0)
|
||||
changed_pass_count: int = Field(
|
||||
description="Changed non-muted PASS findings", ge=0
|
||||
)
|
||||
changed_pass_muted_count: int = Field(
|
||||
description="Changed muted PASS findings", ge=0
|
||||
)
|
||||
|
||||
@@ -464,7 +464,9 @@ class FindingGroupsTools(BaseTool):
|
||||
|
||||
clean_params = self.api_client.build_filter_params(params)
|
||||
api_response = await self.api_client.get(endpoint, params=clean_params)
|
||||
response = FindingGroupResourcesListResponse.from_api_response(api_response)
|
||||
response = FindingGroupResourcesListResponse.from_api_response(
|
||||
api_response
|
||||
)
|
||||
return response.model_dump()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error listing finding group resources: {e}")
|
||||
|
||||
Generated
-6920
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -85,7 +85,7 @@ class {check_name}(Check):
|
||||
|
||||
## TECH STACK
|
||||
|
||||
Python 3.10+ | Poetry 2.3+ | pytest | moto (AWS mocking) | Pre-commit hooks (black, flake8, pylint, bandit)
|
||||
Python 3.10+ | uv | pytest | moto (AWS mocking) | Pre-commit hooks (black, flake8, pylint, bandit)
|
||||
|
||||
---
|
||||
|
||||
@@ -112,20 +112,20 @@ prowler/
|
||||
|
||||
```bash
|
||||
# Setup
|
||||
poetry install --with dev
|
||||
poetry run pre-commit install
|
||||
uv sync
|
||||
uv run pre-commit install
|
||||
|
||||
# Run Prowler
|
||||
poetry run python prowler-cli.py {provider}
|
||||
poetry run python prowler-cli.py {provider} --check {check_name}
|
||||
poetry run python prowler-cli.py {provider} --list-checks
|
||||
uv run python prowler-cli.py {provider}
|
||||
uv run python prowler-cli.py {provider} --check {check_name}
|
||||
uv run python prowler-cli.py {provider} --list-checks
|
||||
|
||||
# Testing
|
||||
poetry run pytest -n auto -vvv tests/
|
||||
poetry run pytest tests/providers/{provider}/services/{service}/ -v
|
||||
uv run pytest -n auto -vvv tests/
|
||||
uv run pytest tests/providers/{provider}/services/{service}/ -v
|
||||
|
||||
# Code Quality
|
||||
poetry run pre-commit run --all-files
|
||||
uv run pre-commit run --all-files
|
||||
```
|
||||
|
||||
---
|
||||
@@ -145,8 +145,8 @@ poetry run pre-commit run --all-files
|
||||
|
||||
## QA CHECKLIST
|
||||
|
||||
- [ ] `poetry run pytest` passes
|
||||
- [ ] `poetry run pre-commit run --all-files` passes
|
||||
- [ ] `uv run pytest` passes
|
||||
- [ ] `uv run pre-commit run --all-files` passes
|
||||
- [ ] Check metadata JSON is valid
|
||||
- [ ] Tests cover PASS, FAIL, and empty resource scenarios
|
||||
- [ ] Docstrings follow Google style
|
||||
|
||||
@@ -10,13 +10,20 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `iam_user_access_not_stale_to_sagemaker` check for AWS provider with configurable `max_unused_sagemaker_access_days` (default 90) [(#11000)](https://github.com/prowler-cloud/prowler/pull/11000)
|
||||
- `cloudtrail_bedrock_logging_enabled` check for AWS provider [(#10858)](https://github.com/prowler-cloud/prowler/pull/10858)
|
||||
- Okta provider with OAuth 2.0 authentication and `signon_global_session_idle_timeout_15min` check [(#11079)](https://github.com/prowler-cloud/prowler/pull/11079)
|
||||
- Scaleway provider with `iam_no_root_api_keys` check [(#11166)](https://github.com/prowler-cloud/prowler/pull/11166)
|
||||
- `sagemaker_domain_sso_configured` check for AWS provider [(#11094)](https://github.com/prowler-cloud/prowler/pull/11094)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
- `entra_emergency_access_exclusion` check for M365 provider now scopes the exclusion requirement to enabled Conditional Access policies with a `Block` grant control instead of every enabled policy, focusing on the lockout-relevant policy set [(#10849)](https://github.com/prowler-cloud/prowler/pull/10849)
|
||||
- AWS IAM customer-managed policy checks no longer emit `FAIL` on unattached policies unless `--scan-unused-services` is enabled [(#11150)](https://github.com/prowler-cloud/prowler/pull/11150)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Google Workspace Directory checks sharing a single resource row, causing the service field to be overwritten by the last check executed [(#11176)](https://github.com/prowler-cloud/prowler/pull/11176)
|
||||
- Google Workspace Calendar and Drive services sharing a single resource row, causing the service field to be overwritten by the last check executed [(#11161)](https://github.com/prowler-cloud/prowler/pull/11161)
|
||||
- `zone_waf_enabled` check for Cloudflare provider now appends a plan-aware hint to the FAIL `status_extended`: a possible-false-positive note on paid plans (Pro, Business, Enterprise) where the legacy `waf` zone setting can read `off` even though WAF managed rulesets are deployed via the dashboard, and a "not available on the Cloudflare Free plan" note on Free zones [(#9896)](https://github.com/prowler-cloud/prowler/pull/9896)
|
||||
- Google Workspace Gmail checks sharing a single resource row, causing the service field to be overwritten by the last check executed [(#11169)](https://github.com/prowler-cloud/prowler/pull/11169)
|
||||
|
||||
---
|
||||
|
||||
## [5.26.2] (Prowler UNRELEASED)
|
||||
|
||||
@@ -157,7 +157,6 @@ from prowler.providers.nhn.models import NHNOutputOptions
|
||||
from prowler.providers.okta.models import OktaOutputOptions
|
||||
from prowler.providers.openstack.models import OpenStackOutputOptions
|
||||
from prowler.providers.oraclecloud.models import OCIOutputOptions
|
||||
from prowler.providers.scaleway.models import ScalewayOutputOptions
|
||||
from prowler.providers.vercel.models import VercelOutputOptions
|
||||
|
||||
|
||||
@@ -432,10 +431,6 @@ def prowler():
|
||||
output_options = OktaOutputOptions(
|
||||
args, bulk_checks_metadata, global_provider.identity
|
||||
)
|
||||
elif provider == "scaleway":
|
||||
output_options = ScalewayOutputOptions(
|
||||
args, bulk_checks_metadata, global_provider.identity
|
||||
)
|
||||
|
||||
# Run the quick inventory for the provider if available
|
||||
if hasattr(args, "quick_inventory") and args.quick_inventory:
|
||||
|
||||
@@ -75,7 +75,6 @@ class Provider(str, Enum):
|
||||
ALIBABACLOUD = "alibabacloud"
|
||||
OPENSTACK = "openstack"
|
||||
IMAGE = "image"
|
||||
SCALEWAY = "scaleway"
|
||||
VERCEL = "vercel"
|
||||
OKTA = "okta"
|
||||
|
||||
|
||||
@@ -741,10 +741,6 @@ def execute(
|
||||
is_finding_muted_args["team_id"] = (
|
||||
team.id if team else global_provider.identity.user_id
|
||||
)
|
||||
elif global_provider.type == "scaleway":
|
||||
is_finding_muted_args["organization_id"] = (
|
||||
global_provider.identity.organization_id
|
||||
)
|
||||
elif global_provider.type == "oraclecloud":
|
||||
is_finding_muted_args["tenancy_id"] = (
|
||||
global_provider.identity.tenancy_id
|
||||
|
||||
@@ -1318,53 +1318,6 @@ class CheckReportVercel(Check_Report):
|
||||
return "global"
|
||||
|
||||
|
||||
class CheckReportScaleway(Check_Report):
|
||||
"""Contains the Scaleway Check's finding information.
|
||||
|
||||
Scaleway scans run at the organization level. Most IAM/account-level
|
||||
resources are global; regional resources expose a ``region`` attribute
|
||||
on the underlying object, which we surface as the report ``region``.
|
||||
"""
|
||||
|
||||
resource_name: str
|
||||
resource_id: str
|
||||
organization_id: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
metadata: Dict,
|
||||
resource: Any,
|
||||
resource_name: str = None,
|
||||
resource_id: str = None,
|
||||
organization_id: str = None,
|
||||
) -> None:
|
||||
"""Initialize the Scaleway Check's finding information.
|
||||
|
||||
Args:
|
||||
metadata: Check metadata dictionary.
|
||||
resource: The Scaleway resource being checked.
|
||||
resource_name: Override for resource name.
|
||||
resource_id: Override for resource ID.
|
||||
organization_id: Override for the organization ID.
|
||||
"""
|
||||
super().__init__(metadata, resource)
|
||||
self.resource_name = resource_name or getattr(
|
||||
resource, "name", getattr(resource, "resource_name", "")
|
||||
)
|
||||
self.resource_id = resource_id or getattr(
|
||||
resource, "id", getattr(resource, "resource_id", "")
|
||||
)
|
||||
self.organization_id = organization_id or getattr(
|
||||
resource, "organization_id", ""
|
||||
)
|
||||
self._region = getattr(resource, "region", None) or "global"
|
||||
|
||||
@property
|
||||
def region(self) -> str:
|
||||
"""Scaleway regional resources expose their own region; IAM is global."""
|
||||
return self._region
|
||||
|
||||
|
||||
# Testing Pending
|
||||
def load_check_metadata(metadata_file: str) -> CheckMetadata:
|
||||
"""
|
||||
|
||||
@@ -29,10 +29,10 @@ class ProwlerArgumentParser:
|
||||
self.parser = argparse.ArgumentParser(
|
||||
prog="prowler",
|
||||
formatter_class=RawTextHelpFormatter,
|
||||
usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,scaleway,vercel,dashboard,iac,image,llm} ...",
|
||||
usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,vercel,dashboard,iac,image,llm} ...",
|
||||
epilog="""
|
||||
Available Cloud Providers:
|
||||
{aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,iac,llm,image,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,scaleway,vercel}
|
||||
{aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,iac,llm,image,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,vercel}
|
||||
aws AWS Provider
|
||||
azure Azure Provider
|
||||
gcp GCP Provider
|
||||
@@ -50,7 +50,6 @@ Available Cloud Providers:
|
||||
image Container Image Provider
|
||||
nhn NHN Provider (Unofficial)
|
||||
mongodbatlas MongoDB Atlas Provider
|
||||
scaleway Scaleway Provider
|
||||
vercel Vercel Provider
|
||||
|
||||
Available components:
|
||||
|
||||
@@ -442,18 +442,6 @@ class Finding(BaseModel):
|
||||
output_data["resource_uid"] = check_output.resource_id
|
||||
output_data["region"] = "global"
|
||||
|
||||
elif provider.type == "scaleway":
|
||||
output_data["auth_method"] = "api_key"
|
||||
output_data["account_uid"] = get_nested_attribute(
|
||||
provider, "identity.organization_id"
|
||||
)
|
||||
output_data["account_name"] = get_nested_attribute(
|
||||
provider, "identity.bearer_email"
|
||||
) or get_nested_attribute(provider, "identity.organization_id")
|
||||
output_data["resource_name"] = check_output.resource_name
|
||||
output_data["resource_uid"] = check_output.resource_id
|
||||
output_data["region"] = check_output.region
|
||||
|
||||
elif provider.type == "alibabacloud":
|
||||
output_data["auth_method"] = get_nested_attribute(
|
||||
provider, "identity.identity_arn"
|
||||
|
||||
@@ -1450,77 +1450,6 @@ class HTML(Output):
|
||||
)
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def get_scaleway_assessment_summary(provider: Provider) -> str:
|
||||
"""
|
||||
get_scaleway_assessment_summary gets the HTML assessment summary for the Scaleway provider
|
||||
|
||||
Args:
|
||||
provider (Provider): the Scaleway provider object
|
||||
|
||||
Returns:
|
||||
str: HTML assessment summary for the Scaleway provider
|
||||
"""
|
||||
try:
|
||||
assessment_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>Organization ID:</b> {provider.identity.organization_id}
|
||||
</li>"""
|
||||
|
||||
credentials_items = """
|
||||
<li class="list-group-item">
|
||||
<b>Authentication:</b> API Key
|
||||
</li>"""
|
||||
|
||||
access_key = getattr(provider.session, "access_key", None)
|
||||
if access_key:
|
||||
credentials_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Access Key:</b> {access_key}
|
||||
</li>"""
|
||||
|
||||
bearer_type = getattr(provider.identity, "bearer_type", None)
|
||||
bearer_email = getattr(provider.identity, "bearer_email", None)
|
||||
bearer_id = getattr(provider.identity, "bearer_id", None)
|
||||
if bearer_type:
|
||||
bearer_label = bearer_email or bearer_id or "-"
|
||||
credentials_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Bearer:</b> {bearer_type} ({bearer_label})
|
||||
</li>"""
|
||||
|
||||
region = getattr(provider.session, "default_region", None)
|
||||
if region:
|
||||
credentials_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Default Region:</b> {region}
|
||||
</li>"""
|
||||
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Scaleway Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">{assessment_items}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Scaleway Credentials
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">{credentials_items}
|
||||
</ul>
|
||||
</div>
|
||||
</div>"""
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
|
||||
)
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def get_assessment_summary(provider: Provider) -> str:
|
||||
"""
|
||||
|
||||
@@ -42,8 +42,6 @@ def stdout_report(finding, color, verbose, status, fix):
|
||||
details = finding.region
|
||||
if finding.check_metadata.Provider == "okta":
|
||||
details = finding.region
|
||||
if finding.check_metadata.Provider == "scaleway":
|
||||
details = finding.region
|
||||
|
||||
if (verbose or fix) and (not status or finding.status in status):
|
||||
if finding.muted:
|
||||
|
||||
@@ -111,9 +111,6 @@ def display_summary_table(
|
||||
elif provider.type == "okta":
|
||||
entity_type = "Okta Org"
|
||||
audited_entities = provider.identity.org_domain
|
||||
elif provider.type == "scaleway":
|
||||
entity_type = "Organization"
|
||||
audited_entities = provider.identity.organization_id
|
||||
|
||||
# Check if there are findings and that they are not all MANUAL
|
||||
if findings and not all(finding.status == "MANUAL" for finding in findings):
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "sagemaker_domain_sso_configured",
|
||||
"CheckTitle": "SageMaker domains use SSO authentication instead of IAM mode",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/AWS Security Best Practices"
|
||||
],
|
||||
"ServiceName": "sagemaker",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "Other",
|
||||
"ResourceGroup": "ai_ml",
|
||||
"Description": "**SageMaker Domain** configured with **IAM Identity Center (SSO) authentication**. The check validates that each SageMaker Domain uses SSO mode (`AuthMode: SSO`) and is associated with an IAM Identity Center instance (`SingleSignOnManagedApplicationInstanceId` or `SingleSignOnApplicationArn` present), ensuring user access is centrally managed through AWS IAM Identity Center.",
|
||||
"Risk": "IAM-mode domains create per-user IAM users or roles managed locally to SageMaker, drifting from the organization's identity provider and weakening lifecycle controls such as offboarding, MFA enforcement, and session policies. SSO-mode domains without an IAM Identity Center association leave authentication in an inconsistent state and bypass centralized access governance.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://docs.aws.amazon.com/sagemaker/latest/dg/onboard-sso-users.html"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws sagemaker describe-domain --domain-id <domain_id> --query '{AuthMode:AuthMode,SingleSignOnManagedApplicationInstanceId:SingleSignOnManagedApplicationInstanceId,SingleSignOnApplicationArn:SingleSignOnApplicationArn}'",
|
||||
"NativeIaC": "```yaml\n# CloudFormation: Create a SageMaker Domain with SSO authentication\nResources:\n SageMakerDomain:\n Type: AWS::SageMaker::Domain\n Properties:\n DomainName: <example_domain_name>\n AuthMode: SSO # Critical: enables IAM Identity Center authentication\n DefaultUserSettings:\n ExecutionRole: <example_role_arn>\n VpcId: <example_vpc_id>\n SubnetIds:\n - <example_subnet_id>\n```",
|
||||
"Other": "SageMaker Domains cannot be switched from IAM to SSO mode after creation. To remediate, create a new Domain with AuthMode set to SSO and migrate user profiles.",
|
||||
"Terraform": "```hcl\n# Terraform: Create a SageMaker Domain with SSO authentication\nresource \"aws_sagemaker_domain\" \"example\" {\n domain_name = \"<example_domain_name>\"\n auth_mode = \"SSO\" # Critical: enables IAM Identity Center authentication\n vpc_id = \"<example_vpc_id>\"\n subnet_ids = [\"<example_subnet_id>\"]\n\n default_user_settings {\n execution_role = \"<example_role_arn>\"\n }\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Configure SageMaker Domains with SSO authentication mode to anchor user access to AWS IAM Identity Center. This enforces centralized identity lifecycle management, MFA policies, and session controls. Domains created with IAM mode must be recreated with SSO mode since the auth mode cannot be changed after creation.",
|
||||
"Url": "https://hub.prowler.com/check/sagemaker_domain_sso_configured"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access",
|
||||
"gen-ai"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.sagemaker.sagemaker_client import sagemaker_client
|
||||
|
||||
|
||||
class sagemaker_domain_sso_configured(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for domain in sagemaker_client.sagemaker_domains:
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=domain)
|
||||
if domain.auth_mode == "SSO":
|
||||
if (
|
||||
domain.single_sign_on_managed_application_instance_id
|
||||
or domain.single_sign_on_application_arn
|
||||
):
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"SageMaker domain {domain.name} is configured with SSO authentication and is associated with an IAM Identity Center instance."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"SageMaker domain {domain.name} is configured with SSO authentication but is not associated with an IAM Identity Center instance."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
current_mode = domain.auth_mode if domain.auth_mode else "unknown"
|
||||
report.status_extended = f"SageMaker domain {domain.name} is not configured with SSO authentication, current mode is {current_mode}."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -15,6 +15,7 @@ class SageMaker(AWSService):
|
||||
self.sagemaker_notebook_instances = []
|
||||
self.sagemaker_models = []
|
||||
self.sagemaker_training_jobs = []
|
||||
self.sagemaker_domains = []
|
||||
self.endpoint_configs = {}
|
||||
|
||||
# Retrieve resources concurrently
|
||||
@@ -22,6 +23,7 @@ class SageMaker(AWSService):
|
||||
self.__threading_call__(self._list_models)
|
||||
self.__threading_call__(self._list_training_jobs)
|
||||
self.__threading_call__(self._list_endpoint_configs)
|
||||
self.__threading_call__(self._list_domains)
|
||||
|
||||
# Describe resources concurrently
|
||||
self.__threading_call__(self._describe_model, self.sagemaker_models)
|
||||
@@ -34,6 +36,7 @@ class SageMaker(AWSService):
|
||||
self.__threading_call__(
|
||||
self._describe_endpoint_config, list(self.endpoint_configs.values())
|
||||
)
|
||||
self.__threading_call__(self._describe_domain, self.sagemaker_domains)
|
||||
|
||||
# List tags concurrently for each resource collection
|
||||
# This replaces the previous sequential sequential execution to improve performance
|
||||
@@ -47,6 +50,7 @@ class SageMaker(AWSService):
|
||||
self.__threading_call__(
|
||||
self._list_tags_for_resource, list(self.endpoint_configs.values())
|
||||
)
|
||||
self.__threading_call__(self._list_tags_for_resource, self.sagemaker_domains)
|
||||
|
||||
def _list_notebook_instances(self, regional_client):
|
||||
logger.info("SageMaker - listing notebook instances...")
|
||||
@@ -218,6 +222,46 @@ class SageMaker(AWSService):
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _list_domains(self, regional_client):
|
||||
logger.info("SageMaker - listing domains...")
|
||||
try:
|
||||
list_domains_paginator = regional_client.get_paginator("list_domains")
|
||||
for page in list_domains_paginator.paginate():
|
||||
for domain in page["Domains"]:
|
||||
if not self.audit_resources or (
|
||||
is_resource_filtered(domain["DomainArn"], self.audit_resources)
|
||||
):
|
||||
self.sagemaker_domains.append(
|
||||
Domain(
|
||||
domain_id=domain["DomainId"],
|
||||
name=domain["DomainName"],
|
||||
region=regional_client.region,
|
||||
arn=domain["DomainArn"],
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _describe_domain(self, domain):
|
||||
logger.info("SageMaker - describing domain...")
|
||||
try:
|
||||
regional_client = self.regional_clients[domain.region]
|
||||
describe_domain = regional_client.describe_domain(DomainId=domain.domain_id)
|
||||
if "AuthMode" in describe_domain:
|
||||
domain.auth_mode = describe_domain["AuthMode"]
|
||||
domain.single_sign_on_managed_application_instance_id = describe_domain.get(
|
||||
"SingleSignOnManagedApplicationInstanceId"
|
||||
)
|
||||
domain.single_sign_on_application_arn = describe_domain.get(
|
||||
"SingleSignOnApplicationArn"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{domain.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _list_endpoint_configs(self, regional_client):
|
||||
logger.info("SageMaker - listing endpoint configs...")
|
||||
try:
|
||||
@@ -303,6 +347,17 @@ class ProductionVariant(BaseModel):
|
||||
initial_instance_count: int
|
||||
|
||||
|
||||
class Domain(BaseModel):
|
||||
domain_id: str
|
||||
name: str
|
||||
region: str
|
||||
arn: str
|
||||
auth_mode: Optional[str] = None
|
||||
single_sign_on_managed_application_instance_id: Optional[str] = None
|
||||
single_sign_on_application_arn: Optional[str] = None
|
||||
tags: Optional[list] = []
|
||||
|
||||
|
||||
class EndpointConfig(BaseModel):
|
||||
name: str
|
||||
region: str
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
from typing import Optional
|
||||
|
||||
# Cloudflare returns the plan name in ``zone.plan.name`` (e.g. "Free Website",
|
||||
# "Pro Website", "Business Website", "Enterprise Website"). Free plans do not
|
||||
# expose WAF managed rulesets at all, while paid plans expose them but the
|
||||
# legacy ``waf`` zone setting can lag behind the actual deployment state.
|
||||
PAID_PLAN_KEYWORDS = ("pro", "business", "enterprise")
|
||||
FREE_PLAN_KEYWORDS = ("free",)
|
||||
|
||||
|
||||
def _plan_matches(plan: Optional[str], keywords: tuple[str, ...]) -> bool:
|
||||
if not isinstance(plan, str):
|
||||
return False
|
||||
plan_lower = plan.lower()
|
||||
return any(keyword in plan_lower for keyword in keywords)
|
||||
|
||||
|
||||
def is_paid_plan(plan: Optional[str]) -> bool:
|
||||
"""Return True when the Cloudflare zone plan is a paid tier."""
|
||||
return _plan_matches(plan, PAID_PLAN_KEYWORDS)
|
||||
|
||||
|
||||
def is_free_plan(plan: Optional[str]) -> bool:
|
||||
"""Return True when the Cloudflare zone plan is the Free tier."""
|
||||
return _plan_matches(plan, FREE_PLAN_KEYWORDS)
|
||||
|
||||
|
||||
def paid_plan_suffix(plan: Optional[str], message: str) -> str:
|
||||
"""Return an explanatory suffix only when the zone is on a paid plan."""
|
||||
return f" {message}" if is_paid_plan(plan) else ""
|
||||
|
||||
|
||||
def free_plan_suffix(plan: Optional[str], message: str) -> str:
|
||||
"""Return an explanatory suffix only when the zone is on the Free plan."""
|
||||
return f" {message}" if is_free_plan(plan) else ""
|
||||
@@ -1,6 +1,19 @@
|
||||
from prowler.lib.check.models import Check, CheckReportCloudflare
|
||||
from prowler.providers.cloudflare.lib.plan import (
|
||||
free_plan_suffix,
|
||||
paid_plan_suffix,
|
||||
)
|
||||
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
|
||||
|
||||
PAID_PLAN_FALSE_POSITIVE_HINT = (
|
||||
"This may be a false positive if WAF managed rulesets are configured via "
|
||||
"the Cloudflare dashboard; verify manually in Security > WAF."
|
||||
)
|
||||
FREE_PLAN_UNAVAILABLE_HINT = (
|
||||
"This may be expected because the Web Application Firewall is not "
|
||||
"available on the Cloudflare Free plan."
|
||||
)
|
||||
|
||||
|
||||
class zone_waf_enabled(Check):
|
||||
"""Ensure that WAF is enabled for Cloudflare zones.
|
||||
@@ -35,6 +48,16 @@ class zone_waf_enabled(Check):
|
||||
report.status_extended = f"WAF is enabled for zone {zone.name}."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"WAF is not enabled for zone {zone.name}."
|
||||
# Two plan-specific hints can be appended to the FAIL message:
|
||||
# - Paid plans: the legacy ``waf`` zone setting can read ``off``
|
||||
# while WAF managed rulesets are deployed via the dashboard,
|
||||
# so the FAIL may be a false positive.
|
||||
# - Free plans: WAF is not available at all, so the FAIL is
|
||||
# expected and the suffix points that out.
|
||||
report.status_extended = (
|
||||
f"WAF is not enabled for zone {zone.name}."
|
||||
f"{paid_plan_suffix(zone.plan, PAID_PLAN_FALSE_POSITIVE_HINT)}"
|
||||
f"{free_plan_suffix(zone.plan, FREE_PLAN_UNAVAILABLE_HINT)}"
|
||||
)
|
||||
findings.append(report)
|
||||
return findings
|
||||
|
||||
@@ -416,17 +416,6 @@ class Provider(ABC):
|
||||
mutelist_path=arguments.mutelist_file,
|
||||
fixer_config=fixer_config,
|
||||
)
|
||||
elif "scaleway" in provider_class_name.lower():
|
||||
provider_class(
|
||||
access_key=getattr(arguments, "access_key", None),
|
||||
secret_key=getattr(arguments, "secret_key", None),
|
||||
organization_id=getattr(arguments, "organization_id", None),
|
||||
project_id=getattr(arguments, "project_id", None),
|
||||
region=getattr(arguments, "region", None),
|
||||
config_path=arguments.config_file,
|
||||
mutelist_path=arguments.mutelist_file,
|
||||
fixer_config=fixer_config,
|
||||
)
|
||||
|
||||
except TypeError as error:
|
||||
logger.critical(
|
||||
|
||||
+4
-1
@@ -20,7 +20,10 @@ class calendar_external_invitations_warning(Check):
|
||||
if calendar_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=calendar_client.provider.domain_resource,
|
||||
resource=calendar_client.policies,
|
||||
resource_id="calendarPolicies",
|
||||
resource_name="Calendar Policies",
|
||||
customer_id=calendar_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
warning_enabled = calendar_client.policies.external_invitations_warning
|
||||
|
||||
+4
-1
@@ -20,7 +20,10 @@ class calendar_external_sharing_primary_calendar(Check):
|
||||
if calendar_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=calendar_client.provider.domain_resource,
|
||||
resource=calendar_client.policies,
|
||||
resource_id="calendarPolicies",
|
||||
resource_name="Calendar Policies",
|
||||
customer_id=calendar_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
sharing = calendar_client.policies.primary_calendar_external_sharing
|
||||
|
||||
+4
-1
@@ -20,7 +20,10 @@ class calendar_external_sharing_secondary_calendar(Check):
|
||||
if calendar_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=calendar_client.provider.domain_resource,
|
||||
resource=calendar_client.policies,
|
||||
resource_id="calendarPolicies",
|
||||
resource_name="Calendar Policies",
|
||||
customer_id=calendar_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
sharing = calendar_client.policies.secondary_calendar_external_sharing
|
||||
|
||||
+3
@@ -24,6 +24,9 @@ class directory_super_admin_count(Check):
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=directory_client.provider.domain_resource,
|
||||
resource_id="directoryUsers",
|
||||
resource_name="Directory Users",
|
||||
customer_id=directory_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
if 2 <= admin_count <= 4:
|
||||
|
||||
+3
@@ -47,6 +47,9 @@ class directory_super_admin_only_admin_roles(Check):
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=directory_client.provider.domain_resource,
|
||||
resource_id="directoryUsers",
|
||||
resource_name="Directory Users",
|
||||
customer_id=directory_client.provider.identity.customer_id,
|
||||
)
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ class drive_access_checker_recipients_only(Check):
|
||||
if drive_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=drive_client.provider.domain_resource,
|
||||
resource=drive_client.policies,
|
||||
resource_id="drivePolicies",
|
||||
resource_name="Drive Policies",
|
||||
customer_id=drive_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
access_checker = drive_client.policies.access_checker_suggestions
|
||||
|
||||
+4
-1
@@ -20,7 +20,10 @@ class drive_desktop_access_disabled(Check):
|
||||
if drive_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=drive_client.provider.domain_resource,
|
||||
resource=drive_client.policies,
|
||||
resource_id="drivePolicies",
|
||||
resource_name="Drive Policies",
|
||||
customer_id=drive_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
allow_desktop = drive_client.policies.allow_drive_for_desktop
|
||||
|
||||
+4
-1
@@ -18,7 +18,10 @@ class drive_external_sharing_warn_users(Check):
|
||||
if drive_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=drive_client.provider.domain_resource,
|
||||
resource=drive_client.policies,
|
||||
resource_id="drivePolicies",
|
||||
resource_name="Drive Policies",
|
||||
customer_id=drive_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
warning_enabled = drive_client.policies.warn_for_external_sharing
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ class drive_internal_users_distribute_content(Check):
|
||||
if drive_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=drive_client.provider.domain_resource,
|
||||
resource=drive_client.policies,
|
||||
resource_id="drivePolicies",
|
||||
resource_name="Drive Policies",
|
||||
customer_id=drive_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
allowed = drive_client.policies.allowed_parties_for_distributing_content
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ class drive_publishing_files_disabled(Check):
|
||||
if drive_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=drive_client.provider.domain_resource,
|
||||
resource=drive_client.policies,
|
||||
resource_id="drivePolicies",
|
||||
resource_name="Drive Policies",
|
||||
customer_id=drive_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
allow_publishing = drive_client.policies.allow_publishing_files
|
||||
|
||||
+4
-1
@@ -20,7 +20,10 @@ class drive_shared_drive_creation_allowed(Check):
|
||||
if drive_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=drive_client.provider.domain_resource,
|
||||
resource=drive_client.policies,
|
||||
resource_id="drivePolicies",
|
||||
resource_name="Drive Policies",
|
||||
customer_id=drive_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
allow_creation = drive_client.policies.allow_shared_drive_creation
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ class drive_shared_drive_disable_download_print_copy(Check):
|
||||
if drive_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=drive_client.provider.domain_resource,
|
||||
resource=drive_client.policies,
|
||||
resource_id="drivePolicies",
|
||||
resource_name="Drive Policies",
|
||||
customer_id=drive_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
allowed = drive_client.policies.allowed_parties_for_download_print_copy
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ class drive_shared_drive_managers_cannot_override(Check):
|
||||
if drive_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=drive_client.provider.domain_resource,
|
||||
resource=drive_client.policies,
|
||||
resource_id="drivePolicies",
|
||||
resource_name="Drive Policies",
|
||||
customer_id=drive_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
allow_override = drive_client.policies.allow_managers_to_override_settings
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ class drive_shared_drive_members_only_access(Check):
|
||||
if drive_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=drive_client.provider.domain_resource,
|
||||
resource=drive_client.policies,
|
||||
resource_id="drivePolicies",
|
||||
resource_name="Drive Policies",
|
||||
customer_id=drive_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
allow_non_member = drive_client.policies.allow_non_member_access
|
||||
|
||||
+4
-1
@@ -18,7 +18,10 @@ class drive_sharing_allowlisted_domains(Check):
|
||||
if drive_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=drive_client.provider.domain_resource,
|
||||
resource=drive_client.policies,
|
||||
resource_id="drivePolicies",
|
||||
resource_name="Drive Policies",
|
||||
customer_id=drive_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
mode = drive_client.policies.external_sharing_mode
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@ class drive_warn_sharing_with_allowlisted_domains(Check):
|
||||
if drive_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=drive_client.provider.domain_resource,
|
||||
resource=drive_client.policies,
|
||||
resource_id="drivePolicies",
|
||||
resource_name="Drive Policies",
|
||||
customer_id=drive_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
warn_enabled = (
|
||||
|
||||
+4
-1
@@ -18,7 +18,10 @@ class gmail_anomalous_attachment_protection_enabled(Check):
|
||||
if gmail_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=gmail_client.provider.domain_resource,
|
||||
resource=gmail_client.policies,
|
||||
resource_id="gmailPolicies",
|
||||
resource_name="Gmail Policies",
|
||||
customer_id=gmail_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
enabled = gmail_client.policies.enable_anomalous_attachment_protection
|
||||
|
||||
+4
-1
@@ -18,7 +18,10 @@ class gmail_auto_forwarding_disabled(Check):
|
||||
if gmail_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=gmail_client.provider.domain_resource,
|
||||
resource=gmail_client.policies,
|
||||
resource_id="gmailPolicies",
|
||||
resource_name="Gmail Policies",
|
||||
customer_id=gmail_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
forwarding_enabled = gmail_client.policies.enable_auto_forwarding
|
||||
|
||||
+4
-1
@@ -18,7 +18,10 @@ class gmail_comprehensive_mail_storage_enabled(Check):
|
||||
if gmail_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=gmail_client.provider.domain_resource,
|
||||
resource=gmail_client.policies,
|
||||
resource_id="gmailPolicies",
|
||||
resource_name="Gmail Policies",
|
||||
customer_id=gmail_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
storage_enabled = gmail_client.policies.comprehensive_mail_storage_enabled
|
||||
|
||||
+4
-1
@@ -18,7 +18,10 @@ class gmail_domain_spoofing_protection_enabled(Check):
|
||||
if gmail_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=gmail_client.provider.domain_resource,
|
||||
resource=gmail_client.policies,
|
||||
resource_id="gmailPolicies",
|
||||
resource_name="Gmail Policies",
|
||||
customer_id=gmail_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
enabled = gmail_client.policies.detect_domain_name_spoofing
|
||||
|
||||
+4
-1
@@ -18,7 +18,10 @@ class gmail_employee_name_spoofing_protection_enabled(Check):
|
||||
if gmail_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=gmail_client.provider.domain_resource,
|
||||
resource=gmail_client.policies,
|
||||
resource_id="gmailPolicies",
|
||||
resource_name="Gmail Policies",
|
||||
customer_id=gmail_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
enabled = gmail_client.policies.detect_employee_name_spoofing
|
||||
|
||||
+4
-1
@@ -18,7 +18,10 @@ class gmail_encrypted_attachment_protection_enabled(Check):
|
||||
if gmail_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=gmail_client.provider.domain_resource,
|
||||
resource=gmail_client.policies,
|
||||
resource_id="gmailPolicies",
|
||||
resource_name="Gmail Policies",
|
||||
customer_id=gmail_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
enabled = gmail_client.policies.enable_encrypted_attachment_protection
|
||||
|
||||
+4
-1
@@ -18,7 +18,10 @@ class gmail_enhanced_pre_delivery_scanning_enabled(Check):
|
||||
if gmail_client.policies_fetched:
|
||||
report = CheckReportGoogleWorkspace(
|
||||
metadata=self.metadata(),
|
||||
resource=gmail_client.provider.domain_resource,
|
||||
resource=gmail_client.policies,
|
||||
resource_id="gmailPolicies",
|
||||
resource_name="Gmail Policies",
|
||||
customer_id=gmail_client.provider.identity.customer_id,
|
||||
)
|
||||
|
||||
scanning_enabled = (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user