mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-06-10 21:42:29 +00:00
Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa729a9d2d | |||
| d086a624a0 | |||
| a7c2b6cbce | |||
| 5da5848509 | |||
| 1a397d1024 | |||
| d9c849bed0 | |||
| a33c301fcc | |||
| e65bf81bf8 | |||
| ea419b49d8 | |||
| 5900d2314a | |||
| 3116352931 | |||
| d54bf452ca | |||
| 8d8f551664 | |||
| ae961e5065 | |||
| 3a096b1750 | |||
| 6f01041178 | |||
| 13e2ede763 | |||
| c53ddfd532 | |||
| f86bd7b52e | |||
| 6177fc6286 | |||
| 0fd952ae2b | |||
| 74622dd576 | |||
| 4dfa2b9748 | |||
| 435424a680 | |||
| dbbefd0558 | |||
| e55d1d470e | |||
| ab69f3b665 | |||
| a28f4994a8 | |||
| 349611d52d | |||
| 10b965e3c7 | |||
| 554a5024c1 | |||
| 7d03bc5e17 | |||
| c660b35ed6 | |||
| f3bac38a55 | |||
| 61330937f7 | |||
| 5ac978b9a3 | |||
| b4159bd590 | |||
| ef4d45d409 | |||
| f210c26c2f | |||
| a55a736363 | |||
| 9f2af5abc2 | |||
| fee98a58eb | |||
| 1ab8f2f0ac | |||
| e7fbc8b391 | |||
| 8caab36c3f | |||
| 0c4794b060 | |||
| 782e3f238b | |||
| e1c7e0a99b | |||
| 6ef70484c7 | |||
| 621170d9c9 | |||
| b6e2255e9e | |||
| 3ce8eae72f | |||
| 81aa1883fd | |||
| 534dedb608 | |||
| cff1704d7b | |||
| 0ca444895f | |||
| a9865209a1 | |||
| 8526e8b4a6 | |||
| a52ef3c04a | |||
| 1f3f5c2e27 | |||
| 6eebfcfe77 | |||
| 9d8b69abda | |||
| 60aa601e92 | |||
| fc1fd538bd | |||
| 40c1761840 | |||
| 0ab0e8671d | |||
| 7a7c828fc7 | |||
| 5cbe473eb9 | |||
| caf2f61563 | |||
| 9dc4deccb6 | |||
| 476e7d1010 | |||
| cb01769237 | |||
| 4c802620c4 | |||
| 4fa8d5465e | |||
| 31b9619627 | |||
| d4a1bc10e9 | |||
| a1848747a3 | |||
| 4c0a3f477f | |||
| bc443eef22 | |||
| 298ad3382f | |||
| bfcbe0a9c4 | |||
| 37aa290d1c | |||
| 5cd7fe4f96 | |||
| 0234f038f0 |
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "prowler-plugins",
|
||||
"description": "Prowler Cloud Security for Claude Code",
|
||||
"owner": {
|
||||
"name": "Prowler",
|
||||
"email": "support@prowler.com"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "prowler",
|
||||
"source": "./claude_plugins/prowler",
|
||||
"description": "Prowler for Claude Code — cloud security and compliance skills powered by the Prowler MCP server. Bundles compliance triage and remediation; more skills coming.",
|
||||
"category": "security",
|
||||
"homepage": "https://prowler.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -145,7 +145,7 @@ SENTRY_RELEASE=local
|
||||
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT}
|
||||
|
||||
#### Prowler release version ####
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.27.0
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.28.2
|
||||
|
||||
# Social login credentials
|
||||
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
|
||||
|
||||
+22
-22
@@ -6,17 +6,17 @@
|
||||
version: 2
|
||||
updates:
|
||||
# v5
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 25
|
||||
target-branch: master
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "pip"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
# - package-ecosystem: "pip"
|
||||
# directory: "/"
|
||||
# schedule:
|
||||
# interval: "monthly"
|
||||
# open-pull-requests-limit: 25
|
||||
# target-branch: master
|
||||
# labels:
|
||||
# - "dependencies"
|
||||
# - "pip"
|
||||
# cooldown:
|
||||
# default-days: 7
|
||||
|
||||
# Dependabot Updates are temporary disabled - 2025/03/19
|
||||
# - package-ecosystem: "pip"
|
||||
@@ -66,17 +66,17 @@ updates:
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "pre-commit"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 25
|
||||
target-branch: master
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "pre-commit"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
# - package-ecosystem: "pre-commit"
|
||||
# directory: "/"
|
||||
# schedule:
|
||||
# interval: "monthly"
|
||||
# open-pull-requests-limit: 25
|
||||
# target-branch: master
|
||||
# labels:
|
||||
# - "dependencies"
|
||||
# - "pre-commit"
|
||||
# cooldown:
|
||||
# default-days: 7
|
||||
|
||||
# Dependabot Updates are temporary disabled - 2025/04/15
|
||||
# v4.6
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:best-practices",
|
||||
":enablePreCommit",
|
||||
":semanticCommits",
|
||||
":enableVulnerabilityAlertsWithLabel(security)",
|
||||
"docker:enableMajor",
|
||||
"helpers:pinGitHubActionDigestsToSemver",
|
||||
"helpers:disableTypesNodeMajor",
|
||||
"security:openssf-scorecard",
|
||||
"customManagers:githubActionsVersions",
|
||||
"customManagers:dockerfileVersions"
|
||||
],
|
||||
"timezone": "Europe/Madrid",
|
||||
"baseBranchPatterns": [
|
||||
"master"
|
||||
],
|
||||
"labels": [
|
||||
"dependencies"
|
||||
],
|
||||
"dependencyDashboardTitle": "Dependency Dashboard",
|
||||
"prConcurrentLimit": 20,
|
||||
"prHourlyLimit": 10,
|
||||
"vulnerabilityAlerts": {
|
||||
"prHourlyLimit": 0,
|
||||
"prConcurrentLimit": 0
|
||||
},
|
||||
"configMigration": true,
|
||||
"minimumReleaseAge": "7 days",
|
||||
"rangeStrategy": "pin",
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "Patches: 1st of every month, Madrid overnight window (22:00-06:00)",
|
||||
"matchUpdateTypes": [
|
||||
"patch"
|
||||
],
|
||||
"schedule": [
|
||||
"* 22-23,0-5 1 * *"
|
||||
],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "Minors: 8th of every 3 months, Madrid overnight window (22:00-06:00)",
|
||||
"matchUpdateTypes": [
|
||||
"minor"
|
||||
],
|
||||
"schedule": [
|
||||
"* 22-23,0-5 8 */3 *"
|
||||
],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "Majors: 15th of every 3 months, Madrid overnight window",
|
||||
"matchUpdateTypes": [
|
||||
"major"
|
||||
],
|
||||
"schedule": [
|
||||
"* 22-23,0-5 15 */3 *"
|
||||
],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "GitHub Actions - single grouped PR, no changelog, scope=ci",
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"groupName": "github-actions",
|
||||
"semanticCommitScope": "ci",
|
||||
"addLabels": [
|
||||
"no-changelog"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Docker images - single grouped PR, no changelog, scope=docker",
|
||||
"matchManagers": [
|
||||
"dockerfile",
|
||||
"docker-compose"
|
||||
],
|
||||
"groupName": "docker",
|
||||
"semanticCommitScope": "docker",
|
||||
"addLabels": [
|
||||
"no-changelog"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Pre-commit hooks - single grouped PR, scope=pre-commit",
|
||||
"matchManagers": [
|
||||
"pre-commit"
|
||||
],
|
||||
"groupName": "pre-commit hooks",
|
||||
"semanticCommitScope": "pre-commit",
|
||||
"addLabels": [
|
||||
"no-changelog"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "UI - scope=ui",
|
||||
"matchFileNames": [
|
||||
"ui/**"
|
||||
],
|
||||
"semanticCommitScope": "ui"
|
||||
},
|
||||
{
|
||||
"description": "API - scope=api",
|
||||
"matchFileNames": [
|
||||
"api/**"
|
||||
],
|
||||
"semanticCommitScope": "api"
|
||||
},
|
||||
{
|
||||
"description": "MCP server - scope=mcp",
|
||||
"matchFileNames": [
|
||||
"mcp_server/**"
|
||||
],
|
||||
"semanticCommitScope": "mcp"
|
||||
},
|
||||
{
|
||||
"description": "Python SDK (root) - scope=sdk",
|
||||
"matchFileNames": [
|
||||
"pyproject.toml",
|
||||
"poetry.lock",
|
||||
"util/prowler-bulk-provisioning/**"
|
||||
],
|
||||
"semanticCommitScope": "sdk"
|
||||
},
|
||||
{
|
||||
"description": "UI devDependencies - no changelog",
|
||||
"matchFileNames": [
|
||||
"ui/**"
|
||||
],
|
||||
"matchDepTypes": [
|
||||
"devDependencies"
|
||||
],
|
||||
"addLabels": [
|
||||
"no-changelog"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
name: 'API: Bump Version'
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- 'published'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.release.tag_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
PROWLER_VERSION: ${{ github.event.release.tag_name }}
|
||||
BASE_BRANCH: master
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
detect-release-type:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
is_minor: ${{ steps.detect.outputs.is_minor }}
|
||||
is_patch: ${{ steps.detect.outputs.is_patch }}
|
||||
major_version: ${{ steps.detect.outputs.major_version }}
|
||||
minor_version: ${{ steps.detect.outputs.minor_version }}
|
||||
patch_version: ${{ steps.detect.outputs.patch_version }}
|
||||
current_api_version: ${{ steps.get_api_version.outputs.current_api_version }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Get current API version
|
||||
id: get_api_version
|
||||
run: |
|
||||
CURRENT_API_VERSION=$(grep -oP '^version = "\K[^"]+' api/pyproject.toml)
|
||||
echo "current_api_version=${CURRENT_API_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
echo "Current API version: $CURRENT_API_VERSION"
|
||||
|
||||
- name: Detect release type and parse version
|
||||
id: detect
|
||||
run: |
|
||||
if [[ $PROWLER_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
MAJOR_VERSION=${BASH_REMATCH[1]}
|
||||
MINOR_VERSION=${BASH_REMATCH[2]}
|
||||
PATCH_VERSION=${BASH_REMATCH[3]}
|
||||
|
||||
echo "major_version=${MAJOR_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
echo "minor_version=${MINOR_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
echo "patch_version=${PATCH_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
if (( MAJOR_VERSION != 5 )); then
|
||||
echo "::error::Releasing another Prowler major version, aborting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (( PATCH_VERSION == 0 )); then
|
||||
echo "is_minor=true" >> "${GITHUB_OUTPUT}"
|
||||
echo "is_patch=false" >> "${GITHUB_OUTPUT}"
|
||||
echo "✓ Minor release detected: $PROWLER_VERSION"
|
||||
else
|
||||
echo "is_minor=false" >> "${GITHUB_OUTPUT}"
|
||||
echo "is_patch=true" >> "${GITHUB_OUTPUT}"
|
||||
echo "✓ Patch release detected: $PROWLER_VERSION"
|
||||
fi
|
||||
else
|
||||
echo "::error::Invalid version syntax: '$PROWLER_VERSION' (must be X.Y.Z)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bump-minor-version:
|
||||
needs: detect-release-type
|
||||
if: needs.detect-release-type.outputs.is_minor == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Calculate next API minor version
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
CURRENT_API_VERSION="${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_CURRENT_API_VERSION}"
|
||||
|
||||
# API version follows Prowler minor + 1
|
||||
# For Prowler 5.17.0 -> API 1.18.0
|
||||
# For next master (Prowler 5.18.0) -> API 1.19.0
|
||||
NEXT_API_VERSION=1.$((MINOR_VERSION + 2)).0
|
||||
|
||||
echo "CURRENT_API_VERSION=${CURRENT_API_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "NEXT_API_VERSION=${NEXT_API_VERSION}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "Prowler release version: ${MAJOR_VERSION}.${MINOR_VERSION}.0"
|
||||
echo "Current API version: $CURRENT_API_VERSION"
|
||||
echo "Next API minor version (for master): $NEXT_API_VERSION"
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_CURRENT_API_VERSION: ${{ needs.detect-release-type.outputs.current_api_version }}
|
||||
|
||||
- name: Bump API versions in files for master
|
||||
run: |
|
||||
set -e
|
||||
|
||||
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${NEXT_API_VERSION}\"|" api/pyproject.toml
|
||||
sed -i "s|spectacular_settings.VERSION = \"${CURRENT_API_VERSION}\"|spectacular_settings.VERSION = \"${NEXT_API_VERSION}\"|" api/src/backend/api/v1/views.py
|
||||
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${NEXT_API_VERSION}|" api/src/backend/api/specs/v1.yaml
|
||||
|
||||
echo "Files modified:"
|
||||
git --no-pager diff
|
||||
|
||||
- name: Create PR for next API minor version to master
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: master
|
||||
commit-message: 'chore(api): Bump version to v${{ env.NEXT_API_VERSION }}'
|
||||
branch: api-version-bump-to-v${{ env.NEXT_API_VERSION }}
|
||||
title: 'chore(api): Bump version to v${{ env.NEXT_API_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler API version to v${{ env.NEXT_API_VERSION }} after releasing Prowler v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
|
||||
- name: Checkout version branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: v${{ needs.detect-release-type.outputs.major_version }}.${{ needs.detect-release-type.outputs.minor_version }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Calculate first API patch version
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
CURRENT_API_VERSION="${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_CURRENT_API_VERSION}"
|
||||
VERSION_BRANCH=v${MAJOR_VERSION}.${MINOR_VERSION}
|
||||
|
||||
# API version follows Prowler minor + 1
|
||||
# For Prowler 5.17.0 release -> version branch v5.17 should have API 1.18.1
|
||||
FIRST_API_PATCH_VERSION=1.$((MINOR_VERSION + 1)).1
|
||||
|
||||
echo "CURRENT_API_VERSION=${CURRENT_API_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "FIRST_API_PATCH_VERSION=${FIRST_API_PATCH_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "VERSION_BRANCH=${VERSION_BRANCH}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "Prowler release version: ${MAJOR_VERSION}.${MINOR_VERSION}.0"
|
||||
echo "First API patch version (for ${VERSION_BRANCH}): $FIRST_API_PATCH_VERSION"
|
||||
echo "Version branch: $VERSION_BRANCH"
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_CURRENT_API_VERSION: ${{ needs.detect-release-type.outputs.current_api_version }}
|
||||
|
||||
- name: Bump API versions in files for version branch
|
||||
run: |
|
||||
set -e
|
||||
|
||||
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${FIRST_API_PATCH_VERSION}\"|" api/pyproject.toml
|
||||
sed -i "s|spectacular_settings.VERSION = \"${CURRENT_API_VERSION}\"|spectacular_settings.VERSION = \"${FIRST_API_PATCH_VERSION}\"|" api/src/backend/api/v1/views.py
|
||||
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${FIRST_API_PATCH_VERSION}|" api/src/backend/api/specs/v1.yaml
|
||||
|
||||
echo "Files modified:"
|
||||
git --no-pager diff
|
||||
|
||||
- name: Create PR for first API patch version to version branch
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: ${{ env.VERSION_BRANCH }}
|
||||
commit-message: 'chore(api): Bump version to v${{ env.FIRST_API_PATCH_VERSION }}'
|
||||
branch: api-version-bump-to-v${{ env.FIRST_API_PATCH_VERSION }}
|
||||
title: 'chore(api): Bump version to v${{ env.FIRST_API_PATCH_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler API version to v${{ env.FIRST_API_PATCH_VERSION }} in version branch after releasing Prowler v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
|
||||
bump-patch-version:
|
||||
needs: detect-release-type
|
||||
if: needs.detect-release-type.outputs.is_patch == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Calculate next API patch version
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
PATCH_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_PATCH_VERSION}
|
||||
CURRENT_API_VERSION="${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_CURRENT_API_VERSION}"
|
||||
VERSION_BRANCH=v${MAJOR_VERSION}.${MINOR_VERSION}
|
||||
|
||||
# Extract current API patch to increment it
|
||||
if [[ $CURRENT_API_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
API_PATCH=${BASH_REMATCH[3]}
|
||||
|
||||
# API version follows Prowler minor + 1
|
||||
# Keep same API minor (based on Prowler minor), increment patch
|
||||
NEXT_API_PATCH_VERSION=1.$((MINOR_VERSION + 1)).$((API_PATCH + 1))
|
||||
|
||||
echo "CURRENT_API_VERSION=${CURRENT_API_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "NEXT_API_PATCH_VERSION=${NEXT_API_PATCH_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "VERSION_BRANCH=${VERSION_BRANCH}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "Prowler release version: ${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}"
|
||||
echo "Current API version: $CURRENT_API_VERSION"
|
||||
echo "Next API patch version: $NEXT_API_PATCH_VERSION"
|
||||
echo "Target branch: $VERSION_BRANCH"
|
||||
else
|
||||
echo "::error::Invalid API version format: $CURRENT_API_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_PATCH_VERSION: ${{ needs.detect-release-type.outputs.patch_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_CURRENT_API_VERSION: ${{ needs.detect-release-type.outputs.current_api_version }}
|
||||
|
||||
- name: Bump API versions in files for version branch
|
||||
run: |
|
||||
set -e
|
||||
|
||||
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${NEXT_API_PATCH_VERSION}\"|" api/pyproject.toml
|
||||
sed -i "s|spectacular_settings.VERSION = \"${CURRENT_API_VERSION}\"|spectacular_settings.VERSION = \"${NEXT_API_PATCH_VERSION}\"|" api/src/backend/api/v1/views.py
|
||||
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${NEXT_API_PATCH_VERSION}|" api/src/backend/api/specs/v1.yaml
|
||||
|
||||
echo "Files modified:"
|
||||
git --no-pager diff
|
||||
|
||||
- name: Create PR for next API patch version to version branch
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: ${{ env.VERSION_BRANCH }}
|
||||
commit-message: 'chore(api): Bump version to v${{ env.NEXT_API_PATCH_VERSION }}'
|
||||
branch: api-version-bump-to-v${{ env.NEXT_API_PATCH_VERSION }}
|
||||
title: 'chore(api): Bump version to v${{ env.NEXT_API_PATCH_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler API version to v${{ env.NEXT_API_PATCH_VERSION }} after releasing Prowler v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Check for API changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
api/**
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -61,12 +61,12 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ./.github/codeql/api-codeql-config.yml
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
category: '/language:${{ matrix.language }}'
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -122,6 +122,7 @@ jobs:
|
||||
github.com:443
|
||||
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
pypi.org:443
|
||||
registry-1.docker.io:443
|
||||
release-assets.githubusercontent.com:443
|
||||
@@ -132,14 +133,18 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Pin prowler SDK to latest master commit
|
||||
if: github.event_name == 'push'
|
||||
- name: Refresh prowler SDK pin to current branch tip
|
||||
run: |
|
||||
LATEST_SHA=$(git ls-remote https://github.com/prowler-cloud/prowler.git refs/heads/master | cut -f1)
|
||||
sed -i "s|prowler-cloud/prowler.git@master|prowler-cloud/prowler.git@${LATEST_SHA}|" api/pyproject.toml
|
||||
# api/pyproject.toml has `@master` on master and `@v5.X` on release
|
||||
# branches (set by prepare-release.yml). uv lock --upgrade-package
|
||||
# re-resolves whichever ref is present against the current branch tip
|
||||
# and writes the SHA into api/uv.lock. The Dockerfile runs
|
||||
# `uv sync --locked`, which is what actually drives the install.
|
||||
pip install --no-cache-dir "uv==0.11.14"
|
||||
(cd api && uv lock --upgrade-package prowler)
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -150,7 +155,7 @@ jobs:
|
||||
- name: Build and push API container for ${{ matrix.arch }}
|
||||
id: container-push
|
||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
push: true
|
||||
@@ -170,7 +175,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -179,8 +184,9 @@ jobs:
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -230,7 +236,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -277,7 +283,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Check if Dockerfile changed
|
||||
id: dockerfile-changed
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: api/Dockerfile
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -83,6 +83,7 @@ jobs:
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
debian.map.fastlydns.net:80
|
||||
release-assets.githubusercontent.com:443
|
||||
objects.githubusercontent.com:443
|
||||
@@ -103,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Check for API changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: api/**
|
||||
files_ignore: |
|
||||
@@ -118,7 +119,7 @@ jobs:
|
||||
|
||||
- name: Build container
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.API_WORKING_DIR }}
|
||||
push: false
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -59,6 +59,7 @@ jobs:
|
||||
github.com:443
|
||||
api.github.com:443
|
||||
objects.githubusercontent.com:443
|
||||
raw.githubusercontent.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
api.osv.dev:443
|
||||
api.deps.dev:443
|
||||
@@ -72,7 +73,7 @@ jobs:
|
||||
|
||||
- name: Check for API changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
api/**
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
|
||||
- name: Check for API changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
api/**
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
name: 'Release: Bump Versions'
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- 'published'
|
||||
|
||||
concurrency:
|
||||
group: release-bump-versions-${{ github.event.release.tag_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
PROWLER_VERSION: ${{ github.event.release.tag_name }}
|
||||
DOCS_FILE: docs/getting-started/installation/prowler-app.mdx
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
detect-release-type:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
is_minor: ${{ steps.detect.outputs.is_minor }}
|
||||
is_patch: ${{ steps.detect.outputs.is_patch }}
|
||||
major_version: ${{ steps.detect.outputs.major_version }}
|
||||
minor_version: ${{ steps.detect.outputs.minor_version }}
|
||||
patch_version: ${{ steps.detect.outputs.patch_version }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Detect release type and parse version
|
||||
id: detect
|
||||
run: |
|
||||
if [[ $PROWLER_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
MAJOR_VERSION=${BASH_REMATCH[1]}
|
||||
MINOR_VERSION=${BASH_REMATCH[2]}
|
||||
PATCH_VERSION=${BASH_REMATCH[3]}
|
||||
|
||||
echo "major_version=${MAJOR_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
echo "minor_version=${MINOR_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
echo "patch_version=${PATCH_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
if (( MAJOR_VERSION != 5 )); then
|
||||
echo "::error::Releasing another Prowler major version, aborting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (( PATCH_VERSION == 0 )); then
|
||||
echo "is_minor=true" >> "${GITHUB_OUTPUT}"
|
||||
echo "is_patch=false" >> "${GITHUB_OUTPUT}"
|
||||
echo "✓ Minor release detected: $PROWLER_VERSION"
|
||||
else
|
||||
echo "is_minor=false" >> "${GITHUB_OUTPUT}"
|
||||
echo "is_patch=true" >> "${GITHUB_OUTPUT}"
|
||||
echo "✓ Patch release detected: $PROWLER_VERSION"
|
||||
fi
|
||||
else
|
||||
echo "::error::Invalid version syntax: '$PROWLER_VERSION' (must be X.Y.Z)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bump-minor-master:
|
||||
name: Bump versions on master (minor release)
|
||||
needs: detect-release-type
|
||||
if: needs.detect-release-type.outputs.is_minor == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: master
|
||||
persist-credentials: false
|
||||
|
||||
- name: Compute next versions for master
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
|
||||
# SDK / UI / docs mirror the Prowler version directly.
|
||||
NEXT_SDK_VERSION=${MAJOR_VERSION}.$((MINOR_VERSION + 1)).0
|
||||
|
||||
# API is an independent stream: 1.<prowler_minor + 1>.X
|
||||
# After Prowler 5.M.0 release, master moves on to next API minor: 1.(M+2).0
|
||||
NEXT_API_VERSION=1.$((MINOR_VERSION + 2)).0
|
||||
|
||||
# Read current versions to drive sed replacements.
|
||||
CURRENT_API_VERSION=$(grep -oP '^version = "\K[^"]+' api/pyproject.toml)
|
||||
CURRENT_DOCS_VERSION=$(grep -oP 'PROWLER_UI_VERSION="\K[^"]+' "${DOCS_FILE}")
|
||||
|
||||
echo "NEXT_SDK_VERSION=${NEXT_SDK_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "NEXT_API_VERSION=${NEXT_API_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "CURRENT_API_VERSION=${CURRENT_API_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "CURRENT_DOCS_VERSION=${CURRENT_DOCS_VERSION}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "Released Prowler version: $PROWLER_VERSION"
|
||||
echo "Next SDK/UI version (master): $NEXT_SDK_VERSION"
|
||||
echo "Next API version (master): $NEXT_API_VERSION (current: $CURRENT_API_VERSION)"
|
||||
echo "Docs target version: $PROWLER_VERSION (current: $CURRENT_DOCS_VERSION)"
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
|
||||
- name: Decide whether to bump docs on master
|
||||
id: docs_decision
|
||||
run: |
|
||||
# Skip docs bump if master is already at or ahead of the release version
|
||||
# (re-run, or patch shipped against an older minor line).
|
||||
HIGHEST=$(printf '%s\n%s\n' "${CURRENT_DOCS_VERSION}" "${PROWLER_VERSION}" | sort -V | tail -n1)
|
||||
if [[ "${CURRENT_DOCS_VERSION}" == "${PROWLER_VERSION}" || "${HIGHEST}" != "${PROWLER_VERSION}" ]]; then
|
||||
echo "skip=true" >> "${GITHUB_OUTPUT}"
|
||||
echo "Skipping docs bump: current ($CURRENT_DOCS_VERSION) >= release ($PROWLER_VERSION)"
|
||||
else
|
||||
echo "skip=false" >> "${GITHUB_OUTPUT}"
|
||||
fi
|
||||
|
||||
- name: Bump SDK version (pyproject.toml, config.py)
|
||||
run: |
|
||||
set -e
|
||||
sed -i "s|version = \"${PROWLER_VERSION}\"|version = \"${NEXT_SDK_VERSION}\"|" pyproject.toml
|
||||
sed -i "s|prowler_version = \"${PROWLER_VERSION}\"|prowler_version = \"${NEXT_SDK_VERSION}\"|" prowler/config/config.py
|
||||
|
||||
- name: Bump API version (api/pyproject.toml, specs/v1.yaml)
|
||||
run: |
|
||||
set -e
|
||||
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${NEXT_API_VERSION}\"|" api/pyproject.toml
|
||||
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${NEXT_API_VERSION}|" api/src/backend/api/specs/v1.yaml
|
||||
|
||||
- name: Regenerate lockfiles after version bump
|
||||
run: |
|
||||
set -e
|
||||
# The bumps above edit pyproject.toml / api/pyproject.toml but leave
|
||||
# uv.lock / api/uv.lock stale, which makes `uv sync --locked` fail in
|
||||
# the container builds. Refresh both with the uv version the images
|
||||
# pin (plain `uv lock`, no --upgrade: only the version line changes).
|
||||
pip install --no-cache-dir "uv==0.11.14"
|
||||
uv lock
|
||||
(cd api && uv lock)
|
||||
|
||||
- name: Bump UI version (.env)
|
||||
run: |
|
||||
set -e
|
||||
sed -i "s|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=.*|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v${NEXT_SDK_VERSION}|" .env
|
||||
|
||||
- name: Bump docs versions (prowler-app.mdx)
|
||||
if: steps.docs_decision.outputs.skip == 'false'
|
||||
run: |
|
||||
set -e
|
||||
sed -i "s|PROWLER_UI_VERSION=\"${CURRENT_DOCS_VERSION}\"|PROWLER_UI_VERSION=\"${PROWLER_VERSION}\"|" "${DOCS_FILE}"
|
||||
sed -i "s|PROWLER_API_VERSION=\"${CURRENT_DOCS_VERSION}\"|PROWLER_API_VERSION=\"${PROWLER_VERSION}\"|" "${DOCS_FILE}"
|
||||
|
||||
- name: Show consolidated diff
|
||||
run: git --no-pager diff
|
||||
|
||||
- name: Create PR for next versions to master
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: master
|
||||
commit-message: 'chore(release): Bump versions to v${{ env.NEXT_SDK_VERSION }}'
|
||||
branch: release-version-bump-to-v${{ env.NEXT_SDK_VERSION }}
|
||||
title: 'chore(release): Bump versions to v${{ env.NEXT_SDK_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler versions on master after releasing Prowler v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
| Area | File(s) | New version |
|
||||
| --- | --- | --- |
|
||||
| SDK | `pyproject.toml`, `prowler/config/config.py` | v${{ env.NEXT_SDK_VERSION }} |
|
||||
| API | `api/pyproject.toml`, `api/src/backend/api/specs/v1.yaml` | v${{ env.NEXT_API_VERSION }} |
|
||||
| UI | `.env` (`NEXT_PUBLIC_PROWLER_RELEASE_VERSION`) | v${{ env.NEXT_SDK_VERSION }} |
|
||||
| Docs | `docs/getting-started/installation/prowler-app.mdx` (`PROWLER_UI_VERSION`, `PROWLER_API_VERSION`) | v${{ env.PROWLER_VERSION }} (skipped if already at or ahead) |
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
|
||||
bump-minor-version-branch:
|
||||
name: Bump versions on version branch (minor release)
|
||||
needs: detect-release-type
|
||||
if: needs.detect-release-type.outputs.is_minor == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout version branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: v${{ needs.detect-release-type.outputs.major_version }}.${{ needs.detect-release-type.outputs.minor_version }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Compute first patch versions for version branch
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
VERSION_BRANCH=v${MAJOR_VERSION}.${MINOR_VERSION}
|
||||
|
||||
# SDK / UI first patch mirrors Prowler version directly.
|
||||
FIRST_PATCH_VERSION=${MAJOR_VERSION}.${MINOR_VERSION}.1
|
||||
|
||||
# API on this branch stays on the 1.<MINOR+1>.X stream, starting at .1
|
||||
FIRST_API_PATCH_VERSION=1.$((MINOR_VERSION + 1)).1
|
||||
|
||||
CURRENT_API_VERSION=$(grep -oP '^version = "\K[^"]+' api/pyproject.toml)
|
||||
|
||||
echo "FIRST_PATCH_VERSION=${FIRST_PATCH_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "FIRST_API_PATCH_VERSION=${FIRST_API_PATCH_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "CURRENT_API_VERSION=${CURRENT_API_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "VERSION_BRANCH=${VERSION_BRANCH}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "Released Prowler version: $PROWLER_VERSION"
|
||||
echo "Version branch: $VERSION_BRANCH"
|
||||
echo "First SDK/UI patch: $FIRST_PATCH_VERSION"
|
||||
echo "First API patch: $FIRST_API_PATCH_VERSION (current: $CURRENT_API_VERSION)"
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
|
||||
- name: Bump SDK version (pyproject.toml, config.py)
|
||||
run: |
|
||||
set -e
|
||||
sed -i "s|version = \"${PROWLER_VERSION}\"|version = \"${FIRST_PATCH_VERSION}\"|" pyproject.toml
|
||||
sed -i "s|prowler_version = \"${PROWLER_VERSION}\"|prowler_version = \"${FIRST_PATCH_VERSION}\"|" prowler/config/config.py
|
||||
|
||||
- name: Bump API version (api/pyproject.toml, specs/v1.yaml)
|
||||
run: |
|
||||
set -e
|
||||
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${FIRST_API_PATCH_VERSION}\"|" api/pyproject.toml
|
||||
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${FIRST_API_PATCH_VERSION}|" api/src/backend/api/specs/v1.yaml
|
||||
|
||||
- name: Regenerate lockfiles after version bump
|
||||
run: |
|
||||
set -e
|
||||
# The bumps above edit pyproject.toml / api/pyproject.toml but leave
|
||||
# uv.lock / api/uv.lock stale, which makes `uv sync --locked` fail in
|
||||
# the container builds. Refresh both with the uv version the images
|
||||
# pin (plain `uv lock`, no --upgrade: only the version line changes).
|
||||
pip install --no-cache-dir "uv==0.11.14"
|
||||
uv lock
|
||||
(cd api && uv lock)
|
||||
|
||||
- name: Bump UI version (.env)
|
||||
run: |
|
||||
set -e
|
||||
sed -i "s|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=.*|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v${FIRST_PATCH_VERSION}|" .env
|
||||
|
||||
- name: Show consolidated diff
|
||||
run: git --no-pager diff
|
||||
|
||||
- name: Create PR for first patch versions to version branch
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: ${{ env.VERSION_BRANCH }}
|
||||
commit-message: 'chore(release): Bump versions to v${{ env.FIRST_PATCH_VERSION }}'
|
||||
branch: release-version-bump-to-v${{ env.FIRST_PATCH_VERSION }}
|
||||
title: 'chore(release): Bump versions to v${{ env.FIRST_PATCH_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler versions on `${{ env.VERSION_BRANCH }}` after releasing Prowler v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
| Area | File(s) | New version |
|
||||
| --- | --- | --- |
|
||||
| SDK | `pyproject.toml`, `prowler/config/config.py` | v${{ env.FIRST_PATCH_VERSION }} |
|
||||
| API | `api/pyproject.toml`, `api/src/backend/api/specs/v1.yaml` | v${{ env.FIRST_API_PATCH_VERSION }} |
|
||||
| UI | `.env` (`NEXT_PUBLIC_PROWLER_RELEASE_VERSION`) | v${{ env.FIRST_PATCH_VERSION }} |
|
||||
| Docs | (not touched on version branches) | — |
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
|
||||
bump-patch-version-branch:
|
||||
name: Bump versions on version branch (patch release)
|
||||
needs: detect-release-type
|
||||
if: needs.detect-release-type.outputs.is_patch == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Compute next patch versions
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
PATCH_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_PATCH_VERSION}
|
||||
VERSION_BRANCH=v${MAJOR_VERSION}.${MINOR_VERSION}
|
||||
|
||||
# SDK / UI patch mirrors Prowler version directly.
|
||||
NEXT_PATCH_VERSION=${MAJOR_VERSION}.${MINOR_VERSION}.$((PATCH_VERSION + 1))
|
||||
|
||||
CURRENT_API_VERSION=$(grep -oP '^version = "\K[^"]+' api/pyproject.toml)
|
||||
|
||||
# API on this branch stays on 1.<MINOR+1>.X; bump its patch component.
|
||||
if [[ $CURRENT_API_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
API_PATCH=${BASH_REMATCH[3]}
|
||||
NEXT_API_PATCH_VERSION=1.$((MINOR_VERSION + 1)).$((API_PATCH + 1))
|
||||
else
|
||||
echo "::error::Invalid API version format: $CURRENT_API_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "NEXT_PATCH_VERSION=${NEXT_PATCH_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "NEXT_API_PATCH_VERSION=${NEXT_API_PATCH_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "CURRENT_API_VERSION=${CURRENT_API_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "VERSION_BRANCH=${VERSION_BRANCH}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "Released Prowler version: $PROWLER_VERSION"
|
||||
echo "Version branch: $VERSION_BRANCH"
|
||||
echo "Next SDK/UI patch: $NEXT_PATCH_VERSION"
|
||||
echo "Next API patch: $NEXT_API_PATCH_VERSION (current: $CURRENT_API_VERSION)"
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_PATCH_VERSION: ${{ needs.detect-release-type.outputs.patch_version }}
|
||||
|
||||
- name: Bump SDK version (pyproject.toml, config.py)
|
||||
run: |
|
||||
set -e
|
||||
sed -i "s|version = \"${PROWLER_VERSION}\"|version = \"${NEXT_PATCH_VERSION}\"|" pyproject.toml
|
||||
sed -i "s|prowler_version = \"${PROWLER_VERSION}\"|prowler_version = \"${NEXT_PATCH_VERSION}\"|" prowler/config/config.py
|
||||
|
||||
- name: Bump API version (api/pyproject.toml, specs/v1.yaml)
|
||||
run: |
|
||||
set -e
|
||||
sed -i "s|version = \"${CURRENT_API_VERSION}\"|version = \"${NEXT_API_PATCH_VERSION}\"|" api/pyproject.toml
|
||||
sed -i "s| version: ${CURRENT_API_VERSION}| version: ${NEXT_API_PATCH_VERSION}|" api/src/backend/api/specs/v1.yaml
|
||||
|
||||
- name: Regenerate lockfiles after version bump
|
||||
run: |
|
||||
set -e
|
||||
# The bumps above edit pyproject.toml / api/pyproject.toml but leave
|
||||
# uv.lock / api/uv.lock stale, which makes `uv sync --locked` fail in
|
||||
# the container builds. Refresh both with the uv version the images
|
||||
# pin (plain `uv lock`, no --upgrade: only the version line changes).
|
||||
pip install --no-cache-dir "uv==0.11.14"
|
||||
uv lock
|
||||
(cd api && uv lock)
|
||||
|
||||
- name: Bump UI version (.env)
|
||||
run: |
|
||||
set -e
|
||||
sed -i "s|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=.*|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v${NEXT_PATCH_VERSION}|" .env
|
||||
|
||||
- name: Show consolidated diff
|
||||
run: git --no-pager diff
|
||||
|
||||
- name: Create PR for next patch versions to version branch
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: ${{ env.VERSION_BRANCH }}
|
||||
commit-message: 'chore(release): Bump versions to v${{ env.NEXT_PATCH_VERSION }}'
|
||||
branch: release-version-bump-to-v${{ env.NEXT_PATCH_VERSION }}
|
||||
title: 'chore(release): Bump versions to v${{ env.NEXT_PATCH_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler versions on `${{ env.VERSION_BRANCH }}` after releasing Prowler v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
| Area | File(s) | New version |
|
||||
| --- | --- | --- |
|
||||
| SDK | `pyproject.toml`, `prowler/config/config.py` | v${{ env.NEXT_PATCH_VERSION }} |
|
||||
| API | `api/pyproject.toml`, `api/src/backend/api/specs/v1.yaml` | v${{ env.NEXT_API_PATCH_VERSION }} |
|
||||
| UI | `.env` (`NEXT_PUBLIC_PROWLER_RELEASE_VERSION`) | v${{ env.NEXT_PATCH_VERSION }} |
|
||||
| Docs | (not touched on version branches) | — |
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -51,6 +51,6 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
uses: zizmorcore/zizmor-action@a16621b09c6db4281f81a93cb393b05dcd7b7165 # v0.5.5
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
name: 'Docs: Bump Version'
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- 'published'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.release.tag_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
PROWLER_VERSION: ${{ github.event.release.tag_name }}
|
||||
BASE_BRANCH: master
|
||||
DOCS_FILE: docs/getting-started/installation/prowler-app.mdx
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
bump-version:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Validate release version
|
||||
run: |
|
||||
if [[ ! $PROWLER_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
echo "::error::Invalid version syntax: '$PROWLER_VERSION' (must be X.Y.Z)"
|
||||
exit 1
|
||||
fi
|
||||
if (( ${BASH_REMATCH[1]} != 5 )); then
|
||||
echo "::error::Releasing another Prowler major version, aborting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout master branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ env.BASE_BRANCH }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Read current docs version on master
|
||||
id: docs_version
|
||||
run: |
|
||||
CURRENT_DOCS_VERSION=$(grep -oP 'PROWLER_UI_VERSION="\K[^"]+' "${DOCS_FILE}")
|
||||
echo "CURRENT_DOCS_VERSION=${CURRENT_DOCS_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "Current docs version on master: $CURRENT_DOCS_VERSION"
|
||||
echo "Target release version: $PROWLER_VERSION"
|
||||
|
||||
# Skip if master is already at or ahead of the release version
|
||||
# (re-run, or patch shipped against an older minor line)
|
||||
HIGHEST=$(printf '%s\n%s\n' "${CURRENT_DOCS_VERSION}" "${PROWLER_VERSION}" | sort -V | tail -n1)
|
||||
if [[ "${CURRENT_DOCS_VERSION}" == "${PROWLER_VERSION}" || "${HIGHEST}" != "${PROWLER_VERSION}" ]]; then
|
||||
echo "skip=true" >> "${GITHUB_OUTPUT}"
|
||||
echo "Skipping bump: current ($CURRENT_DOCS_VERSION) >= release ($PROWLER_VERSION)"
|
||||
else
|
||||
echo "skip=false" >> "${GITHUB_OUTPUT}"
|
||||
fi
|
||||
|
||||
- name: Bump versions in documentation
|
||||
if: steps.docs_version.outputs.skip == 'false'
|
||||
run: |
|
||||
set -e
|
||||
sed -i "s|PROWLER_UI_VERSION=\"${CURRENT_DOCS_VERSION}\"|PROWLER_UI_VERSION=\"${PROWLER_VERSION}\"|" "${DOCS_FILE}"
|
||||
sed -i "s|PROWLER_API_VERSION=\"${CURRENT_DOCS_VERSION}\"|PROWLER_API_VERSION=\"${PROWLER_VERSION}\"|" "${DOCS_FILE}"
|
||||
echo "Files modified:"
|
||||
git --no-pager diff
|
||||
|
||||
- name: Create PR for documentation update to master
|
||||
if: steps.docs_version.outputs.skip == 'false'
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: ${{ env.BASE_BRANCH }}
|
||||
commit-message: 'chore(docs): Bump version to v${{ env.PROWLER_VERSION }}'
|
||||
branch: docs-version-bump-to-v${{ env.PROWLER_VERSION }}
|
||||
title: 'chore(docs): Bump version to v${{ env.PROWLER_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Update Prowler documentation version references to v${{ env.PROWLER_VERSION }} after releasing Prowler v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
### Files Updated
|
||||
- `docs/getting-started/installation/prowler-app.mdx`: `PROWLER_UI_VERSION` and `PROWLER_API_VERSION`
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
# We can't block as Trufflehog needs to verify secrets against vendors
|
||||
egress-policy: audit
|
||||
@@ -44,6 +44,6 @@ jobs:
|
||||
|
||||
- name: Scan diff for secrets with TruffleHog
|
||||
# Action auto-injects --since-commit/--branch from event payload; passing them in extra_args produces duplicate flags.
|
||||
uses: trufflesecurity/trufflehog@ef6e76c3c4023279497fab4721ffa071a722fd05 # v3.92.4
|
||||
uses: trufflesecurity/trufflehog@37b77001d0174ebec2fcca2bd83ff83a6d45a3ab # v3.95.3
|
||||
with:
|
||||
extra_args: --results=verified,unknown
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
|
||||
Generated
+12
-12
@@ -66,12 +66,12 @@ jobs:
|
||||
title: ${{ steps.compute-text.outputs.title }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Setup Scripts
|
||||
uses: github/gh-aw/actions/setup@9382be3ca9ac18917e111a99d4e6bbff58d0dccc # v0.43.23
|
||||
uses: github/gh-aw/actions/setup@4d44d0e89851a877f4ddc0cb6c0197e42b1016c5 # v0.73.0
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Check workflow file timestamps
|
||||
@@ -135,12 +135,12 @@ jobs:
|
||||
secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Setup Scripts
|
||||
uses: github/gh-aw/actions/setup@9382be3ca9ac18917e111a99d4e6bbff58d0dccc # v0.43.23
|
||||
uses: github/gh-aw/actions/setup@4d44d0e89851a877f4ddc0cb6c0197e42b1016c5 # v0.73.0
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Checkout repository
|
||||
@@ -870,12 +870,12 @@ jobs:
|
||||
total_count: ${{ steps.missing_tool.outputs.total_count }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Setup Scripts
|
||||
uses: github/gh-aw/actions/setup@9382be3ca9ac18917e111a99d4e6bbff58d0dccc # v0.43.23
|
||||
uses: github/gh-aw/actions/setup@4d44d0e89851a877f4ddc0cb6c0197e42b1016c5 # v0.73.0
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Download agent output artifact
|
||||
@@ -982,12 +982,12 @@ jobs:
|
||||
success: ${{ steps.parse_results.outputs.success }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Setup Scripts
|
||||
uses: github/gh-aw/actions/setup@9382be3ca9ac18917e111a99d4e6bbff58d0dccc # v0.43.23
|
||||
uses: github/gh-aw/actions/setup@4d44d0e89851a877f4ddc0cb6c0197e42b1016c5 # v0.73.0
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Download agent artifacts
|
||||
@@ -1091,12 +1091,12 @@ jobs:
|
||||
activated: ${{ (steps.check_membership.outputs.is_team_member == 'true') && (steps.check_rate_limit.outputs.rate_limit_ok == 'true') }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Setup Scripts
|
||||
uses: github/gh-aw/actions/setup@9382be3ca9ac18917e111a99d4e6bbff58d0dccc # v0.43.23
|
||||
uses: github/gh-aw/actions/setup@4d44d0e89851a877f4ddc0cb6c0197e42b1016c5 # v0.73.0
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Add eyes reaction for immediate feedback
|
||||
@@ -1164,12 +1164,12 @@ jobs:
|
||||
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Setup Scripts
|
||||
uses: github/gh-aw/actions/setup@9382be3ca9ac18917e111a99d4e6bbff58d0dccc # v0.43.23
|
||||
uses: github/gh-aw/actions/setup@4d44d0e89851a877f4ddc0cb6c0197e42b1016c5 # v0.73.0
|
||||
with:
|
||||
destination: /opt/gh-aw/actions
|
||||
- name: Download agent output artifact
|
||||
|
||||
@@ -27,12 +27,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Apply labels to PR
|
||||
uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||
uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -114,6 +114,7 @@ jobs:
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
ghcr.io:443
|
||||
pkg-containers.githubusercontent.com:443
|
||||
files.pythonhosted.org:443
|
||||
@@ -125,7 +126,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -136,7 +137,7 @@ jobs:
|
||||
- name: Build and push MCP container for ${{ matrix.arch }}
|
||||
id: container-push
|
||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
push: true
|
||||
@@ -164,18 +165,19 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
github.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -225,7 +227,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -272,7 +274,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Check if Dockerfile changed
|
||||
id: dockerfile-changed
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: mcp_server/Dockerfile
|
||||
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -79,6 +79,7 @@ jobs:
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
ghcr.io:443
|
||||
pkg-containers.githubusercontent.com:443
|
||||
files.pythonhosted.org:443
|
||||
@@ -98,7 +99,7 @@ jobs:
|
||||
|
||||
- name: Check for MCP changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: mcp_server/**
|
||||
files_ignore: |
|
||||
@@ -111,7 +112,7 @@ jobs:
|
||||
|
||||
- name: Build MCP container
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.MCP_WORKING_DIR }}
|
||||
push: false
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -88,7 +88,8 @@ jobs:
|
||||
|
||||
# The MCP server version (mcp_server/pyproject.toml) is decoupled from the Prowler release
|
||||
# version: it only changes when MCP code changes. mcp-bump-version.yml normally keeps it in
|
||||
# sync with mcp_server/CHANGELOG.md, but this publish workflow still runs on every release.
|
||||
# sync with mcp_server/CHANGELOG.md (separate from the release bump-version.yml), but this
|
||||
# publish workflow still runs on every release.
|
||||
# Pre-flight PyPI check covers the legitimate "no MCP changes for this release" case (and any
|
||||
# workflow_dispatch re-runs) without failing with HTTP 400 (version exists).
|
||||
- name: Check if prowler-mcp version already exists on PyPI
|
||||
@@ -112,7 +113,7 @@ jobs:
|
||||
|
||||
- name: Publish prowler-mcp package to PyPI
|
||||
if: steps.pypi-check.outputs.skip != 'true'
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
with:
|
||||
packages-dir: ${{ env.WORKING_DIRECTORY }}/dist/
|
||||
print-hash: true
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
name: 'MCP: Security'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v5.*'
|
||||
paths:
|
||||
- 'mcp_server/pyproject.toml'
|
||||
- 'mcp_server/uv.lock'
|
||||
- '.github/workflows/mcp-security.yml'
|
||||
- '.github/actions/osv-scanner/**'
|
||||
- '.github/scripts/osv-scan.sh'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v5.*'
|
||||
paths:
|
||||
- 'mcp_server/pyproject.toml'
|
||||
- 'mcp_server/uv.lock'
|
||||
- '.github/workflows/mcp-security.yml'
|
||||
- '.github/actions/osv-scanner/**'
|
||||
- '.github/scripts/osv-scan.sh'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
mcp-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@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
|
||||
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 MCP dependency changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
mcp_server/pyproject.toml
|
||||
mcp_server/uv.lock
|
||||
.github/workflows/mcp-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: mcp_server/uv.lock
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build ${{ matrix.component }} container (linux/arm64)
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ matrix.context }}
|
||||
file: ${{ matrix.dockerfile }}
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
api/**
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
prowler/providers/**/services/**/*.metadata.json
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: '**'
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Parse version and determine branch
|
||||
run: |
|
||||
# Validate version format (reusing pattern from sdk-bump-version.yml)
|
||||
# Validate version format (reusing pattern from bump-version.yml)
|
||||
if [[ $PROWLER_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
MAJOR_VERSION=${BASH_REMATCH[1]}
|
||||
MINOR_VERSION=${BASH_REMATCH[2]}
|
||||
@@ -299,17 +299,6 @@ jobs:
|
||||
fi
|
||||
echo "✓ api/pyproject.toml prowler dependency: $CURRENT_PROWLER_REF"
|
||||
|
||||
- name: Verify API version in api/src/backend/api/v1/views.py
|
||||
if: ${{ env.HAS_API_CHANGES == 'true' }}
|
||||
run: |
|
||||
CURRENT_API_VERSION=$(grep 'spectacular_settings.VERSION = ' api/src/backend/api/v1/views.py | sed -E 's/.*spectacular_settings.VERSION = "([^"]+)".*/\1/' | tr -d '[:space:]')
|
||||
API_VERSION_TRIMMED=$(echo "$API_VERSION" | tr -d '[:space:]')
|
||||
if [ "$CURRENT_API_VERSION" != "$API_VERSION_TRIMMED" ]; then
|
||||
echo "ERROR: API version mismatch in views.py (expected: '$API_VERSION_TRIMMED', found: '$CURRENT_API_VERSION')"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ api/src/backend/api/v1/views.py version: $CURRENT_API_VERSION"
|
||||
|
||||
- name: Verify API version in api/src/backend/api/specs/v1.yaml
|
||||
if: ${{ env.HAS_API_CHANGES == 'true' }}
|
||||
run: |
|
||||
@@ -349,7 +338,7 @@ jobs:
|
||||
|
||||
- name: Create PR for API dependency update
|
||||
if: ${{ env.PATCH_VERSION == '0' }}
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
commit-message: 'chore(api): update prowler dependency to ${{ env.BRANCH_NAME }} for release ${{ env.PROWLER_VERSION }}'
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
name: 'CI: Renovate Config Validate'
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
paths:
|
||||
- '.github/renovate.json'
|
||||
- '.pre-commit-config.yaml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
# renovate: datasource=pypi depName=prek
|
||||
PREK_VERSION: '0.4.0'
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate Renovate config
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.github.com:443
|
||||
github.com:443
|
||||
objects.githubusercontent.com:443
|
||||
codeload.github.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
registry.npmjs.org:443
|
||||
nodejs.org:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
|
||||
|
||||
- name: Install prek
|
||||
run: uv tool install "prek==${PREK_VERSION}"
|
||||
|
||||
- name: Validate Renovate config
|
||||
run: prek run renovate-config-validator --files .github/renovate.json
|
||||
@@ -1,247 +0,0 @@
|
||||
name: 'SDK: Bump Version'
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- 'published'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.release.tag_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
PROWLER_VERSION: ${{ github.event.release.tag_name }}
|
||||
BASE_BRANCH: master
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
detect-release-type:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
is_minor: ${{ steps.detect.outputs.is_minor }}
|
||||
is_patch: ${{ steps.detect.outputs.is_patch }}
|
||||
major_version: ${{ steps.detect.outputs.major_version }}
|
||||
minor_version: ${{ steps.detect.outputs.minor_version }}
|
||||
patch_version: ${{ steps.detect.outputs.patch_version }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Detect release type and parse version
|
||||
id: detect
|
||||
run: |
|
||||
if [[ $PROWLER_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
MAJOR_VERSION=${BASH_REMATCH[1]}
|
||||
MINOR_VERSION=${BASH_REMATCH[2]}
|
||||
PATCH_VERSION=${BASH_REMATCH[3]}
|
||||
|
||||
echo "major_version=${MAJOR_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
echo "minor_version=${MINOR_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
echo "patch_version=${PATCH_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
if (( MAJOR_VERSION != 5 )); then
|
||||
echo "::error::Releasing another Prowler major version, aborting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (( PATCH_VERSION == 0 )); then
|
||||
echo "is_minor=true" >> "${GITHUB_OUTPUT}"
|
||||
echo "is_patch=false" >> "${GITHUB_OUTPUT}"
|
||||
echo "✓ Minor release detected: $PROWLER_VERSION"
|
||||
else
|
||||
echo "is_minor=false" >> "${GITHUB_OUTPUT}"
|
||||
echo "is_patch=true" >> "${GITHUB_OUTPUT}"
|
||||
echo "✓ Patch release detected: $PROWLER_VERSION"
|
||||
fi
|
||||
else
|
||||
echo "::error::Invalid version syntax: '$PROWLER_VERSION' (must be X.Y.Z)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bump-minor-version:
|
||||
needs: detect-release-type
|
||||
if: needs.detect-release-type.outputs.is_minor == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Calculate next minor version
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
|
||||
NEXT_MINOR_VERSION=${MAJOR_VERSION}.$((MINOR_VERSION + 1)).0
|
||||
echo "NEXT_MINOR_VERSION=${NEXT_MINOR_VERSION}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "Current version: $PROWLER_VERSION"
|
||||
echo "Next minor version: $NEXT_MINOR_VERSION"
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
|
||||
- name: Bump versions in files for master
|
||||
run: |
|
||||
set -e
|
||||
|
||||
sed -i "s|version = \"${PROWLER_VERSION}\"|version = \"${NEXT_MINOR_VERSION}\"|" pyproject.toml
|
||||
sed -i "s|prowler_version = \"${PROWLER_VERSION}\"|prowler_version = \"${NEXT_MINOR_VERSION}\"|" prowler/config/config.py
|
||||
|
||||
echo "Files modified:"
|
||||
git --no-pager diff
|
||||
|
||||
- name: Create PR for next minor version to master
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: master
|
||||
commit-message: 'chore(sdk): Bump version to v${{ env.NEXT_MINOR_VERSION }}'
|
||||
branch: sdk-version-bump-to-v${{ env.NEXT_MINOR_VERSION }}
|
||||
title: 'chore(sdk): Bump version to v${{ env.NEXT_MINOR_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler version to v${{ env.NEXT_MINOR_VERSION }} after releasing v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
|
||||
- name: Checkout version branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: v${{ needs.detect-release-type.outputs.major_version }}.${{ needs.detect-release-type.outputs.minor_version }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Calculate first patch version
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
|
||||
FIRST_PATCH_VERSION=${MAJOR_VERSION}.${MINOR_VERSION}.1
|
||||
VERSION_BRANCH=v${MAJOR_VERSION}.${MINOR_VERSION}
|
||||
|
||||
echo "FIRST_PATCH_VERSION=${FIRST_PATCH_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "VERSION_BRANCH=${VERSION_BRANCH}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "First patch version: $FIRST_PATCH_VERSION"
|
||||
echo "Version branch: $VERSION_BRANCH"
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
|
||||
- name: Bump versions in files for version branch
|
||||
run: |
|
||||
set -e
|
||||
|
||||
sed -i "s|version = \"${PROWLER_VERSION}\"|version = \"${FIRST_PATCH_VERSION}\"|" pyproject.toml
|
||||
sed -i "s|prowler_version = \"${PROWLER_VERSION}\"|prowler_version = \"${FIRST_PATCH_VERSION}\"|" prowler/config/config.py
|
||||
|
||||
echo "Files modified:"
|
||||
git --no-pager diff
|
||||
|
||||
- name: Create PR for first patch version to version branch
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: ${{ env.VERSION_BRANCH }}
|
||||
commit-message: 'chore(sdk): Bump version to v${{ env.FIRST_PATCH_VERSION }}'
|
||||
branch: sdk-version-bump-to-v${{ env.FIRST_PATCH_VERSION }}
|
||||
title: 'chore(sdk): Bump version to v${{ env.FIRST_PATCH_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler version to v${{ env.FIRST_PATCH_VERSION }} in version branch after releasing v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
|
||||
bump-patch-version:
|
||||
needs: detect-release-type
|
||||
if: needs.detect-release-type.outputs.is_patch == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Calculate next patch version
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
PATCH_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_PATCH_VERSION}
|
||||
|
||||
NEXT_PATCH_VERSION=${MAJOR_VERSION}.${MINOR_VERSION}.$((PATCH_VERSION + 1))
|
||||
VERSION_BRANCH=v${MAJOR_VERSION}.${MINOR_VERSION}
|
||||
|
||||
echo "NEXT_PATCH_VERSION=${NEXT_PATCH_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "VERSION_BRANCH=${VERSION_BRANCH}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "Current version: $PROWLER_VERSION"
|
||||
echo "Next patch version: $NEXT_PATCH_VERSION"
|
||||
echo "Target branch: $VERSION_BRANCH"
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_PATCH_VERSION: ${{ needs.detect-release-type.outputs.patch_version }}
|
||||
|
||||
- name: Bump versions in files for version branch
|
||||
run: |
|
||||
set -e
|
||||
|
||||
sed -i "s|version = \"${PROWLER_VERSION}\"|version = \"${NEXT_PATCH_VERSION}\"|" pyproject.toml
|
||||
sed -i "s|prowler_version = \"${PROWLER_VERSION}\"|prowler_version = \"${NEXT_PATCH_VERSION}\"|" prowler/config/config.py
|
||||
|
||||
echo "Files modified:"
|
||||
git --no-pager diff
|
||||
|
||||
- name: Create PR for next patch version to version branch
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: ${{ env.VERSION_BRANCH }}
|
||||
commit-message: 'chore(sdk): Bump version to v${{ env.NEXT_PATCH_VERSION }}'
|
||||
branch: sdk-version-bump-to-v${{ env.NEXT_PATCH_VERSION }}
|
||||
title: 'chore(sdk): Bump version to v${{ env.NEXT_PATCH_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler version to v${{ env.NEXT_PATCH_VERSION }} after releasing v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
- name: Check for SDK changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: ./**
|
||||
files_ignore: |
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -66,12 +66,12 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ./.github/codeql/sdk-codeql-config.yml
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
category: '/language:${{ matrix.language }}'
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -141,7 +141,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -149,6 +149,7 @@ jobs:
|
||||
public.ecr.aws:443
|
||||
registry-1.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
auth.docker.io:443
|
||||
debian.map.fastlydns.net:80
|
||||
github.com:443
|
||||
@@ -167,13 +168,13 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to Public ECR
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: public.ecr.aws
|
||||
username: ${{ secrets.PUBLIC_ECR_AWS_ACCESS_KEY_ID }}
|
||||
@@ -187,7 +188,7 @@ jobs:
|
||||
- name: Build and push SDK container for ${{ matrix.arch }}
|
||||
id: container-push
|
||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
file: ${{ env.DOCKERFILE_PATH }}
|
||||
@@ -208,7 +209,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -216,19 +217,20 @@ jobs:
|
||||
auth.docker.io:443
|
||||
public.ecr.aws:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
github.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
api.ecr-public.us-east-1.amazonaws.com:443
|
||||
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to Public ECR
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: public.ecr.aws
|
||||
username: ${{ secrets.PUBLIC_ECR_AWS_ACCESS_KEY_ID }}
|
||||
@@ -265,7 +267,7 @@ jobs:
|
||||
# Push to toniblyx/prowler only for current version (latest/stable/release tags)
|
||||
- name: Login to DockerHub (toniblyx)
|
||||
if: needs.setup.outputs.latest_tag == 'latest'
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.TONIBLYX_DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.TONIBLYX_DOCKERHUB_PASSWORD }}
|
||||
@@ -290,7 +292,7 @@ jobs:
|
||||
# Re-login as prowlercloud for cleanup of intermediate tags
|
||||
- name: Login to DockerHub (prowlercloud)
|
||||
if: always()
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -318,7 +320,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
- name: Check if Dockerfile changed
|
||||
id: dockerfile-changed
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: Dockerfile
|
||||
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -85,6 +85,7 @@ jobs:
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
api.github.com:443
|
||||
mirror.gcr.io:443
|
||||
check.trivy.dev:443
|
||||
@@ -108,7 +109,7 @@ jobs:
|
||||
|
||||
- name: Check for SDK changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: ./**
|
||||
files_ignore: |
|
||||
@@ -137,7 +138,7 @@ jobs:
|
||||
|
||||
- name: Build SDK container
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
run: uv build
|
||||
|
||||
- name: Publish Prowler package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
with:
|
||||
print-hash: true
|
||||
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -129,6 +129,6 @@ jobs:
|
||||
run: uv build
|
||||
|
||||
- name: Publish prowler-cloud package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
with:
|
||||
print-hash: true
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
run: pip install boto3
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0
|
||||
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
|
||||
with:
|
||||
aws-region: ${{ env.AWS_REGION }}
|
||||
role-to-assume: ${{ secrets.DEV_IAM_ROLE_ARN }}
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Create pull request
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
author: 'prowler-bot <179230569+prowler-bot@users.noreply.github.com>'
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
- name: Create pull request
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
author: 'prowler-bot <179230569+prowler-bot@users.noreply.github.com>'
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Check for SDK changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files:
|
||||
./**
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -46,6 +46,7 @@ jobs:
|
||||
schema.ocsf.io:443
|
||||
registry-1.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
||||
o26192.ingest.us.sentry.io:443
|
||||
management.azure.com:443
|
||||
@@ -69,7 +70,7 @@ jobs:
|
||||
|
||||
- name: Check for SDK changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: ./**
|
||||
files_ignore: |
|
||||
@@ -102,7 +103,7 @@ jobs:
|
||||
- name: Check if AWS files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-aws
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/aws/**
|
||||
@@ -232,7 +233,7 @@ jobs:
|
||||
- name: Check if Azure files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-azure
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/azure/**
|
||||
@@ -256,7 +257,7 @@ jobs:
|
||||
- name: Check if GCP files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-gcp
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/gcp/**
|
||||
@@ -280,7 +281,7 @@ jobs:
|
||||
- name: Check if Kubernetes files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-kubernetes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/kubernetes/**
|
||||
@@ -304,7 +305,7 @@ jobs:
|
||||
- name: Check if GitHub files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-github
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/github/**
|
||||
@@ -328,7 +329,7 @@ jobs:
|
||||
- name: Check if Okta files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-okta
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/okta/**
|
||||
@@ -352,7 +353,7 @@ jobs:
|
||||
- name: Check if NHN files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-nhn
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/nhn/**
|
||||
@@ -376,7 +377,7 @@ jobs:
|
||||
- name: Check if M365 files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-m365
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/m365/**
|
||||
@@ -400,7 +401,7 @@ jobs:
|
||||
- name: Check if IaC files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-iac
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/iac/**
|
||||
@@ -424,7 +425,7 @@ jobs:
|
||||
- name: Check if MongoDB Atlas files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-mongodbatlas
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/mongodbatlas/**
|
||||
@@ -448,7 +449,7 @@ jobs:
|
||||
- name: Check if OCI files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-oraclecloud
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/oraclecloud/**
|
||||
@@ -472,7 +473,7 @@ jobs:
|
||||
- name: Check if OpenStack files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-openstack
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/openstack/**
|
||||
@@ -496,7 +497,7 @@ jobs:
|
||||
- name: Check if Google Workspace files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-googleworkspace
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/googleworkspace/**
|
||||
@@ -520,7 +521,7 @@ jobs:
|
||||
- name: Check if Vercel files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-vercel
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/**/vercel/**
|
||||
@@ -544,7 +545,7 @@ jobs:
|
||||
- name: Check if Lib files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-lib
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/lib/**
|
||||
@@ -568,7 +569,7 @@ jobs:
|
||||
- name: Check if Config files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-config
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
./prowler/config/**
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
name: 'UI: Bump Version'
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- 'published'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.release.tag_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
PROWLER_VERSION: ${{ github.event.release.tag_name }}
|
||||
BASE_BRANCH: master
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
detect-release-type:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
is_minor: ${{ steps.detect.outputs.is_minor }}
|
||||
is_patch: ${{ steps.detect.outputs.is_patch }}
|
||||
major_version: ${{ steps.detect.outputs.major_version }}
|
||||
minor_version: ${{ steps.detect.outputs.minor_version }}
|
||||
patch_version: ${{ steps.detect.outputs.patch_version }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Detect release type and parse version
|
||||
id: detect
|
||||
run: |
|
||||
if [[ $PROWLER_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
MAJOR_VERSION=${BASH_REMATCH[1]}
|
||||
MINOR_VERSION=${BASH_REMATCH[2]}
|
||||
PATCH_VERSION=${BASH_REMATCH[3]}
|
||||
|
||||
echo "major_version=${MAJOR_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
echo "minor_version=${MINOR_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
echo "patch_version=${PATCH_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
if (( MAJOR_VERSION != 5 )); then
|
||||
echo "::error::Releasing another Prowler major version, aborting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (( PATCH_VERSION == 0 )); then
|
||||
echo "is_minor=true" >> "${GITHUB_OUTPUT}"
|
||||
echo "is_patch=false" >> "${GITHUB_OUTPUT}"
|
||||
echo "✓ Minor release detected: $PROWLER_VERSION"
|
||||
else
|
||||
echo "is_minor=false" >> "${GITHUB_OUTPUT}"
|
||||
echo "is_patch=true" >> "${GITHUB_OUTPUT}"
|
||||
echo "✓ Patch release detected: $PROWLER_VERSION"
|
||||
fi
|
||||
else
|
||||
echo "::error::Invalid version syntax: '$PROWLER_VERSION' (must be X.Y.Z)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bump-minor-version:
|
||||
needs: detect-release-type
|
||||
if: needs.detect-release-type.outputs.is_minor == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Calculate next minor version
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
|
||||
NEXT_MINOR_VERSION=${MAJOR_VERSION}.$((MINOR_VERSION + 1)).0
|
||||
echo "NEXT_MINOR_VERSION=${NEXT_MINOR_VERSION}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "Current version: $PROWLER_VERSION"
|
||||
echo "Next minor version: $NEXT_MINOR_VERSION"
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
|
||||
- name: Bump UI version in .env for master
|
||||
run: |
|
||||
set -e
|
||||
|
||||
sed -i "s|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=.*|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v${NEXT_MINOR_VERSION}|" .env
|
||||
|
||||
echo "Files modified:"
|
||||
git --no-pager diff
|
||||
|
||||
- name: Create PR for next minor version to master
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: master
|
||||
commit-message: 'chore(ui): Bump version to v${{ env.NEXT_MINOR_VERSION }}'
|
||||
branch: ui-version-bump-to-v${{ env.NEXT_MINOR_VERSION }}
|
||||
title: 'chore(ui): Bump version to v${{ env.NEXT_MINOR_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler UI version to v${{ env.NEXT_MINOR_VERSION }} after releasing Prowler v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
### Files Updated
|
||||
- `.env`: `NEXT_PUBLIC_PROWLER_RELEASE_VERSION`
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
|
||||
- name: Checkout version branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: v${{ needs.detect-release-type.outputs.major_version }}.${{ needs.detect-release-type.outputs.minor_version }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Calculate first patch version
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
|
||||
FIRST_PATCH_VERSION=${MAJOR_VERSION}.${MINOR_VERSION}.1
|
||||
VERSION_BRANCH=v${MAJOR_VERSION}.${MINOR_VERSION}
|
||||
|
||||
echo "FIRST_PATCH_VERSION=${FIRST_PATCH_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "VERSION_BRANCH=${VERSION_BRANCH}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "First patch version: $FIRST_PATCH_VERSION"
|
||||
echo "Version branch: $VERSION_BRANCH"
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
|
||||
- name: Bump UI version in .env for version branch
|
||||
run: |
|
||||
set -e
|
||||
|
||||
sed -i "s|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=.*|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v${FIRST_PATCH_VERSION}|" .env
|
||||
|
||||
echo "Files modified:"
|
||||
git --no-pager diff
|
||||
|
||||
- name: Create PR for first patch version to version branch
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: ${{ env.VERSION_BRANCH }}
|
||||
commit-message: 'chore(ui): Bump version to v${{ env.FIRST_PATCH_VERSION }}'
|
||||
branch: ui-version-bump-to-v${{ env.FIRST_PATCH_VERSION }}
|
||||
title: 'chore(ui): Bump version to v${{ env.FIRST_PATCH_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler UI version to v${{ env.FIRST_PATCH_VERSION }} in version branch after releasing Prowler v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
### Files Updated
|
||||
- `.env`: `NEXT_PUBLIC_PROWLER_RELEASE_VERSION`
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
|
||||
bump-patch-version:
|
||||
needs: detect-release-type
|
||||
if: needs.detect-release-type.outputs.is_patch == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Calculate next patch version
|
||||
run: |
|
||||
MAJOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION}
|
||||
MINOR_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION}
|
||||
PATCH_VERSION=${NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_PATCH_VERSION}
|
||||
|
||||
NEXT_PATCH_VERSION=${MAJOR_VERSION}.${MINOR_VERSION}.$((PATCH_VERSION + 1))
|
||||
VERSION_BRANCH=v${MAJOR_VERSION}.${MINOR_VERSION}
|
||||
|
||||
echo "NEXT_PATCH_VERSION=${NEXT_PATCH_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "VERSION_BRANCH=${VERSION_BRANCH}" >> "${GITHUB_ENV}"
|
||||
|
||||
echo "Current version: $PROWLER_VERSION"
|
||||
echo "Next patch version: $NEXT_PATCH_VERSION"
|
||||
echo "Target branch: $VERSION_BRANCH"
|
||||
env:
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MAJOR_VERSION: ${{ needs.detect-release-type.outputs.major_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_MINOR_VERSION: ${{ needs.detect-release-type.outputs.minor_version }}
|
||||
NEEDS_DETECT_RELEASE_TYPE_OUTPUTS_PATCH_VERSION: ${{ needs.detect-release-type.outputs.patch_version }}
|
||||
|
||||
- name: Bump UI version in .env for version branch
|
||||
run: |
|
||||
set -e
|
||||
|
||||
sed -i "s|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=.*|NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v${NEXT_PATCH_VERSION}|" .env
|
||||
|
||||
echo "Files modified:"
|
||||
git --no-pager diff
|
||||
|
||||
- name: Create PR for next patch version to version branch
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
base: ${{ env.VERSION_BRANCH }}
|
||||
commit-message: 'chore(ui): Bump version to v${{ env.NEXT_PATCH_VERSION }}'
|
||||
branch: ui-version-bump-to-v${{ env.NEXT_PATCH_VERSION }}
|
||||
title: 'chore(ui): Bump version to v${{ env.NEXT_PATCH_VERSION }}'
|
||||
labels: no-changelog,skip-sync
|
||||
body: |
|
||||
### Description
|
||||
|
||||
Bump Prowler UI version to v${{ env.NEXT_PATCH_VERSION }} after releasing Prowler v${{ env.PROWLER_VERSION }}.
|
||||
|
||||
### Files Updated
|
||||
- `.env`: `NEXT_PUBLIC_PROWLER_RELEASE_VERSION`
|
||||
|
||||
### License
|
||||
|
||||
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -62,12 +62,12 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ./.github/codeql/ui-codeql-config.yml
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
category: '/language:${{ matrix.language }}'
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -110,12 +110,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
registry-1.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
auth.docker.io:443
|
||||
registry.npmjs.org:443
|
||||
dl-cdn.alpinelinux.org:443
|
||||
@@ -129,7 +130,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -140,7 +141,7 @@ jobs:
|
||||
- name: Build and push UI container for ${{ matrix.arch }}
|
||||
id: container-push
|
||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
build-args: |
|
||||
@@ -163,7 +164,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -172,9 +173,10 @@ jobs:
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -224,7 +226,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -271,7 +273,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Check if Dockerfile changed
|
||||
id: dockerfile-changed
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: ui/Dockerfile
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -80,6 +80,7 @@ jobs:
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
registry.npmjs.org:443
|
||||
dl-cdn.alpinelinux.org:443
|
||||
fonts.googleapis.com:443
|
||||
@@ -99,7 +100,7 @@ jobs:
|
||||
|
||||
- name: Check for UI changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: ui/**
|
||||
files_ignore: |
|
||||
@@ -113,7 +114,7 @@ jobs:
|
||||
|
||||
- name: Build UI container
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.UI_WORKING_DIR }}
|
||||
target: prod
|
||||
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -184,7 +184,7 @@ jobs:
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm and Next.js cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
${{ env.STORE_PATH }}
|
||||
@@ -204,7 +204,7 @@ jobs:
|
||||
run: pnpm run build
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
@@ -295,7 +295,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
- name: Check for UI dependency changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
ui/package.json
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
|
||||
- name: Check for UI changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
ui/**
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
- name: Get changed source files for targeted tests
|
||||
id: changed-source
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
ui/**/*.ts
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
- name: Check for critical path changes (run all tests)
|
||||
id: critical-changes
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
ui/lib/**
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
|
||||
- name: Setup pnpm and Next.js cache
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
${{ env.STORE_PATH }}
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
- name: Cache Playwright browsers
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: playwright-cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-chromium-${{ hashFiles('ui/pnpm-lock.yaml') }}
|
||||
|
||||
+1
-4
@@ -1,22 +1,19 @@
|
||||
rules:
|
||||
secrets-outside-env:
|
||||
ignore:
|
||||
- api-bump-version.yml
|
||||
- api-container-build-push.yml
|
||||
- api-tests.yml
|
||||
- backport.yml
|
||||
- docs-bump-version.yml
|
||||
- bump-version.yml
|
||||
- issue-triage.lock.yml
|
||||
- mcp-container-build-push.yml
|
||||
- nightly-arm64-container-builds.yml
|
||||
- pr-merged.yml
|
||||
- prepare-release.yml
|
||||
- sdk-bump-version.yml
|
||||
- sdk-container-build-push.yml
|
||||
- sdk-refresh-aws-services-regions.yml
|
||||
- sdk-refresh-oci-regions.yml
|
||||
- sdk-tests.yml
|
||||
- ui-bump-version.yml
|
||||
- ui-container-build-push.yml
|
||||
- ui-e2e-tests-v2.yml
|
||||
superfluous-actions:
|
||||
|
||||
@@ -60,7 +60,6 @@ htmlcov/
|
||||
**/mcp-config.json
|
||||
**/mcpServers.json
|
||||
.mcp/
|
||||
.mcp.json
|
||||
|
||||
# AI Coding Assistants - Cursor
|
||||
.cursorignore
|
||||
|
||||
@@ -49,6 +49,14 @@ repos:
|
||||
files: ^\.github/(workflows|actions)/.+\.ya?ml$|^\.github/dependabot\.ya?ml$
|
||||
priority: 30
|
||||
|
||||
## RENOVATE
|
||||
- repo: https://github.com/renovatebot/pre-commit-hooks
|
||||
rev: 43.150.0
|
||||
hooks:
|
||||
- id: renovate-config-validator
|
||||
files: ^\.github/renovate\.json$
|
||||
priority: 10
|
||||
|
||||
## BASH
|
||||
- repo: https://github.com/koalaman/shellcheck-precommit
|
||||
rev: v0.11.0
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
.envrc
|
||||
ui/.env.local
|
||||
openspec/
|
||||
|
||||
+7
-7
@@ -76,11 +76,11 @@ USER prowler
|
||||
WORKDIR /home/prowler
|
||||
|
||||
# Copy necessary files
|
||||
COPY prowler/ /home/prowler/prowler/
|
||||
COPY dashboard/ /home/prowler/dashboard/
|
||||
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
|
||||
COPY --chown=prowler:prowler prowler/ /home/prowler/prowler/
|
||||
COPY --chown=prowler:prowler dashboard/ /home/prowler/dashboard/
|
||||
COPY --chown=prowler:prowler pyproject.toml uv.lock /home/prowler/
|
||||
COPY --chown=prowler:prowler README.md /home/prowler/
|
||||
COPY --chown=prowler:prowler prowler/providers/m365/lib/powershell/m365_powershell.py /home/prowler/prowler/providers/m365/lib/powershell/m365_powershell.py
|
||||
|
||||
# Install Python dependencies
|
||||
ENV HOME='/home/prowler'
|
||||
@@ -89,7 +89,7 @@ ENV PATH="${HOME}/.local/bin:${PATH}"
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir uv==0.11.14
|
||||
|
||||
RUN uv sync --compile-bytecode && \
|
||||
RUN uv sync --locked --compile-bytecode && \
|
||||
rm -rf ~/.cache/uv
|
||||
|
||||
# Install PowerShell modules
|
||||
@@ -100,4 +100,4 @@ RUN pip uninstall dash-html-components -y && \
|
||||
pip uninstall dash-core-components -y
|
||||
|
||||
USER prowler
|
||||
ENTRYPOINT [".venv/bin/prowler"]
|
||||
ENTRYPOINT ["/home/prowler/.venv/bin/prowler"]
|
||||
|
||||
@@ -104,23 +104,24 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
|
||||
|
||||
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/compliance/) | [Categories](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/misc/#categories) | Support | Interface |
|
||||
|---|---|---|---|---|---|---|
|
||||
| AWS | 595 | 84 | 43 | 17 | Official | UI, API, CLI |
|
||||
| AWS | 600 | 84 | 44 | 18 | Official | UI, API, CLI |
|
||||
| Azure | 167 | 22 | 19 | 16 | Official | UI, API, CLI |
|
||||
| GCP | 102 | 18 | 17 | 12 | Official | UI, API, CLI |
|
||||
| Kubernetes | 83 | 7 | 7 | 11 | Official | UI, API, CLI |
|
||||
| GitHub | 24 | 3 | 1 | 5 | Official | UI, API, CLI |
|
||||
| M365 | 101 | 10 | 4 | 10 | Official | UI, API, CLI |
|
||||
| M365 | 102 | 10 | 4 | 10 | Official | UI, API, CLI |
|
||||
| OCI | 51 | 14 | 4 | 10 | Official | UI, API, CLI |
|
||||
| Alibaba Cloud | 61 | 9 | 4 | 9 | Official | UI, API, CLI |
|
||||
| Alibaba Cloud | 63 | 9 | 4 | 9 | Official | UI, API, CLI |
|
||||
| Cloudflare | 29 | 3 | 0 | 5 | Official | UI, API, CLI |
|
||||
| IaC | [See `trivy` docs.](https://trivy.dev/latest/docs/coverage/iac/) | N/A | N/A | N/A | Official | UI, API, CLI |
|
||||
| MongoDB Atlas | 10 | 3 | 0 | 8 | Official | UI, API, CLI |
|
||||
| LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | CLI |
|
||||
| Image | N/A | N/A | N/A | N/A | Official | CLI, API |
|
||||
| Google Workspace | 25 | 4 | 2 | 4 | Official | UI, API, CLI |
|
||||
| Google Workspace | 39 | 5 | 2 | 5 | Official | UI, API, CLI |
|
||||
| OpenStack | 34 | 5 | 0 | 9 | Official | UI, API, CLI |
|
||||
| Vercel | 26 | 6 | 0 | 5 | Official | UI, API, CLI |
|
||||
| Vercel | 26 | 6 | 0 | 8 | 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]
|
||||
|
||||
+22
-11
@@ -2,29 +2,40 @@
|
||||
|
||||
All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
## [1.28.0] (Prowler UNRELEASED)
|
||||
## [1.29.1] (Prowler v5.28.1)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- `finding-groups` slow response with finding-level filters such as `region`; check title and description are now read from the daily summaries, which drops sorting by `check_title` [(#11326)](https://github.com/prowler-cloud/prowler/pull/11326)
|
||||
|
||||
---
|
||||
|
||||
## [1.29.0] (Prowler v5.28.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- `okta` provider support [(#11184)](https://github.com/prowler-cloud/prowler/pull/11184)
|
||||
- `resource.metadata` attribute included in `/api/v1/findings?include=resources` [(#11187)](https://github.com/prowler-cloud/prowler/pull/11187)
|
||||
|
||||
---
|
||||
|
||||
## [1.28.0] (Prowler v5.27.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- GIN index on `findings(categories, resource_services, resource_regions, resource_types)` to speed up `/api/v1/finding-groups` array filters [(#11001)](https://github.com/prowler-cloud/prowler/pull/11001)
|
||||
- `GET /health/live` and `GET /health/ready` Kubernetes-style probe endpoints following the IETF Health Check Response Format (`application/health+json`). Readiness verifies PostgreSQL, Valkey and Neo4j connectivity and returns 503 with per-dependency detail when any is unreachable [(#11200)](https://github.com/prowler-cloud/prowler/pull/11200)
|
||||
|
||||
### 🔄 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)
|
||||
- Replace `poetry` with `uv` as package manager [(#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)
|
||||
- PDF compliance reports cap detail tables at 100 failed findings per check (configurable via `DJANGO_PDF_MAX_FINDINGS_PER_CHECK`) to bound worker memory on large scans [(#11160)](https://github.com/prowler-cloud/prowler/pull/11160)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- `perform_scan_task` and `perform_scheduled_scan_task` now short-circuit with a warning and `return None` when the target provider no longer exists, instead of letting `handle_provider_deletion` raise `ProviderDeletedException`. `perform_scheduled_scan_task` also removes any orphan `PeriodicTask` it finds so beat stops re-firing scans for deleted providers. Prevents queued messages for deleted providers from being recorded as `FAILURE` and, in one-shot scan-worker deployments, from burning a fresh container per redelivery [(#11185)](https://github.com/prowler-cloud/prowler/pull/11185)
|
||||
|
||||
---
|
||||
|
||||
## [1.27.2] (Prowler UNRELEASED)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Attack Paths: BEDROCK-001 and BEDROCK-002 now target roles trusting `bedrock-agentcore.amazonaws.com` instead of `bedrock.amazonaws.com`, eliminating false positives against regular Bedrock service roles (Agents, Knowledge Bases, model invocation) [(#11141)](https://github.com/prowler-cloud/prowler/pull/11141)
|
||||
- `perform_scan_task` and `perform_scheduled_scan_task` now short-circuit with a warning and `return None` when the target provider no longer exists, instead of letting `handle_provider_deletion` raise `ProviderDeletedException`. `perform_scheduled_scan_task` also removes any orphan `PeriodicTask` it finds so beat stops re-firing scans for deleted providers. Prevents queued messages for deleted providers from being recorded as `FAILURE` [(#11185)](https://github.com/prowler-cloud/prowler/pull/11185)
|
||||
- Attack Paths: `BEDROCK-001` and `BEDROCK-002` now target roles trusting `bedrock-agentcore.amazonaws.com` instead of `bedrock.amazonaws.com`, eliminating false positives against regular Bedrock service roles (Agents, Knowledge Bases, model invocation) [(#11141)](https://github.com/prowler-cloud/prowler/pull/11141)
|
||||
|
||||
---
|
||||
|
||||
|
||||
+4
-4
@@ -89,7 +89,7 @@ WORKDIR /home/prowler
|
||||
# Ensure output directory exists
|
||||
RUN mkdir -p /tmp/prowler_api_output
|
||||
|
||||
COPY pyproject.toml uv.lock ./
|
||||
COPY --chown=prowler:prowler pyproject.toml uv.lock ./
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir uv==0.11.14
|
||||
@@ -97,13 +97,13 @@ RUN pip install --no-cache-dir --upgrade pip && \
|
||||
ENV PATH="/home/prowler/.local/bin:$PATH"
|
||||
|
||||
# Add `--no-install-project` to avoid installing the current project as a package
|
||||
RUN uv sync --no-install-project && \
|
||||
RUN uv sync --locked --no-install-project && \
|
||||
rm -rf ~/.cache/uv
|
||||
|
||||
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
|
||||
COPY --chown=prowler:prowler src/backend/ ./backend/
|
||||
COPY --chown=prowler:prowler docker-entrypoint.sh ./docker-entrypoint.sh
|
||||
|
||||
WORKDIR /home/prowler/backend
|
||||
|
||||
|
||||
+2
-2
@@ -43,7 +43,7 @@ dependencies = [
|
||||
"defusedxml==0.7.1",
|
||||
"gunicorn==23.0.0",
|
||||
"lxml==6.1.0",
|
||||
"prowler @ git+https://github.com/prowler-cloud/prowler.git@master",
|
||||
"prowler @ git+https://github.com/prowler-cloud/prowler.git@v5.28",
|
||||
"psycopg2-binary==2.9.9",
|
||||
"pytest-celery[redis] (==1.3.0)",
|
||||
"sentry-sdk[django] (==2.56.0)",
|
||||
@@ -68,7 +68,7 @@ name = "prowler-api"
|
||||
package-mode = false
|
||||
# Needed for the SDK compatibility
|
||||
requires-python = ">=3.11,<3.13"
|
||||
version = "1.28.0"
|
||||
version = "1.29.2"
|
||||
|
||||
[tool.uv]
|
||||
# Transitive pins matching master to avoid silent drift; bump deliberately.
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
"""Liveness and readiness endpoints following the IETF Health Check Response
|
||||
Format (draft-inadarei-api-health-check-06).
|
||||
|
||||
Liveness reports only process status. Readiness verifies that PostgreSQL,
|
||||
Valkey and Neo4j are reachable and returns per-dependency detail when any
|
||||
of them is unreachable.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from contextlib import suppress
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
import redis
|
||||
from config.version import API_VERSION, RELEASE_ID
|
||||
from django.conf import settings
|
||||
from django.db import connections
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import status
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.throttling import ScopedRateThrottle
|
||||
from rest_framework.views import APIView
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_ID = "prowler-api"
|
||||
SERVICE_DESCRIPTION = "Prowler API"
|
||||
|
||||
# Status vocabulary from the IETF draft (section 3.1).
|
||||
STATUS_PASS = "pass"
|
||||
STATUS_FAIL = "fail"
|
||||
STATUS_WARN = "warn"
|
||||
|
||||
# Short socket timeout so a stuck Valkey cannot stall the probe.
|
||||
# Neo4j inherits its driver-level ``connection_acquisition_timeout``.
|
||||
VALKEY_PROBE_TIMEOUT_SECONDS = 2
|
||||
|
||||
# Brief cache window so high-frequency probes (ALB target groups, scrapers)
|
||||
# do not stampede the actual dependency checks.
|
||||
CACHE_CONTROL_HEADER = "max-age=3, must-revalidate"
|
||||
|
||||
# In-process readiness cache. Caps real dependency hits to roughly
|
||||
# (gunicorn workers / TTL) per second regardless of incoming RPS or the
|
||||
# source-IP distribution. Kept in sync with the Cache-Control max-age.
|
||||
# Access is guarded by a lock so concurrent readers do not race on the
|
||||
# read-decide-write cycle of the double-checked locking pattern below.
|
||||
READINESS_CACHE_TTL_SECONDS = 3.0
|
||||
_readiness_cache: tuple[float, dict[str, Any], int] | None = None
|
||||
_readiness_cache_lock = threading.Lock()
|
||||
|
||||
|
||||
class HealthJSONRenderer(JSONRenderer):
|
||||
"""Emits responses with the ``application/health+json`` content type."""
|
||||
|
||||
media_type = "application/health+json"
|
||||
format = "health"
|
||||
|
||||
|
||||
def _now_iso() -> str:
|
||||
return (
|
||||
datetime.now(timezone.utc)
|
||||
.isoformat(timespec="milliseconds")
|
||||
.replace("+00:00", "Z")
|
||||
)
|
||||
|
||||
|
||||
def _measure(name: str, check_fn) -> tuple[dict[str, Any], float]:
|
||||
"""Time ``check_fn`` and return ``(result, elapsed_ms)``.
|
||||
|
||||
``check_fn`` returns ``None`` on success or raises on failure. The full
|
||||
exception is logged for operator diagnostics under ``name``; the
|
||||
response payload intentionally omits the error detail to avoid leaking
|
||||
infrastructure information (DNS names, ports, credentials, certificate
|
||||
chains) to anonymous clients.
|
||||
"""
|
||||
started = time.perf_counter()
|
||||
try:
|
||||
check_fn()
|
||||
except Exception:
|
||||
elapsed_ms = (time.perf_counter() - started) * 1000
|
||||
logger.warning("Health probe '%s' failed", name, exc_info=True)
|
||||
return ({"status": STATUS_FAIL}, elapsed_ms)
|
||||
elapsed_ms = (time.perf_counter() - started) * 1000
|
||||
return ({"status": STATUS_PASS}, elapsed_ms)
|
||||
|
||||
|
||||
def _probe_postgres() -> None:
|
||||
with connections["default"].cursor() as cursor:
|
||||
cursor.execute("SELECT 1")
|
||||
cursor.fetchone()
|
||||
|
||||
|
||||
def _probe_valkey() -> None:
|
||||
client = redis.Redis.from_url(
|
||||
settings.CELERY_BROKER_URL,
|
||||
socket_connect_timeout=VALKEY_PROBE_TIMEOUT_SECONDS,
|
||||
socket_timeout=VALKEY_PROBE_TIMEOUT_SECONDS,
|
||||
)
|
||||
try:
|
||||
if not client.ping():
|
||||
raise RuntimeError("PING did not return PONG")
|
||||
finally:
|
||||
# Best-effort cleanup: a failure releasing the socket (e.g. broken
|
||||
# connection, half-closed by the server) must not mask the probe
|
||||
# result. Narrowed to the exception types redis-py and the stdlib
|
||||
# socket layer can raise on close.
|
||||
with suppress(redis.RedisError, OSError):
|
||||
client.close()
|
||||
|
||||
|
||||
def _probe_neo4j() -> None:
|
||||
# Lazy import: avoids pulling attack_paths into the boot import graph.
|
||||
from api.attack_paths.database import get_driver
|
||||
|
||||
get_driver().verify_connectivity()
|
||||
|
||||
|
||||
def _build_check_entry(
|
||||
component_id: str,
|
||||
component_type: str,
|
||||
result: dict[str, Any],
|
||||
elapsed_ms: float,
|
||||
) -> dict[str, Any]:
|
||||
entry: dict[str, Any] = {
|
||||
"componentId": component_id,
|
||||
"componentType": component_type,
|
||||
"observedValue": round(elapsed_ms, 2),
|
||||
"observedUnit": "ms",
|
||||
"status": result["status"],
|
||||
"time": _now_iso(),
|
||||
}
|
||||
if "output" in result:
|
||||
entry["output"] = result["output"]
|
||||
return entry
|
||||
|
||||
|
||||
def _aggregate_status(check_entries: list[dict[str, Any]]) -> str:
|
||||
statuses = {entry["status"] for entry in check_entries}
|
||||
if STATUS_FAIL in statuses:
|
||||
return STATUS_FAIL
|
||||
if STATUS_WARN in statuses:
|
||||
return STATUS_WARN
|
||||
return STATUS_PASS
|
||||
|
||||
|
||||
def _base_payload(overall_status: str) -> dict[str, Any]:
|
||||
return {
|
||||
"status": overall_status,
|
||||
"version": API_VERSION,
|
||||
"releaseId": RELEASE_ID,
|
||||
"serviceId": SERVICE_ID,
|
||||
"description": SERVICE_DESCRIPTION,
|
||||
}
|
||||
|
||||
|
||||
def _readiness_payload() -> tuple[dict[str, Any], int]:
|
||||
global _readiness_cache
|
||||
|
||||
# Lock-free fast path: a stale snapshot still satisfies the freshness
|
||||
# check correctly because we re-check after acquiring the lock below.
|
||||
snapshot = _readiness_cache
|
||||
if (
|
||||
snapshot is not None
|
||||
and time.monotonic() - snapshot[0] < READINESS_CACHE_TTL_SECONDS
|
||||
):
|
||||
return snapshot[1], snapshot[2]
|
||||
|
||||
with _readiness_cache_lock:
|
||||
# Double-checked locking: another thread may have refreshed while
|
||||
# we were waiting on the lock.
|
||||
snapshot = _readiness_cache
|
||||
if (
|
||||
snapshot is not None
|
||||
and time.monotonic() - snapshot[0] < READINESS_CACHE_TTL_SECONDS
|
||||
):
|
||||
return snapshot[1], snapshot[2]
|
||||
|
||||
postgres_result, postgres_ms = _measure("postgres", _probe_postgres)
|
||||
valkey_result, valkey_ms = _measure("valkey", _probe_valkey)
|
||||
neo4j_result, neo4j_ms = _measure("neo4j", _probe_neo4j)
|
||||
|
||||
entries = [
|
||||
_build_check_entry("postgres", "datastore", postgres_result, postgres_ms),
|
||||
_build_check_entry("valkey", "datastore", valkey_result, valkey_ms),
|
||||
_build_check_entry("neo4j", "datastore", neo4j_result, neo4j_ms),
|
||||
]
|
||||
overall = _aggregate_status(entries)
|
||||
|
||||
payload = _base_payload(overall)
|
||||
payload["checks"] = {
|
||||
"postgres:responseTime": [entries[0]],
|
||||
"valkey:responseTime": [entries[1]],
|
||||
"neo4j:responseTime": [entries[2]],
|
||||
}
|
||||
|
||||
http_status = (
|
||||
status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
if overall == STATUS_FAIL
|
||||
else status.HTTP_200_OK
|
||||
)
|
||||
_readiness_cache = (time.monotonic(), payload, http_status)
|
||||
return payload, http_status
|
||||
|
||||
|
||||
def _health_response(payload: dict[str, Any], http_status: int) -> Response:
|
||||
response = Response(payload, status=http_status)
|
||||
response["Cache-Control"] = CACHE_CONTROL_HEADER
|
||||
return response
|
||||
|
||||
|
||||
@extend_schema(exclude=True)
|
||||
class LivenessView(APIView):
|
||||
"""Liveness probe. Always 200 when the process can serve requests.
|
||||
|
||||
Dependencies are intentionally not consulted: a failing liveness probe
|
||||
triggers a container restart, which must not happen for transient
|
||||
dependency outages. Throttled per-IP so the endpoint cannot be used as
|
||||
a cheap availability oracle for the process.
|
||||
"""
|
||||
|
||||
authentication_classes: list = []
|
||||
permission_classes: list = []
|
||||
renderer_classes = [HealthJSONRenderer]
|
||||
throttle_classes = [ScopedRateThrottle]
|
||||
throttle_scope = "health-live"
|
||||
|
||||
def get(self, _request, *_args, **_kwargs):
|
||||
return _health_response(_base_payload(STATUS_PASS), status.HTTP_200_OK)
|
||||
|
||||
|
||||
@extend_schema(exclude=True)
|
||||
class ReadinessView(APIView):
|
||||
"""Readiness probe.
|
||||
|
||||
Returns 200 when PostgreSQL, Valkey and Neo4j all respond, or 503 with
|
||||
per-dependency detail when any of them is unreachable. Per-IP throttle
|
||||
plus the short in-process result cache cap the real dependency hits
|
||||
regardless of inbound traffic shape.
|
||||
"""
|
||||
|
||||
authentication_classes: list = []
|
||||
permission_classes: list = []
|
||||
renderer_classes = [HealthJSONRenderer]
|
||||
throttle_classes = [ScopedRateThrottle]
|
||||
throttle_scope = "health-ready"
|
||||
|
||||
def get(self, _request, *_args, **_kwargs):
|
||||
payload, http_status = _readiness_payload()
|
||||
return _health_response(payload, http_status)
|
||||
@@ -0,0 +1,41 @@
|
||||
from django.db import migrations
|
||||
|
||||
import api.db_utils
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("api", "0092_findings_arrays_gin_index_parent"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="provider",
|
||||
name="provider",
|
||||
field=api.db_utils.ProviderEnumField(
|
||||
choices=[
|
||||
("aws", "AWS"),
|
||||
("azure", "Azure"),
|
||||
("gcp", "GCP"),
|
||||
("kubernetes", "Kubernetes"),
|
||||
("m365", "M365"),
|
||||
("github", "GitHub"),
|
||||
("mongodbatlas", "MongoDB Atlas"),
|
||||
("iac", "IaC"),
|
||||
("oraclecloud", "Oracle Cloud Infrastructure"),
|
||||
("alibabacloud", "Alibaba Cloud"),
|
||||
("cloudflare", "Cloudflare"),
|
||||
("openstack", "OpenStack"),
|
||||
("image", "Image"),
|
||||
("googleworkspace", "Google Workspace"),
|
||||
("vercel", "Vercel"),
|
||||
("okta", "Okta"),
|
||||
],
|
||||
default="aws",
|
||||
),
|
||||
),
|
||||
migrations.RunSQL(
|
||||
"ALTER TYPE provider ADD VALUE IF NOT EXISTS 'okta';",
|
||||
reverse_sql=migrations.RunSQL.noop,
|
||||
),
|
||||
]
|
||||
@@ -296,6 +296,7 @@ class Provider(RowLevelSecurityProtectedModel):
|
||||
IMAGE = "image", _("Image")
|
||||
GOOGLEWORKSPACE = "googleworkspace", _("Google Workspace")
|
||||
VERCEL = "vercel", _("Vercel")
|
||||
OKTA = "okta", _("Okta")
|
||||
|
||||
@staticmethod
|
||||
def validate_aws_uid(value):
|
||||
@@ -354,6 +355,26 @@ class Provider(RowLevelSecurityProtectedModel):
|
||||
pointer="/data/attributes/uid",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def validate_okta_uid(value):
|
||||
if not re.match(
|
||||
r"^[a-z0-9][a-z0-9-]*\.("
|
||||
r"okta\.com|oktapreview\.com|okta-emea\.com|"
|
||||
r"okta-gov\.com|okta\.mil|okta-miltest\.com|trex-govcloud\.com"
|
||||
r")$",
|
||||
value,
|
||||
):
|
||||
raise ModelValidationError(
|
||||
detail=(
|
||||
"Okta provider ID must be a valid Okta-managed org domain "
|
||||
"(e.g., acme.okta.com, also .oktapreview.com / .okta-emea.com "
|
||||
"/ .okta-gov.com / .okta.mil / .okta-miltest.com / "
|
||||
".trex-govcloud.com), without scheme or path."
|
||||
),
|
||||
code="okta-uid",
|
||||
pointer="/data/attributes/uid",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def validate_kubernetes_uid(value):
|
||||
if not re.match(
|
||||
@@ -480,6 +501,12 @@ class Provider(RowLevelSecurityProtectedModel):
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if self.provider == self.ProviderChoices.OKTA and self.uid:
|
||||
# Mirror the SDK, which lowercases the org domain before connecting.
|
||||
# Without this the API would reject Acme.okta.com even though the
|
||||
# SDK would accept it, and stored uids could disagree with the
|
||||
# authenticated org domain.
|
||||
self.uid = self.uid.strip().lower()
|
||||
getattr(self, f"validate_{self.provider}_uid")(self.uid)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Prowler API
|
||||
version: 1.28.0
|
||||
version: 1.29.2
|
||||
description: |-
|
||||
Prowler API specification.
|
||||
|
||||
@@ -373,6 +373,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -389,6 +390,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -412,6 +414,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -430,6 +433,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -1453,6 +1457,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -1469,6 +1474,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -1491,6 +1497,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -1509,6 +1516,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -1997,6 +2005,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -2013,6 +2022,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -2035,6 +2045,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -2053,6 +2064,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -2584,6 +2596,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -2600,6 +2613,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -2622,6 +2636,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -2640,6 +2655,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -3134,6 +3150,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -3150,6 +3167,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -3173,6 +3191,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -3191,6 +3210,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -3740,6 +3760,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -3756,6 +3777,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -3779,6 +3801,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -3797,6 +3820,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -4254,6 +4278,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -4270,6 +4295,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -4293,6 +4319,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -4311,6 +4338,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -4766,6 +4794,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -4782,6 +4811,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -4805,6 +4835,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -4823,6 +4854,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -5266,6 +5298,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -5282,6 +5315,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -5305,6 +5339,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -5323,6 +5358,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -7156,6 +7192,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -7172,6 +7209,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -7195,6 +7233,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -7213,6 +7252,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- name: filter[search]
|
||||
@@ -7335,6 +7375,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -7351,6 +7392,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -7374,6 +7416,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -7392,6 +7435,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- name: filter[search]
|
||||
@@ -7503,6 +7547,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -7519,6 +7564,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -7541,6 +7587,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -7559,6 +7606,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- name: filter[search]
|
||||
@@ -7702,6 +7750,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -7718,6 +7767,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -7741,6 +7791,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -7759,6 +7810,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -7915,6 +7967,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -7931,6 +7984,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -7954,6 +8008,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -7972,6 +8027,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -8122,6 +8178,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -8138,6 +8195,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -8160,6 +8218,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -8178,6 +8237,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- name: filter[search]
|
||||
@@ -8370,6 +8430,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -8386,6 +8447,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -8409,6 +8471,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -8427,6 +8490,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -8548,6 +8612,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -8564,6 +8629,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -8587,6 +8653,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -8605,6 +8672,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -8750,6 +8818,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -8766,6 +8835,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -8789,6 +8859,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -8807,6 +8878,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -9593,6 +9665,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -9609,6 +9682,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider__in]
|
||||
schema:
|
||||
@@ -9632,6 +9706,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -9650,6 +9725,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -9673,6 +9749,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -9689,6 +9766,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -9712,6 +9790,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -9730,6 +9809,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- name: filter[search]
|
||||
@@ -10400,6 +10480,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -10416,6 +10497,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -10439,6 +10521,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -10457,6 +10540,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -10951,6 +11035,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -10967,6 +11052,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -10990,6 +11076,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -11008,6 +11095,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -11315,6 +11403,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -11331,6 +11420,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -11354,6 +11444,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -11372,6 +11463,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -11685,6 +11777,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -11701,6 +11794,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -11724,6 +11818,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -11742,6 +11837,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -12580,6 +12676,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -12596,6 +12693,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -12619,6 +12717,7 @@ paths:
|
||||
- openstack
|
||||
- oraclecloud
|
||||
- vercel
|
||||
- okta
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -12637,6 +12736,7 @@ paths:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -20115,6 +20215,23 @@ components:
|
||||
required:
|
||||
- clouds_yaml_content
|
||||
- clouds_yaml_cloud
|
||||
- type: object
|
||||
title: Okta OAuth Credentials
|
||||
properties:
|
||||
okta_client_id:
|
||||
type: string
|
||||
description: Client ID of the Okta API Services app used for OAuth 2.0 private-key JWT authentication.
|
||||
okta_private_key:
|
||||
type: string
|
||||
description: PEM-encoded private key whose matching public key (JWK) is registered on the Okta service app.
|
||||
okta_scopes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: OAuth scopes to request. Optional; defaults to the minimum set required to run the currently enabled Okta checks.
|
||||
required:
|
||||
- okta_client_id
|
||||
- okta_private_key
|
||||
- type: object
|
||||
title: Vercel API Token
|
||||
properties:
|
||||
@@ -21127,6 +21244,7 @@ components:
|
||||
- image
|
||||
- googleworkspace
|
||||
- vercel
|
||||
- okta
|
||||
type: string
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
@@ -21144,6 +21262,7 @@ components:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
x-spec-enum-id: 91f917e0c3ab97e8
|
||||
uid:
|
||||
type: string
|
||||
@@ -21265,6 +21384,7 @@ components:
|
||||
- image
|
||||
- googleworkspace
|
||||
- vercel
|
||||
- okta
|
||||
type: string
|
||||
x-spec-enum-id: 91f917e0c3ab97e8
|
||||
description: |-
|
||||
@@ -21285,6 +21405,7 @@ components:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
uid:
|
||||
type: string
|
||||
title: Unique identifier for the provider, set by the provider
|
||||
@@ -21337,6 +21458,7 @@ components:
|
||||
- image
|
||||
- googleworkspace
|
||||
- vercel
|
||||
- okta
|
||||
type: string
|
||||
x-spec-enum-id: 91f917e0c3ab97e8
|
||||
description: |-
|
||||
@@ -21357,6 +21479,7 @@ components:
|
||||
* `image` - Image
|
||||
* `googleworkspace` - Google Workspace
|
||||
* `vercel` - Vercel
|
||||
* `okta` - Okta
|
||||
uid:
|
||||
type: string
|
||||
minLength: 3
|
||||
@@ -22206,6 +22329,23 @@ components:
|
||||
required:
|
||||
- clouds_yaml_content
|
||||
- clouds_yaml_cloud
|
||||
- type: object
|
||||
title: Okta OAuth Credentials
|
||||
properties:
|
||||
okta_client_id:
|
||||
type: string
|
||||
description: Client ID of the Okta API Services app used for OAuth 2.0 private-key JWT authentication.
|
||||
okta_private_key:
|
||||
type: string
|
||||
description: PEM-encoded private key whose matching public key (JWK) is registered on the Okta service app.
|
||||
okta_scopes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: OAuth scopes to request. Optional; defaults to the minimum set required to run the currently enabled Okta checks.
|
||||
required:
|
||||
- okta_client_id
|
||||
- okta_private_key
|
||||
- type: object
|
||||
title: Vercel API Token
|
||||
properties:
|
||||
@@ -22631,6 +22771,23 @@ components:
|
||||
required:
|
||||
- clouds_yaml_content
|
||||
- clouds_yaml_cloud
|
||||
- type: object
|
||||
title: Okta OAuth Credentials
|
||||
properties:
|
||||
okta_client_id:
|
||||
type: string
|
||||
description: Client ID of the Okta API Services app used for OAuth 2.0 private-key JWT authentication.
|
||||
okta_private_key:
|
||||
type: string
|
||||
description: PEM-encoded private key whose matching public key (JWK) is registered on the Okta service app.
|
||||
okta_scopes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: OAuth scopes to request. Optional; defaults to the minimum set required to run the currently enabled Okta checks.
|
||||
required:
|
||||
- okta_client_id
|
||||
- okta_private_key
|
||||
- type: object
|
||||
title: Vercel API Token
|
||||
properties:
|
||||
@@ -23066,6 +23223,23 @@ components:
|
||||
required:
|
||||
- clouds_yaml_content
|
||||
- clouds_yaml_cloud
|
||||
- type: object
|
||||
title: Okta OAuth Credentials
|
||||
properties:
|
||||
okta_client_id:
|
||||
type: string
|
||||
description: Client ID of the Okta API Services app used for OAuth 2.0 private-key JWT authentication.
|
||||
okta_private_key:
|
||||
type: string
|
||||
description: PEM-encoded private key whose matching public key (JWK) is registered on the Okta service app.
|
||||
okta_scopes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: OAuth scopes to request. Optional; defaults to the minimum set required to run the currently enabled Okta checks.
|
||||
required:
|
||||
- okta_client_id
|
||||
- okta_private_key
|
||||
- type: object
|
||||
title: Vercel API Token
|
||||
properties:
|
||||
|
||||
@@ -0,0 +1,445 @@
|
||||
"""Tests for the health endpoints.
|
||||
|
||||
Cover the IETF response envelope, status code mapping (200 / 503), the
|
||||
``application/health+json`` media type and per-probe failure modes.
|
||||
"""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from config import version as config_version
|
||||
from django.core.cache import cache
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from api import health
|
||||
|
||||
|
||||
HEALTH_MEDIA_TYPE = "application/health+json"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_health_state():
|
||||
"""Per-test isolation: clear throttle counters and the readiness cache.
|
||||
|
||||
DRF's ScopedRateThrottle persists state in Django's cache; without
|
||||
clearing it the throttle budget would be shared across tests and trip
|
||||
midway through the suite.
|
||||
"""
|
||||
cache.clear()
|
||||
health._readiness_cache = None
|
||||
yield
|
||||
cache.clear()
|
||||
health._readiness_cache = None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def api_client():
|
||||
return APIClient()
|
||||
|
||||
|
||||
def _assert_health_envelope(body):
|
||||
"""Every health response must carry the RFC top-level descriptors."""
|
||||
assert body["version"] == config_version.API_VERSION
|
||||
assert body["releaseId"] == config_version.RELEASE_ID
|
||||
assert body["serviceId"] == health.SERVICE_ID
|
||||
assert body["description"] == health.SERVICE_DESCRIPTION
|
||||
|
||||
|
||||
class TestLivenessEndpoint:
|
||||
def test_returns_200_with_pass_status(self, api_client):
|
||||
response = api_client.get(reverse("health-live"))
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response["Content-Type"].startswith(HEALTH_MEDIA_TYPE)
|
||||
assert response["Cache-Control"] == health.CACHE_CONTROL_HEADER
|
||||
body = response.json()
|
||||
assert body["status"] == "pass"
|
||||
_assert_health_envelope(body)
|
||||
|
||||
def test_does_not_require_authentication(self, api_client):
|
||||
api_client.credentials()
|
||||
|
||||
response = api_client.get(reverse("health-live"))
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
def test_does_not_run_dependency_checks(self, api_client):
|
||||
with (
|
||||
patch("api.health._probe_postgres") as mock_pg,
|
||||
patch("api.health._probe_valkey") as mock_vk,
|
||||
patch("api.health._probe_neo4j") as mock_neo,
|
||||
):
|
||||
response = api_client.get(reverse("health-live"))
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
mock_pg.assert_not_called()
|
||||
mock_vk.assert_not_called()
|
||||
mock_neo.assert_not_called()
|
||||
|
||||
|
||||
class TestReadinessEndpoint:
|
||||
@staticmethod
|
||||
def _patch_probes():
|
||||
return (
|
||||
patch("api.health._probe_postgres", return_value=None),
|
||||
patch("api.health._probe_valkey", return_value=None),
|
||||
patch("api.health._probe_neo4j", return_value=None),
|
||||
)
|
||||
|
||||
def test_returns_200_and_pass_when_all_dependencies_healthy(self, api_client):
|
||||
with (
|
||||
patch("api.health._probe_postgres"),
|
||||
patch("api.health._probe_valkey"),
|
||||
patch("api.health._probe_neo4j"),
|
||||
):
|
||||
response = api_client.get(reverse("health-ready"))
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response["Content-Type"].startswith(HEALTH_MEDIA_TYPE)
|
||||
assert response["Cache-Control"] == health.CACHE_CONTROL_HEADER
|
||||
|
||||
body = response.json()
|
||||
_assert_health_envelope(body)
|
||||
assert body["status"] == "pass"
|
||||
|
||||
# Per RFC, `checks` values are arrays of one or more measurement
|
||||
# objects. We use a single measurement per dependency.
|
||||
assert set(body["checks"].keys()) == {
|
||||
"postgres:responseTime",
|
||||
"valkey:responseTime",
|
||||
"neo4j:responseTime",
|
||||
}
|
||||
for key in body["checks"]:
|
||||
entries = body["checks"][key]
|
||||
assert isinstance(entries, list) and len(entries) == 1
|
||||
entry = entries[0]
|
||||
assert entry["status"] == "pass"
|
||||
assert entry["componentType"] == "datastore"
|
||||
assert entry["observedUnit"] == "ms"
|
||||
assert isinstance(entry["observedValue"], (int, float))
|
||||
assert entry["observedValue"] >= 0
|
||||
assert "time" in entry
|
||||
# `output` must not leak when the check passed.
|
||||
assert "output" not in entry
|
||||
|
||||
def test_returns_503_and_fail_when_postgres_is_down(self, api_client):
|
||||
with (
|
||||
patch(
|
||||
"api.health._probe_postgres",
|
||||
side_effect=RuntimeError("connection refused"),
|
||||
),
|
||||
patch("api.health._probe_valkey"),
|
||||
patch("api.health._probe_neo4j"),
|
||||
):
|
||||
response = api_client.get(reverse("health-ready"))
|
||||
|
||||
assert response.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
body = response.json()
|
||||
assert body["status"] == "fail"
|
||||
pg_entry = body["checks"]["postgres:responseTime"][0]
|
||||
assert pg_entry["status"] == "fail"
|
||||
# Exception detail is never echoed in the response, only logged.
|
||||
assert "output" not in pg_entry
|
||||
assert body["checks"]["valkey:responseTime"][0]["status"] == "pass"
|
||||
assert body["checks"]["neo4j:responseTime"][0]["status"] == "pass"
|
||||
|
||||
def test_returns_503_and_fail_when_valkey_is_down(self, api_client):
|
||||
with (
|
||||
patch("api.health._probe_postgres"),
|
||||
patch("api.health._probe_valkey", side_effect=ConnectionError("timeout")),
|
||||
patch("api.health._probe_neo4j"),
|
||||
):
|
||||
response = api_client.get(reverse("health-ready"))
|
||||
|
||||
assert response.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
body = response.json()
|
||||
assert body["status"] == "fail"
|
||||
vk_entry = body["checks"]["valkey:responseTime"][0]
|
||||
assert vk_entry["status"] == "fail"
|
||||
assert "output" not in vk_entry
|
||||
|
||||
def test_returns_503_and_fail_when_neo4j_is_down(self, api_client):
|
||||
with (
|
||||
patch("api.health._probe_postgres"),
|
||||
patch("api.health._probe_valkey"),
|
||||
patch(
|
||||
"api.health._probe_neo4j",
|
||||
side_effect=RuntimeError("ServiceUnavailable"),
|
||||
),
|
||||
):
|
||||
response = api_client.get(reverse("health-ready"))
|
||||
|
||||
assert response.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
body = response.json()
|
||||
assert body["status"] == "fail"
|
||||
neo_entry = body["checks"]["neo4j:responseTime"][0]
|
||||
assert neo_entry["status"] == "fail"
|
||||
assert "output" not in neo_entry
|
||||
|
||||
def test_reports_all_failures_simultaneously(self, api_client):
|
||||
with (
|
||||
patch("api.health._probe_postgres", side_effect=RuntimeError("pg down")),
|
||||
patch("api.health._probe_valkey", side_effect=RuntimeError("vk down")),
|
||||
patch("api.health._probe_neo4j", side_effect=RuntimeError("neo down")),
|
||||
):
|
||||
response = api_client.get(reverse("health-ready"))
|
||||
|
||||
assert response.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
body = response.json()
|
||||
assert body["status"] == "fail"
|
||||
for key in (
|
||||
"postgres:responseTime",
|
||||
"valkey:responseTime",
|
||||
"neo4j:responseTime",
|
||||
):
|
||||
entry = body["checks"][key][0]
|
||||
assert entry["status"] == "fail"
|
||||
# No dependency-specific error string leaks into the payload.
|
||||
assert "output" not in entry
|
||||
|
||||
def test_does_not_leak_exception_detail_on_failure(self, api_client):
|
||||
# Sanity check: an exception message resembling infra detail
|
||||
# (host, port, credentials) must not surface in the response under
|
||||
# any field.
|
||||
sensitive = (
|
||||
"connection to server at "
|
||||
'"postgres-rw.prod.svc.cluster.local" (10.0.0.5), port 5432 '
|
||||
'failed: FATAL: password authentication failed for user "prowler_user"'
|
||||
)
|
||||
with (
|
||||
patch("api.health._probe_postgres", side_effect=RuntimeError(sensitive)),
|
||||
patch("api.health._probe_valkey"),
|
||||
patch("api.health._probe_neo4j"),
|
||||
):
|
||||
response = api_client.get(reverse("health-ready"))
|
||||
|
||||
body = response.json()
|
||||
assert "output" not in body["checks"]["postgres:responseTime"][0]
|
||||
payload_text = response.content.decode()
|
||||
for token in (
|
||||
"postgres-rw",
|
||||
"10.0.0.5",
|
||||
"5432",
|
||||
"prowler_user",
|
||||
"password authentication failed",
|
||||
):
|
||||
assert token not in payload_text
|
||||
|
||||
def test_does_not_require_authentication(self, api_client):
|
||||
with (
|
||||
patch("api.health._probe_postgres"),
|
||||
patch("api.health._probe_valkey"),
|
||||
patch("api.health._probe_neo4j"),
|
||||
):
|
||||
api_client.credentials()
|
||||
response = api_client.get(reverse("health-ready"))
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
|
||||
class TestReadinessCache:
|
||||
"""In-process cache caps the rate at which real probes hit the deps."""
|
||||
|
||||
def test_result_is_cached_for_ttl_seconds(self, api_client):
|
||||
with (
|
||||
patch("api.health._probe_postgres") as pg,
|
||||
patch("api.health._probe_valkey") as vk,
|
||||
patch("api.health._probe_neo4j") as neo,
|
||||
):
|
||||
r1 = api_client.get(reverse("health-ready"))
|
||||
r2 = api_client.get(reverse("health-ready"))
|
||||
|
||||
assert r1.status_code == status.HTTP_200_OK
|
||||
assert r2.status_code == status.HTTP_200_OK
|
||||
# Second request must not trigger fresh dep checks within the TTL.
|
||||
assert pg.call_count == 1
|
||||
assert vk.call_count == 1
|
||||
assert neo.call_count == 1
|
||||
# The cached payload is returned verbatim (same timestamps too).
|
||||
assert r1.json() == r2.json()
|
||||
|
||||
def test_re_probes_after_cache_ttl_expires(self, api_client):
|
||||
with (
|
||||
patch("api.health._probe_postgres") as pg,
|
||||
patch("api.health._probe_valkey"),
|
||||
patch("api.health._probe_neo4j"),
|
||||
):
|
||||
api_client.get(reverse("health-ready"))
|
||||
assert pg.call_count == 1
|
||||
|
||||
# Rewind the cached timestamp past the TTL so the next request
|
||||
# is forced to recompute.
|
||||
cached_ts, payload, http_status_code = health._readiness_cache
|
||||
health._readiness_cache = (
|
||||
cached_ts - health.READINESS_CACHE_TTL_SECONDS - 0.1,
|
||||
payload,
|
||||
http_status_code,
|
||||
)
|
||||
api_client.get(reverse("health-ready"))
|
||||
|
||||
assert pg.call_count == 2
|
||||
|
||||
def test_cache_persists_a_failing_result(self, api_client):
|
||||
# A failing readiness result is cached too; this is intentional so
|
||||
# an attacker spamming the endpoint during an outage cannot amplify
|
||||
# the dependency load.
|
||||
with (
|
||||
patch("api.health._probe_postgres", side_effect=RuntimeError("down")) as pg,
|
||||
patch("api.health._probe_valkey"),
|
||||
patch("api.health._probe_neo4j"),
|
||||
):
|
||||
r1 = api_client.get(reverse("health-ready"))
|
||||
r2 = api_client.get(reverse("health-ready"))
|
||||
|
||||
assert r1.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
assert r2.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
assert pg.call_count == 1
|
||||
|
||||
|
||||
class TestRateLimiting:
|
||||
"""The endpoints are unauthenticated and exposed; per-IP throttle caps
|
||||
naive single-source floods."""
|
||||
|
||||
def test_live_blocks_after_budget_exhausted(self, api_client):
|
||||
# Shrink the budget to 3 req per window so the test stays fast and
|
||||
# deterministic. parse_rate runs once per throttle instance and
|
||||
# each request gets a fresh instance, so this patch propagates.
|
||||
from rest_framework.throttling import ScopedRateThrottle
|
||||
|
||||
with patch.object(ScopedRateThrottle, "parse_rate", return_value=(3, 60)):
|
||||
statuses = [
|
||||
api_client.get(reverse("health-live")).status_code for _ in range(4)
|
||||
]
|
||||
|
||||
assert statuses[:3] == [status.HTTP_200_OK] * 3
|
||||
assert statuses[3] == status.HTTP_429_TOO_MANY_REQUESTS
|
||||
|
||||
def test_ready_blocks_after_budget_exhausted(self, api_client):
|
||||
from rest_framework.throttling import ScopedRateThrottle
|
||||
|
||||
with (
|
||||
patch("api.health._probe_postgres"),
|
||||
patch("api.health._probe_valkey"),
|
||||
patch("api.health._probe_neo4j"),
|
||||
patch.object(ScopedRateThrottle, "parse_rate", return_value=(2, 60)),
|
||||
):
|
||||
statuses = [
|
||||
api_client.get(reverse("health-ready")).status_code for _ in range(3)
|
||||
]
|
||||
|
||||
assert statuses[:2] == [status.HTTP_200_OK] * 2
|
||||
assert statuses[2] == status.HTTP_429_TOO_MANY_REQUESTS
|
||||
|
||||
|
||||
class TestProbeImplementations:
|
||||
"""Smoke tests for each probe primitive."""
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_postgres_probe_succeeds_against_real_db(self):
|
||||
assert health._probe_postgres() is None
|
||||
|
||||
def test_postgres_probe_propagates_db_errors(self):
|
||||
class _BoomCursor:
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *_):
|
||||
return False
|
||||
|
||||
def execute(self, *_args, **_kwargs):
|
||||
raise RuntimeError("boom")
|
||||
|
||||
def fetchone(self): # pragma: no cover - never reached
|
||||
return None
|
||||
|
||||
with patch("api.health.connections") as mock_connections:
|
||||
mock_connections.__getitem__.return_value.cursor.return_value = (
|
||||
_BoomCursor()
|
||||
)
|
||||
with pytest.raises(RuntimeError, match="boom"):
|
||||
health._probe_postgres()
|
||||
|
||||
def test_valkey_probe_succeeds_when_ping_returns_true(self):
|
||||
with patch("api.health.redis.Redis.from_url") as mock_from_url:
|
||||
mock_from_url.return_value.ping.return_value = True
|
||||
assert health._probe_valkey() is None
|
||||
|
||||
def test_valkey_probe_raises_when_ping_returns_false(self):
|
||||
with patch("api.health.redis.Redis.from_url") as mock_from_url:
|
||||
mock_from_url.return_value.ping.return_value = False
|
||||
with pytest.raises(RuntimeError, match="PING"):
|
||||
health._probe_valkey()
|
||||
|
||||
def test_valkey_probe_propagates_connection_errors(self):
|
||||
with patch("api.health.redis.Redis.from_url") as mock_from_url:
|
||||
mock_from_url.return_value.ping.side_effect = ConnectionError("nope")
|
||||
with pytest.raises(ConnectionError, match="nope"):
|
||||
health._probe_valkey()
|
||||
|
||||
def test_valkey_probe_suppresses_redis_error_on_close(self):
|
||||
# A redis-py-level failure releasing the socket must not mask a
|
||||
# successful PING (best-effort cleanup contract).
|
||||
import redis as redis_pkg
|
||||
|
||||
with patch("api.health.redis.Redis.from_url") as mock_from_url:
|
||||
client = mock_from_url.return_value
|
||||
client.ping.return_value = True
|
||||
client.close.side_effect = redis_pkg.RedisError("connection reset")
|
||||
|
||||
assert health._probe_valkey() is None
|
||||
|
||||
client.close.assert_called_once_with()
|
||||
|
||||
def test_valkey_probe_suppresses_oserror_on_close(self):
|
||||
# Socket-layer failures (OSError family) on close are also part of
|
||||
# the swallowed scope.
|
||||
with patch("api.health.redis.Redis.from_url") as mock_from_url:
|
||||
client = mock_from_url.return_value
|
||||
client.ping.return_value = True
|
||||
client.close.side_effect = OSError("EBADF")
|
||||
|
||||
assert health._probe_valkey() is None
|
||||
|
||||
client.close.assert_called_once_with()
|
||||
|
||||
def test_valkey_probe_lets_unexpected_close_errors_propagate(self):
|
||||
# The suppress() is deliberately narrow: anything outside
|
||||
# (redis.RedisError, OSError) must surface so it is not silently
|
||||
# hidden.
|
||||
with patch("api.health.redis.Redis.from_url") as mock_from_url:
|
||||
client = mock_from_url.return_value
|
||||
client.ping.return_value = True
|
||||
client.close.side_effect = RuntimeError("bug")
|
||||
|
||||
with pytest.raises(RuntimeError, match="bug"):
|
||||
health._probe_valkey()
|
||||
|
||||
def test_neo4j_probe_calls_verify_connectivity(self):
|
||||
with patch("api.attack_paths.database.get_driver") as mock_get_driver:
|
||||
mock_get_driver.return_value.verify_connectivity.return_value = None
|
||||
assert health._probe_neo4j() is None
|
||||
mock_get_driver.return_value.verify_connectivity.assert_called_once_with()
|
||||
|
||||
def test_neo4j_probe_propagates_driver_errors(self):
|
||||
with patch("api.attack_paths.database.get_driver") as mock_get_driver:
|
||||
mock_get_driver.return_value.verify_connectivity.side_effect = RuntimeError(
|
||||
"unreachable"
|
||||
)
|
||||
with pytest.raises(RuntimeError, match="unreachable"):
|
||||
health._probe_neo4j()
|
||||
|
||||
|
||||
class TestStatusAggregation:
|
||||
def test_pass_when_all_checks_pass(self):
|
||||
entries = [{"status": "pass"}, {"status": "pass"}]
|
||||
assert health._aggregate_status(entries) == "pass"
|
||||
|
||||
def test_warn_when_any_check_warns_and_none_fail(self):
|
||||
entries = [{"status": "pass"}, {"status": "warn"}]
|
||||
assert health._aggregate_status(entries) == "warn"
|
||||
|
||||
def test_fail_when_any_check_fails(self):
|
||||
entries = [{"status": "pass"}, {"status": "warn"}, {"status": "fail"}]
|
||||
assert health._aggregate_status(entries) == "fail"
|
||||
@@ -31,6 +31,7 @@ from prowler.providers.image.image_provider import ImageProvider
|
||||
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
|
||||
from prowler.providers.m365.m365_provider import M365Provider
|
||||
from prowler.providers.mongodbatlas.mongodbatlas_provider import MongodbatlasProvider
|
||||
from prowler.providers.okta.okta_provider import OktaProvider
|
||||
from prowler.providers.openstack.openstack_provider import OpenstackProvider
|
||||
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
|
||||
from prowler.providers.vercel.vercel_provider import VercelProvider
|
||||
@@ -130,6 +131,7 @@ class TestReturnProwlerProvider:
|
||||
(Provider.ProviderChoices.OPENSTACK.value, OpenstackProvider),
|
||||
(Provider.ProviderChoices.IMAGE.value, ImageProvider),
|
||||
(Provider.ProviderChoices.VERCEL.value, VercelProvider),
|
||||
(Provider.ProviderChoices.OKTA.value, OktaProvider),
|
||||
],
|
||||
)
|
||||
def test_return_prowler_provider(self, provider_type, expected_provider):
|
||||
@@ -238,6 +240,31 @@ class TestProwlerProviderConnectionTest:
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
@patch("api.utils.return_prowler_provider")
|
||||
def test_prowler_provider_connection_test_okta_provider(
|
||||
self, mock_return_prowler_provider
|
||||
):
|
||||
"""Test connection test for Okta provider passes org domain and provider_id."""
|
||||
provider = MagicMock()
|
||||
provider.uid = "acme.okta.com"
|
||||
provider.provider = Provider.ProviderChoices.OKTA.value
|
||||
provider.secret.secret = {
|
||||
"okta_client_id": "0oa123456789abcdef",
|
||||
"okta_private_key": "-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----",
|
||||
"okta_scopes": ["okta.policies.read"],
|
||||
}
|
||||
mock_return_prowler_provider.return_value = MagicMock()
|
||||
|
||||
prowler_provider_connection_test(provider)
|
||||
mock_return_prowler_provider.return_value.test_connection.assert_called_once_with(
|
||||
okta_client_id="0oa123456789abcdef",
|
||||
okta_private_key="-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----",
|
||||
okta_scopes=["okta.policies.read"],
|
||||
okta_org_domain="acme.okta.com",
|
||||
provider_id="acme.okta.com",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
@patch("api.utils.return_prowler_provider")
|
||||
def test_prowler_provider_connection_test_image_provider_no_creds(
|
||||
self, mock_return_prowler_provider
|
||||
@@ -308,6 +335,10 @@ class TestGetProwlerProviderKwargs:
|
||||
Provider.ProviderChoices.VERCEL.value,
|
||||
{"team_id": "provider_uid"},
|
||||
),
|
||||
(
|
||||
Provider.ProviderChoices.OKTA.value,
|
||||
{"okta_org_domain": "provider_uid"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_prowler_provider_kwargs(self, provider_type, expected_extra_kwargs):
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Drift checks for the API version constants.
|
||||
|
||||
Guarantee that ``config.version`` always reflects the canonical
|
||||
``[project].version`` declared in ``api/pyproject.toml``.
|
||||
"""
|
||||
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from config import version as config_version
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def pyproject_data():
|
||||
here = Path(__file__).resolve()
|
||||
for directory in here.parents:
|
||||
candidate = directory / "pyproject.toml"
|
||||
if not candidate.is_file():
|
||||
continue
|
||||
with candidate.open("rb") as f:
|
||||
data = tomllib.load(f)
|
||||
if data.get("project", {}).get("name") == "prowler-api":
|
||||
return data
|
||||
raise AssertionError("api/pyproject.toml not reachable from the test runner")
|
||||
|
||||
|
||||
def test_release_id_matches_pyproject(pyproject_data):
|
||||
assert config_version.RELEASE_ID == pyproject_data["project"]["version"]
|
||||
|
||||
|
||||
def test_api_version_is_major_of_release_id():
|
||||
assert config_version.API_VERSION == config_version.RELEASE_ID.split(".", 1)[0]
|
||||
assert config_version.API_VERSION.isdigit()
|
||||
|
||||
|
||||
def test_api_version_matches_v1_url_prefix():
|
||||
# The public contract version surfaced in the health payload must match
|
||||
# the URL namespace the API is published under.
|
||||
assert config_version.API_VERSION == "1"
|
||||
@@ -1625,6 +1625,21 @@ class TestProviderViewSet:
|
||||
"uid": "C12",
|
||||
"alias": "Google Workspace Minimum Length",
|
||||
},
|
||||
{
|
||||
"provider": "okta",
|
||||
"uid": "acme.okta.com",
|
||||
"alias": "Okta Org",
|
||||
},
|
||||
{
|
||||
"provider": "okta",
|
||||
"uid": "agency.okta-gov.com",
|
||||
"alias": "Okta Gov Org",
|
||||
},
|
||||
{
|
||||
"provider": "okta",
|
||||
"uid": "agency.okta.mil",
|
||||
"alias": "Okta Mil Org",
|
||||
},
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -2143,6 +2158,24 @@ class TestProviderViewSet:
|
||||
"googleworkspace-uid",
|
||||
"uid",
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "okta",
|
||||
"uid": "https://acme.okta.com",
|
||||
"alias": "test",
|
||||
},
|
||||
"okta-uid",
|
||||
"uid",
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "okta",
|
||||
"uid": "acme.example.com",
|
||||
"alias": "test",
|
||||
},
|
||||
"okta-uid",
|
||||
"uid",
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -2163,6 +2196,25 @@ class TestProviderViewSet:
|
||||
== f"/data/attributes/{error_pointer}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input_uid,stored_uid",
|
||||
[
|
||||
("Acme.okta.com", "acme.okta.com"),
|
||||
(" ACME.OKTA.COM ", "acme.okta.com"),
|
||||
("Agency.Okta-Gov.com", "agency.okta-gov.com"),
|
||||
],
|
||||
)
|
||||
def test_providers_create_okta_uid_normalized(
|
||||
self, authenticated_client, input_uid, stored_uid
|
||||
):
|
||||
response = authenticated_client.post(
|
||||
reverse("provider-list"),
|
||||
data={"provider": "okta", "uid": input_uid, "alias": "Okta"},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert Provider.objects.get().uid == stored_uid
|
||||
|
||||
def test_providers_partial_update(self, authenticated_client, providers_fixture):
|
||||
provider1, *_ = providers_fixture
|
||||
new_alias = "This is the new name"
|
||||
@@ -2320,17 +2372,17 @@ class TestProviderViewSet:
|
||||
),
|
||||
("alias", "aws_testing_1", 1),
|
||||
("alias.icontains", "aws", 2),
|
||||
("inserted_at", TODAY, 13),
|
||||
("inserted_at", TODAY, 14),
|
||||
(
|
||||
"inserted_at.gte",
|
||||
"2024-01-01",
|
||||
13,
|
||||
14,
|
||||
),
|
||||
("inserted_at.lte", "2024-01-01", 0),
|
||||
(
|
||||
"updated_at.gte",
|
||||
"2024-01-01",
|
||||
13,
|
||||
14,
|
||||
),
|
||||
("updated_at.lte", "2024-01-01", 0),
|
||||
]
|
||||
@@ -2963,6 +3015,19 @@ class TestProviderSecretViewSet:
|
||||
"api_token": "fake-vercel-api-token-for-testing",
|
||||
},
|
||||
),
|
||||
# Okta with inline private key credentials
|
||||
(
|
||||
Provider.ProviderChoices.OKTA.value,
|
||||
ProviderSecret.TypeChoices.STATIC,
|
||||
{
|
||||
"okta_client_id": "0oa123456789abcdef",
|
||||
"okta_private_key": "-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----",
|
||||
"okta_scopes": [
|
||||
"okta.policies.read",
|
||||
"okta.groups.read",
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_provider_secrets_create_valid(
|
||||
@@ -3075,6 +3140,46 @@ class TestProviderSecretViewSet:
|
||||
== f"/data/attributes/{error_pointer}"
|
||||
)
|
||||
|
||||
def test_provider_secrets_invalid_create_okta_missing_private_key(
|
||||
self,
|
||||
providers_fixture,
|
||||
authenticated_client,
|
||||
):
|
||||
okta_provider = next(
|
||||
provider
|
||||
for provider in providers_fixture
|
||||
if provider.provider == Provider.ProviderChoices.OKTA.value
|
||||
)
|
||||
data = {
|
||||
"data": {
|
||||
"type": "provider-secrets",
|
||||
"attributes": {
|
||||
"name": "Okta Secret",
|
||||
"secret_type": ProviderSecret.TypeChoices.STATIC,
|
||||
"secret": {
|
||||
"okta_client_id": "0oa123456789abcdef",
|
||||
},
|
||||
},
|
||||
"relationships": {
|
||||
"provider": {
|
||||
"data": {"type": "providers", "id": str(okta_provider.id)}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
response = authenticated_client.post(
|
||||
reverse("providersecret-list"),
|
||||
data=json.dumps(data),
|
||||
content_type="application/vnd.api+json",
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert response.json()["errors"][0]["code"] == "required"
|
||||
assert response.json()["errors"][0]["source"]["pointer"] == (
|
||||
"/data/attributes/secret/okta_private_key"
|
||||
)
|
||||
|
||||
def test_provider_secrets_partial_update(
|
||||
self, authenticated_client, provider_secret_fixture
|
||||
):
|
||||
@@ -7053,6 +7158,32 @@ class TestFindingViewSet:
|
||||
"id"
|
||||
] == str(finding_1.resources.first().id)
|
||||
|
||||
def test_findings_retrieve_include_resource_metadata(
|
||||
self, authenticated_client, findings_fixture
|
||||
):
|
||||
finding_1, *_ = findings_fixture
|
||||
resource = finding_1.resources.first()
|
||||
resource.metadata = '{"VulnerabilityID": "CVE-2026-0001"}'
|
||||
resource.details = "Python 3.12 base image"
|
||||
resource.save()
|
||||
|
||||
response = authenticated_client.get(
|
||||
reverse("finding-detail", kwargs={"pk": finding_1.id}),
|
||||
{"include": "resources"},
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
included_resource = next(
|
||||
item
|
||||
for item in response.json()["included"]
|
||||
if item["type"] == "resources" and item["id"] == str(resource.id)
|
||||
)
|
||||
assert (
|
||||
included_resource["attributes"]["metadata"]
|
||||
== '{"VulnerabilityID": "CVE-2026-0001"}'
|
||||
)
|
||||
assert included_resource["attributes"]["details"] == "Python 3.12 base image"
|
||||
|
||||
def test_findings_invalid_retrieve(self, authenticated_client):
|
||||
response = authenticated_client.get(
|
||||
reverse("finding-detail", kwargs={"pk": "random_id"}),
|
||||
@@ -15790,6 +15921,12 @@ class TestFindingGroupViewSet:
|
||||
assert attrs["fail_count"] == 0
|
||||
assert attrs["resources_total"] == 1
|
||||
assert attrs["resources_fail"] == 0
|
||||
# check_title / check_description are resolved post-pagination from the
|
||||
# summary table, not from the finding's check_metadata.
|
||||
assert attrs["check_title"] == "Ensure EC2 instances do not have public IPs"
|
||||
assert (
|
||||
attrs["check_description"] == "EC2 instances should use private IPs only."
|
||||
)
|
||||
|
||||
def test_finding_groups_status_pass_when_no_fail(
|
||||
self, authenticated_client, finding_groups_fixture
|
||||
@@ -17031,6 +17168,12 @@ class TestFindingGroupViewSet:
|
||||
assert attrs["fail_count"] == 0
|
||||
assert attrs["resources_total"] == 1
|
||||
assert attrs["resources_fail"] == 0
|
||||
# check_title / check_description are resolved post-pagination from the
|
||||
# summary table, not from the finding's check_metadata.
|
||||
assert attrs["check_title"] == "Ensure EC2 instances do not have public IPs"
|
||||
assert (
|
||||
attrs["check_description"] == "EC2 instances should use private IPs only."
|
||||
)
|
||||
|
||||
def test_finding_groups_latest_status_in_filter(
|
||||
self, authenticated_client, finding_groups_fixture
|
||||
@@ -17288,18 +17431,20 @@ class TestFindingGroupViewSet:
|
||||
check_ids = [item["id"] for item in data]
|
||||
assert check_ids == sorted(check_ids)
|
||||
|
||||
def test_finding_groups_latest_sort_by_check_title(
|
||||
def test_finding_groups_latest_sort_by_check_title_not_supported(
|
||||
self, authenticated_client, finding_groups_fixture
|
||||
):
|
||||
"""Test /latest supports sorting by check_title."""
|
||||
"""check_title is not a sortable field for finding groups.
|
||||
|
||||
Titles live in the TOASTed check_metadata blob and are resolved after
|
||||
pagination from the summary table, so they cannot drive DB-level
|
||||
ordering. Requesting that sort is rejected.
|
||||
"""
|
||||
response = authenticated_client.get(
|
||||
reverse("finding-group-latest"),
|
||||
{"sort": "check_title"},
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
data = response.json()["data"]
|
||||
check_titles = [item["attributes"]["check_title"] for item in data]
|
||||
assert check_titles == sorted(check_titles)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"endpoint_name", ["finding-group-list", "finding-group-latest"]
|
||||
|
||||
@@ -37,6 +37,7 @@ if TYPE_CHECKING:
|
||||
from prowler.providers.mongodbatlas.mongodbatlas_provider import (
|
||||
MongodbatlasProvider,
|
||||
)
|
||||
from prowler.providers.okta.okta_provider import OktaProvider
|
||||
from prowler.providers.openstack.openstack_provider import OpenstackProvider
|
||||
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
|
||||
from prowler.providers.vercel.vercel_provider import VercelProvider
|
||||
@@ -93,6 +94,7 @@ def return_prowler_provider(
|
||||
| KubernetesProvider
|
||||
| M365Provider
|
||||
| MongodbatlasProvider
|
||||
| OktaProvider
|
||||
| OpenstackProvider
|
||||
| OraclecloudProvider
|
||||
| VercelProvider
|
||||
@@ -181,6 +183,10 @@ def return_prowler_provider(
|
||||
from prowler.providers.vercel.vercel_provider import VercelProvider
|
||||
|
||||
prowler_provider = VercelProvider
|
||||
case Provider.ProviderChoices.OKTA.value:
|
||||
from prowler.providers.okta.okta_provider import OktaProvider
|
||||
|
||||
prowler_provider = OktaProvider
|
||||
case _:
|
||||
raise ValueError(f"Provider type {provider.provider} not supported")
|
||||
return prowler_provider
|
||||
@@ -246,6 +252,11 @@ def get_prowler_provider_kwargs(
|
||||
**prowler_provider_kwargs,
|
||||
"team_id": provider.uid,
|
||||
}
|
||||
elif provider.provider == Provider.ProviderChoices.OKTA.value:
|
||||
prowler_provider_kwargs = {
|
||||
**prowler_provider_kwargs,
|
||||
"okta_org_domain": provider.uid,
|
||||
}
|
||||
elif provider.provider == Provider.ProviderChoices.IMAGE.value:
|
||||
# Detect whether uid is a registry URL (e.g. "docker.io/andoniaf") or
|
||||
# a concrete image reference (e.g. "docker.io/andoniaf/myimage:latest").
|
||||
@@ -290,6 +301,7 @@ def initialize_prowler_provider(
|
||||
| KubernetesProvider
|
||||
| M365Provider
|
||||
| MongodbatlasProvider
|
||||
| OktaProvider
|
||||
| OpenstackProvider
|
||||
| OraclecloudProvider
|
||||
| VercelProvider
|
||||
@@ -351,6 +363,14 @@ def prowler_provider_connection_test(provider: Provider) -> Connection:
|
||||
"raise_on_exception": False,
|
||||
}
|
||||
return prowler_provider.test_connection(**vercel_kwargs)
|
||||
elif provider.provider == Provider.ProviderChoices.OKTA.value:
|
||||
okta_kwargs = {
|
||||
**prowler_provider_kwargs,
|
||||
"okta_org_domain": provider.uid,
|
||||
"provider_id": provider.uid,
|
||||
"raise_on_exception": False,
|
||||
}
|
||||
return prowler_provider.test_connection(**okta_kwargs)
|
||||
elif provider.provider == Provider.ProviderChoices.IMAGE.value:
|
||||
image_kwargs = {
|
||||
"image": provider.uid,
|
||||
|
||||
@@ -404,6 +404,26 @@ from rest_framework_json_api import serializers
|
||||
},
|
||||
"required": ["clouds_yaml_content", "clouds_yaml_cloud"],
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "Okta OAuth Credentials",
|
||||
"properties": {
|
||||
"okta_client_id": {
|
||||
"type": "string",
|
||||
"description": "Client ID of the Okta API Services app used for OAuth 2.0 private-key JWT authentication.",
|
||||
},
|
||||
"okta_private_key": {
|
||||
"type": "string",
|
||||
"description": "PEM-encoded private key whose matching public key (JWK) is registered on the Okta service app.",
|
||||
},
|
||||
"okta_scopes": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "OAuth scopes to request. Optional; defaults to the minimum set required to run the currently enabled Okta checks.",
|
||||
},
|
||||
},
|
||||
"required": ["okta_client_id", "okta_private_key"],
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "Vercel API Token",
|
||||
|
||||
@@ -1397,6 +1397,7 @@ class ResourceIncludeSerializer(RLSSerializer):
|
||||
"service",
|
||||
"type_",
|
||||
"tags",
|
||||
"metadata",
|
||||
"details",
|
||||
"partition",
|
||||
]
|
||||
@@ -1404,6 +1405,7 @@ class ResourceIncludeSerializer(RLSSerializer):
|
||||
"id": {"read_only": True},
|
||||
"inserted_at": {"read_only": True},
|
||||
"updated_at": {"read_only": True},
|
||||
"metadata": {"read_only": True},
|
||||
"details": {"read_only": True},
|
||||
"partition": {"read_only": True},
|
||||
}
|
||||
@@ -1543,6 +1545,8 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer):
|
||||
serializer = GCPProviderSecret(data=secret)
|
||||
elif provider_type == Provider.ProviderChoices.GOOGLEWORKSPACE.value:
|
||||
serializer = GoogleWorkspaceProviderSecret(data=secret)
|
||||
elif provider_type == Provider.ProviderChoices.OKTA.value:
|
||||
serializer = OktaProviderSecret(data=secret)
|
||||
elif provider_type == Provider.ProviderChoices.GITHUB.value:
|
||||
serializer = GithubProviderSecret(data=secret)
|
||||
elif provider_type == Provider.ProviderChoices.IAC.value:
|
||||
@@ -1688,6 +1692,15 @@ class GoogleWorkspaceProviderSecret(serializers.Serializer):
|
||||
resource_name = "provider-secrets"
|
||||
|
||||
|
||||
class OktaProviderSecret(serializers.Serializer):
|
||||
okta_client_id = serializers.CharField()
|
||||
okta_private_key = serializers.CharField()
|
||||
okta_scopes = serializers.ListField(child=serializers.CharField(), required=False)
|
||||
|
||||
class Meta:
|
||||
resource_name = "provider-secrets"
|
||||
|
||||
|
||||
class MongoDBAtlasProviderSecret(serializers.Serializer):
|
||||
atlas_public_key = serializers.CharField()
|
||||
atlas_private_key = serializers.CharField()
|
||||
|
||||
@@ -21,6 +21,7 @@ from celery import chain, states
|
||||
from celery.result import AsyncResult
|
||||
from config.custom_logging import BackendLogger
|
||||
from config.env import env
|
||||
from config.version import RELEASE_ID
|
||||
from config.settings.social_login import (
|
||||
GITHUB_OAUTH_CALLBACK_URL,
|
||||
GOOGLE_OAUTH_CALLBACK_URL,
|
||||
@@ -424,7 +425,7 @@ class SchemaView(SpectacularAPIView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
spectacular_settings.TITLE = "Prowler API"
|
||||
spectacular_settings.VERSION = "1.28.0"
|
||||
spectacular_settings.VERSION = RELEASE_ID
|
||||
spectacular_settings.DESCRIPTION = (
|
||||
"Prowler API specification.\n\nThis file is auto-generated."
|
||||
)
|
||||
@@ -7368,6 +7369,15 @@ class FindingGroupViewSet(BaseRLSViewSet):
|
||||
output_field=IntegerField(),
|
||||
)
|
||||
|
||||
# `check_title` / `check_description` are intentionally NOT resolved
|
||||
# here. They live in the large JSONB `check_metadata` blob (TOASTed),
|
||||
# so reading them per finding row is very expensive, and pulling them
|
||||
# in via a correlated subquery makes Django add the subquery to GROUP
|
||||
# BY, which re-evaluates it once per input row. They are identical for
|
||||
# every finding of a `check_id`, so `_post_process_aggregation` fills
|
||||
# them from the summary table's plain columns in a single batched
|
||||
# lookup scoped to the paginated page.
|
||||
|
||||
# `pass_count`, `fail_count` and `manual_count` only count non-muted
|
||||
# findings. Muted findings are tracked separately via the
|
||||
# `*_muted_count` fields.
|
||||
@@ -7438,15 +7448,6 @@ class FindingGroupViewSet(BaseRLSViewSet):
|
||||
agg_failing_since=Min(
|
||||
"first_seen_at", filter=Q(status="FAIL", muted=False)
|
||||
),
|
||||
check_title=Coalesce(
|
||||
Max(KeyTextTransform("checktitle", "check_metadata")),
|
||||
Max(KeyTextTransform("CheckTitle", "check_metadata")),
|
||||
Max(KeyTextTransform("Checktitle", "check_metadata")),
|
||||
),
|
||||
check_description=Coalesce(
|
||||
Max(KeyTextTransform("description", "check_metadata")),
|
||||
Max(KeyTextTransform("Description", "check_metadata")),
|
||||
),
|
||||
)
|
||||
.annotate(
|
||||
# Group is muted only if it has zero non-muted findings.
|
||||
@@ -7502,9 +7503,38 @@ class FindingGroupViewSet(BaseRLSViewSet):
|
||||
- Computes aggregated status (FAIL > PASS > MANUAL); the orthogonal
|
||||
``muted`` boolean is already on the row from the SQL aggregation
|
||||
- Converts provider string to list
|
||||
- Fills check_title / check_description for the findings path
|
||||
"""
|
||||
rows = list(aggregated_data)
|
||||
|
||||
# The findings-aggregation path omits check_title / check_description
|
||||
# (they sit in TOASTed JSONB; see _aggregate_findings). Fill them from
|
||||
# the summary table's plain columns in one query scoped to this page.
|
||||
# The summary-aggregation path already carries them, so skip it there.
|
||||
if rows and "check_title" not in rows[0]:
|
||||
check_ids = [row["check_id"] for row in rows]
|
||||
role = get_role(self.request.user, self.request.tenant_id)
|
||||
summaries = FindingGroupDailySummary.objects.filter(
|
||||
tenant_id=self.request.tenant_id,
|
||||
check_id__in=check_ids,
|
||||
)
|
||||
# Scope to the user's providers, mirroring get_queryset(), so titles
|
||||
# are read only from providers the user can see.
|
||||
if not role.unlimited_visibility:
|
||||
summaries = summaries.filter(provider__in=get_providers(role))
|
||||
metadata_by_check = {
|
||||
item["check_id"]: item
|
||||
for item in summaries.order_by("check_id", "-inserted_at")
|
||||
.distinct("check_id")
|
||||
.values("check_id", "check_title", "check_description")
|
||||
}
|
||||
for row in rows:
|
||||
metadata = metadata_by_check.get(row["check_id"], {})
|
||||
row["check_title"] = metadata.get("check_title")
|
||||
row["check_description"] = metadata.get("check_description")
|
||||
|
||||
results = []
|
||||
for row in aggregated_data:
|
||||
for row in rows:
|
||||
# Convert severity order back to string
|
||||
severity_order = row.get("severity_order", 1)
|
||||
row["severity"] = SEVERITY_ORDER_REVERSE.get(
|
||||
@@ -7550,7 +7580,6 @@ class FindingGroupViewSet(BaseRLSViewSet):
|
||||
|
||||
_FINDING_GROUP_SORT_MAP = {
|
||||
"check_id": "check_id",
|
||||
"check_title": "check_title",
|
||||
"severity": "severity_order",
|
||||
"status": "status_order",
|
||||
"muted": "muted",
|
||||
|
||||
@@ -118,6 +118,8 @@ REST_FRAMEWORK = {
|
||||
"attack-paths-custom-query": env(
|
||||
"DJANGO_THROTTLE_ATTACK_PATHS_CUSTOM_QUERY", default="10/min"
|
||||
),
|
||||
"health-live": env("DJANGO_THROTTLE_HEALTH_LIVE", default="120/min"),
|
||||
"health-ready": env("DJANGO_THROTTLE_HEALTH_READY", default="60/min"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
from django.urls import include, path
|
||||
|
||||
from api.health import LivenessView, ReadinessView
|
||||
|
||||
urlpatterns = [
|
||||
path("api/v1/", include("api.v1.urls")),
|
||||
path("health/live", LivenessView.as_view(), name="health-live"),
|
||||
path("health/ready", ReadinessView.as_view(), name="health-ready"),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Single source of truth for the API version.
|
||||
|
||||
The semantic version is read once from ``api/pyproject.toml`` at module
|
||||
import; consumers (health payload, OpenAPI schema) read the resulting
|
||||
constants. Fails fast at boot if the file cannot be located, so a
|
||||
packaging mistake surfaces immediately rather than serving stale data.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
_PROJECT_NAME = "prowler-api"
|
||||
|
||||
|
||||
def _discover_release_id() -> str:
|
||||
here = Path(__file__).resolve()
|
||||
for directory in here.parents:
|
||||
candidate = directory / "pyproject.toml"
|
||||
if not candidate.is_file():
|
||||
continue
|
||||
with candidate.open("rb") as f:
|
||||
data = tomllib.load(f)
|
||||
project = data.get("project") or {}
|
||||
if project.get("name") != _PROJECT_NAME:
|
||||
continue
|
||||
version = project.get("version")
|
||||
if not isinstance(version, str) or not version:
|
||||
raise RuntimeError(
|
||||
f"{candidate} declares an empty or invalid [project].version"
|
||||
)
|
||||
return version
|
||||
raise RuntimeError(
|
||||
f"Could not locate the {_PROJECT_NAME} pyproject.toml from {here}"
|
||||
)
|
||||
|
||||
|
||||
RELEASE_ID: str = _discover_release_id()
|
||||
# Public contract major (e.g. "1"); matches the /api/v1/ namespace.
|
||||
API_VERSION: str = RELEASE_ID.split(".", 1)[0]
|
||||
@@ -571,6 +571,12 @@ def providers_fixture(tenants_fixture):
|
||||
alias="vercel_testing",
|
||||
tenant_id=tenant.id,
|
||||
)
|
||||
provider14 = Provider.objects.create(
|
||||
provider="okta",
|
||||
uid="acme.okta.com",
|
||||
alias="okta_testing",
|
||||
tenant_id=tenant.id,
|
||||
)
|
||||
|
||||
return (
|
||||
provider1,
|
||||
@@ -586,6 +592,7 @@ def providers_fixture(tenants_fixture):
|
||||
provider11,
|
||||
provider12,
|
||||
provider13,
|
||||
provider14,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Generated
+5
-4
@@ -4410,8 +4410,8 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "prowler"
|
||||
version = "5.27.0"
|
||||
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=master#0abbb7fc590eaf7de6ed354dd5a217bca261d2b0" }
|
||||
version = "5.28.0"
|
||||
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=v5.28#3a096b17504fe8f3f743fdc44148d35b9723df92" }
|
||||
dependencies = [
|
||||
{ name = "alibabacloud-actiontrail20200706" },
|
||||
{ name = "alibabacloud-credentials" },
|
||||
@@ -4484,6 +4484,7 @@ dependencies = [
|
||||
{ name = "pygithub" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "pytz" },
|
||||
{ name = "scaleway" },
|
||||
{ name = "schema" },
|
||||
{ name = "shodan" },
|
||||
{ name = "slack-sdk" },
|
||||
@@ -4494,7 +4495,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prowler-api"
|
||||
version = "1.28.0"
|
||||
version = "1.29.2"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "cartography" },
|
||||
@@ -4590,7 +4591,7 @@ requires-dist = [
|
||||
{ name = "matplotlib", specifier = "==3.10.8" },
|
||||
{ name = "neo4j", specifier = "==6.1.0" },
|
||||
{ name = "openai", specifier = "==1.109.1" },
|
||||
{ name = "prowler", git = "https://github.com/prowler-cloud/prowler.git?rev=master" },
|
||||
{ name = "prowler", git = "https://github.com/prowler-cloud/prowler.git?rev=v5.28" },
|
||||
{ name = "psycopg2-binary", specifier = "==2.9.9" },
|
||||
{ name = "pytest-celery", extras = ["redis"], specifier = "==1.3.0" },
|
||||
{ name = "reportlab", specifier = "==4.4.10" },
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
||||
"name": "prowler",
|
||||
"version": "0.1.0",
|
||||
"description": "Prowler for Claude Code — cloud security and compliance skills powered by the Prowler MCP server. Bundles compliance triage and remediation; more skills coming.",
|
||||
"author": {
|
||||
"name": "Prowler",
|
||||
"email": "support@prowler.com",
|
||||
"url": "https://prowler.com"
|
||||
},
|
||||
"homepage": "https://docs.prowler.com",
|
||||
"repository": "https://github.com/prowler-cloud/prowler",
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"prowler",
|
||||
"security",
|
||||
"compliance",
|
||||
"cloud-security",
|
||||
"mcp"
|
||||
],
|
||||
"userConfig": {
|
||||
"api_key": {
|
||||
"type": "string",
|
||||
"title": "Prowler API key",
|
||||
"description": "API key token used to authenticate with Prowler Cloud / Prowler App via the Prowler MCP server. Create one at https://cloud.prowler.com.",
|
||||
"sensitive": true,
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"prowler": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.prowler.com/mcp",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${user_config.api_key}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
# Prowler for Claude Code
|
||||
|
||||
End-to-end cloud security and compliance from inside [Claude Code](https://www.claude.com/product/claude-code), powered by the [Prowler MCP server](https://docs.prowler.com/projects/prowler-mcp/). The plugin lets Claude walk a Prowler Cloud-connected account through a compliance assessment and remediate findings until the chosen security or industry framework is compliant.
|
||||
|
||||
> **Preview**: this plugin is under active development. Report issues at <https://github.com/prowler-cloud/prowler/issues> or join the [Slack community](https://goto.prowler.com/slack).
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Claude Code](https://www.claude.com/product/claude-code) installed and signed in.
|
||||
- A [Prowler Cloud](https://cloud.prowler.com) account (the free tier is enough to start).
|
||||
- A Prowler API key — create one at <https://cloud.prowler.com/profile>.
|
||||
|
||||
## Installation
|
||||
|
||||
Inside a Claude Code session:
|
||||
|
||||
```text
|
||||
/plugin marketplace add prowler-cloud/prowler
|
||||
/plugin install prowler@prowler-plugins
|
||||
```
|
||||
|
||||
Or, if you already have the repo checked out locally:
|
||||
|
||||
```text
|
||||
/plugin marketplace add /absolute/path/to/prowler
|
||||
/plugin install prowler@prowler-plugins
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
On first install, Claude Code prompts for your **Prowler API key**. It is stored securely (macOS keychain or `~/.claude/.credentials.json`) and used to authenticate against Prowler Cloud.
|
||||
|
||||
To rotate the key, uninstall and reinstall the plugin — Claude Code will prompt again.
|
||||
|
||||
## Verify the install
|
||||
|
||||
In a Claude Code session:
|
||||
|
||||
```text
|
||||
/mcp → "prowler" appears as a connected server
|
||||
/plugin → "prowler" enabled, skill listed as prowler:framework-compliance-triage
|
||||
```
|
||||
|
||||
If `/mcp` reports the `prowler` server as failed, the most common cause is a rejected API key — re-issue one in Prowler Cloud and reinstall the plugin so it re-prompts.
|
||||
|
||||
## Usage
|
||||
|
||||
Open a conversation that mentions the framework you want to comply with. Examples:
|
||||
|
||||
- *"Make my AWS production account compliant with CIS 4.0."*
|
||||
- *"Make my current Terraform project compliant with the Prowler ThreatScore Compliance Framework based on the latest scan results."*
|
||||
- *"Help me get to 100% on PCI-DSS for this GCP project."*
|
||||
|
||||
You pick a **primary tool** (Terraform, gh / az / aws CLI, web console, or mixed) and a **mode**:
|
||||
|
||||
- **Claude-assisted** (default). Claude shows each fix — target resource, exact commands, side effects, reversibility — and waits for your go-ahead before applying.
|
||||
- **Claude autonomous**. Claude presents a single up-front plan grouped by shared fixes, waits for one confirmation, then proceeds. It pauses mid-loop if a fix has wide blast radius or a finding is not applicable.
|
||||
|
||||
Claude tracks progress in a markdown report under `.prowler/` at your project root — one file per framework × account. Open it any time to see exactly where the flow is. When all findings are addressed, Claude proposes a fresh Prowler scan to verify everything end-to-end.
|
||||
|
||||
## Uninstalling
|
||||
|
||||
```text
|
||||
/plugin uninstall prowler@prowler-plugins
|
||||
/plugin marketplace remove prowler-plugins
|
||||
```
|
||||
|
||||
The stored API key is removed automatically.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Likely cause | Fix |
|
||||
|---|---|---|
|
||||
| `/mcp` shows `prowler` as failed | Rejected API key | Generate a new one in Prowler Cloud and reinstall the plugin to re-prompt. |
|
||||
| Skill not invoked when expected | The skill description didn't match the prompt | Mention the framework name plus "compliance" or "compliant" in your prompt. |
|
||||
| "Framework not supported" | Prowler Hub does not list the framework for that provider | Open an issue or PR at <https://github.com/prowler-cloud/prowler>. |
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 — see [LICENSE](../../LICENSE).
|
||||
@@ -0,0 +1,199 @@
|
||||
---
|
||||
name: framework-compliance-triage
|
||||
description: Make a cloud account compliant with a security or industry framework using Prowler Cloud.
|
||||
---
|
||||
|
||||
# Framework compliance
|
||||
|
||||
Iterative, interactive flow that takes a cloud account through setup, reporting, and remediation until it complies with the chosen security or industry framework.
|
||||
|
||||
## Checkpoints
|
||||
|
||||
This skill uses **checkpoints** to mark moments where you must stop, post a clear question or summary to the user, and wait for the reply before continuing. Each checkpoint is rendered like this:
|
||||
|
||||
> **Checkpoint — <name>**
|
||||
>
|
||||
> What to present, and what to wait for.
|
||||
|
||||
Treat every checkpoint as a hard stop:
|
||||
|
||||
- Do not skip a checkpoint because the user previously said "go ahead", "just do it", or similar. Confirmations are scoped to a single checkpoint and do not transfer to later ones.
|
||||
- Do not bundle two checkpoints into one message. Post one, wait for the reply, then continue.
|
||||
- Do not infer the user's answer from context or proceed on silence. Ask explicitly and wait.
|
||||
- If a checkpoint is conditional (e.g. only fires when multiple accounts exist), evaluate the condition first; if it does not apply, continue without prompting.
|
||||
- If the user's initial message already answers the question a checkpoint asks (e.g. "make my AWS subscription compliant with CIS using Terraform autonomously"), treat the checkpoint as satisfied for the parts they covered, and only ask for what is still missing.
|
||||
|
||||
## 1. Initial Prowler Cloud setup
|
||||
|
||||
> **Checkpoint — Provider and framework selection**
|
||||
>
|
||||
> If the user has not already specified both the provider and the framework, ask explicitly and wait for the answer. If they have specified them in their opening message, skip this checkpoint.
|
||||
|
||||
Confirm both are supported by the Prowler Hub MCP:
|
||||
|
||||
- Enumerate supported providers with `prowler_hub_list_providers`.
|
||||
- Enumerate frameworks for the chosen provider with `prowler_hub_list_compliances`, passing the provider `id` as the only element of the `provider` input list.
|
||||
|
||||
If the framework is not supported, tell the user, suggest they request it or contribute it themselves, and end the flow. Otherwise continue.
|
||||
|
||||
### 1.1 Connect to Prowler Cloud
|
||||
|
||||
Verify the Prowler MCP connection by calling `prowler_app_search_providers` — a successful response returns the list of providers. If the call fails, walk the user through troubleshooting: internet connectivity, Prowler Cloud credentials, and permissions on the Prowler Cloud account.
|
||||
For getting accurate information about configurations use `prowler_docs_search` to pull relevant instructions from the Prowler documentation.
|
||||
|
||||
### 1.2 Verify the provider is configured (or configure it)
|
||||
|
||||
Call `prowler_app_search_providers` to check whether the target provider (AWS account, Azure Subscription, GitHub Account...) exists in the user's Prowler Cloud account. Handle the result based on what's found:
|
||||
|
||||
- **Provider not present.** Guide the user through adding and configuring it. Retrieve the relevant connection, credential, and permission instructions with `prowler_docs_search`.
|
||||
- **Provider present but misconfigured** (missing credentials, insufficient permissions, etc.). Walk the user through fixing the configuration, pulling the relevant guidance with `prowler_docs_search`.
|
||||
- **Provider present and configured.** Continue.
|
||||
|
||||
> **Checkpoint — Account selection** *(conditional: more than one account of the chosen provider is configured)*
|
||||
>
|
||||
> List the accounts with helpful detail (account name, uid, last scan date) and ask which one to use. Wait for the answer. If only one account exists, skip this checkpoint and use it.
|
||||
|
||||
### 1.3 Review compliance report for the provider account
|
||||
|
||||
The flow needs at least one completed scan with a compliance report available.
|
||||
|
||||
Look for a completed scan first: call `prowler_app_list_scans` with the selected `provider_id` and `state: ["completed"]`, then call `prowler_app_get_compliance_overview` with each `scan_id` to find one whose compliance report is available. If one is found, continue to the next section.
|
||||
|
||||
If no completed scan has a report, call `prowler_app_list_scans` again with `state: ["available", "executing"]` to detect a scan in progress.
|
||||
|
||||
> **Checkpoint — Scan-in-progress decision** *(conditional: an in-progress scan was detected)*
|
||||
>
|
||||
> Tell the user a scan is already running and ask whether to wait for it to complete or start a fresh one. Wait for the answer.
|
||||
|
||||
If no scan is running (or the user chose to start a fresh one), trigger a new scan with `prowler_app_trigger_scan` and the `provider_id`. The link `https://cloud.prowler.com/scans?filter%5Bprovider_uid__in%5D={provider_id}` lets the user monitor progress.
|
||||
|
||||
When a scan is in progress (either pre-existing and elected to wait, or just triggered), stop the flow and ask the user to return when it's completed — restart this section to re-check the results.
|
||||
|
||||
## 2. Compliance report
|
||||
|
||||
Every iteration of the remediation loop reads and writes a single markdown file per provider account and framework, stored at `${CLAUDE_PROJECT_DIR}/.prowler/compliance-<compliance_id>-<provider_uid>.md`. Sanitize `<provider_uid>` to `[a-zA-Z0-9_-]` by replacing anything else with `-`. Create `.prowler/` if missing.
|
||||
|
||||
Across iterations, edit only: status tags on failed requirements and their findings, the per-requirement `Fix plan` / `Fix applied` sub-bullets added during sections 3.3–3.4, the **Global remediation approach** block, and the **Activity log** (append-only, newest on top). Requirement descriptions, finding IDs, and the entire **Manual review requirements** section are read-only after first render.
|
||||
|
||||
Status taxonomy for failed requirements and their findings:
|
||||
|
||||
- `[FAIL]` — failing in the latest scan.
|
||||
- `[IN PROGRESS]` — picked up by section 3.3.
|
||||
- `[FIXED-UNVERIFIED]` — remediation applied; not yet confirmed.
|
||||
- `[PASS]` — passing in the latest scan (set when a rescan in section 3.5 confirms the fix).
|
||||
- `[SKIPPED]` — user explicitly deferred.
|
||||
|
||||
### Report template
|
||||
|
||||
A fresh report is rendered like this (substituting values from the `prowler_app_get_compliance_framework_state_details` Prowler MCP tool response):
|
||||
|
||||
````markdown
|
||||
# Compliance report: <compliance_id>
|
||||
|
||||
**Provider account**: <display name + uid>
|
||||
**Scan ID**: <scan_id>
|
||||
**Generated**: <ISO timestamp>
|
||||
**Last update**: <ISO timestamp>
|
||||
**Status**: <passed>/<total> passing (<pct>%) · <failed> failing · <manual_review> manual review
|
||||
|
||||
## Global remediation approach
|
||||
<!-- Filled by section 3.1. -->
|
||||
- **Primary tool**: _Terraform | Azure CLI | AWS CLI | web console | mixed_
|
||||
- **Mode**: _Claude autonomous | Claude-assisted_
|
||||
- **Notes**:
|
||||
|
||||
## Activity log
|
||||
- <ISO timestamp> — Report initialized from scan `<scan_id>`.
|
||||
|
||||
## Failed requirements
|
||||
|
||||
### <code> — [FAIL]
|
||||
**Description**: <text>
|
||||
**Findings** (<n>):
|
||||
- [FAIL] `<finding_id>`
|
||||
|
||||
## Manual review requirements
|
||||
- **<code>** — [PENDING]: <description>
|
||||
````
|
||||
|
||||
### 2.1 Generate or refresh the report
|
||||
|
||||
Resolve the report path for the current `compliance_id` and provider account.
|
||||
|
||||
If the file does not exist, call `prowler_app_get_compliance_framework_state_details` for the target scan, render the template above, and write the file with one initialization entry in the activity log.
|
||||
|
||||
If the file exists, read it and compare its `Scan ID` to the target scan from section 1.3. When the scan matches, reuse the file and summarize remaining `[FAIL]` and `[IN PROGRESS]` items in chat.
|
||||
|
||||
> **Checkpoint — Report refresh** *(conditional: the file's `Scan ID` differs from the current target scan)*
|
||||
>
|
||||
> Tell the user the report on disk was generated from a different scan and ask whether to refresh it from the new scan. Wait for the answer.
|
||||
|
||||
On confirmation, regenerate the failed-requirements section from the new `prowler_app_get_compliance_framework_state_details` response, carry forward the **Global remediation approach** block and the full activity log, and append an activity-log entry noting the scan change.
|
||||
|
||||
Once the file is current, surface the top failing requirements in chat: sort by finding count descending, show the top 5 with their codes and counts, and point to the file path for the full list.
|
||||
|
||||
## 3. Remediation loop
|
||||
|
||||
### 3.1 Define the global remediation approach
|
||||
|
||||
Two modes are available:
|
||||
|
||||
- **Claude-assisted** (default when the user has not specified): per-requirement confirmation. For each requirement Claude shows the target resource, exact commands, side effects, and reversibility, then waits for explicit go-ahead before applying.
|
||||
- **Claude autonomous**: no per-requirement gate, but Claude still presents one batch-level fix plan up front (§3.2) and waits for a single confirmation, and pauses if a finding looks not applicable, requires a paid feature, or has wide blast radius (breaks dev workflow, forces collaborator changes, is hard to reverse).
|
||||
|
||||
If the user phrases their request as "just do it" or similar, treat that as autonomous **with** the batch-plan confirmation still required — the confirmation is a property of the skill, not the user's verbosity preference.
|
||||
|
||||
> **Checkpoint — Global remediation approach**
|
||||
>
|
||||
> Ask the user which tool to use for fixes (Terraform, gh / az / aws CLI, web console, mixed...) and which mode to operate in. Wait for the answer before continuing. This checkpoint is non-negotiable: never assume a default tool, and never assume autonomous mode.
|
||||
|
||||
Once answered, write the values into the **Global remediation approach** block of the report file.
|
||||
|
||||
> **Checkpoint — Overwriting an existing approach** *(conditional: the block is already populated from a previous session)*
|
||||
>
|
||||
> Show the previous values and the new ones, and ask the user to confirm before overwriting. Wait for the answer.
|
||||
|
||||
### 3.2 Present the batch fix plan *(autonomous mode only)*
|
||||
|
||||
In **assisted** mode, skip this section — the per-requirement gate in §3.3 confirms each fix as it comes up. Only run §3.2 in **autonomous** mode, where the loop will otherwise apply fixes without further input.
|
||||
|
||||
Before touching anything, post a single chat summary covering every `[FAIL]` requirement:
|
||||
|
||||
- Group findings that share a fix (e.g. ten branch-protection requirements satisfied by one PUT call → present as one group).
|
||||
- For each group: target resource, exact tool calls, side effects, reversibility.
|
||||
- Call out findings that look **not applicable** to this target (e.g. an Organization-only check evaluated against a User account, a feature gated by a paid plan, a resource type the user doesn't have) and propose `[SKIPPED]` with the reason.
|
||||
- Call out findings that require manual user action Claude cannot perform.
|
||||
|
||||
> **Checkpoint — Batch fix plan approval** *(conditional: autonomous mode)*
|
||||
>
|
||||
> Post the grouped plan and wait for explicit confirmation. Do not start any fix before the user replies.
|
||||
|
||||
Once approved, the loop proceeds through the batch without further prompts unless something deviates from the approved plan.
|
||||
|
||||
### 3.3 Pick the first FAIL requirement and inspect its findings
|
||||
|
||||
Pick the first `[FAIL]` requirement at the top of the failed-requirements section. Move its status and every finding under it to `[IN PROGRESS]`, and add a `**Fix plan**:` sub-bullet describing what will be done.
|
||||
|
||||
Call `prowler_app_get_finding_details` for each `finding_id` to retrieve the failing resource and the Prowler Hub's remediation guidance for that check using the tool `prowler_hub_get_check_details` with the `check_id` from the finding details. Summarize the guidance in chat, and append it to the `**Fix plan**` note for each finding.
|
||||
|
||||
If a finding does not apply to the target resource (Organization-only check on a User account, paid-tier feature, missing resource type, etc.), set the requirement status to `[SKIPPED]` with the reason, log it in the activity log, and move on without attempting the fix — even if it was missed during §3.2.
|
||||
|
||||
> **Checkpoint — Per-requirement approval** *(conditional: assisted mode)*
|
||||
>
|
||||
> Post the per-requirement plan in chat — resource, command, side effects, reversibility — and wait for confirmation before moving to §3.4. In **autonomous** mode, post the plan for transparency but proceed unless it deviates from the batch plan agreed in §3.2.
|
||||
|
||||
### 3.4 Diagnose, fix, verify
|
||||
|
||||
Read the remediation guidance returned in §3.3, identify the root cause, and apply the fix using the tool defined in the **Global remediation approach** block. After applying, verify via the same tool that applied the fix or via a provider API call when applicable. If the re-read shows the change did not land, leave the status at `[IN PROGRESS]`, surface the error to the user, and stop the loop for this requirement.
|
||||
|
||||
When the change is in place, append a `**Fix applied**: <tool, summary, refs>` sub-bullet to the requirement, move each fixed finding to `[FIXED-UNVERIFIED]`, and add one activity-log entry describing the change. If no programmatic verification was possible (e.g. web console action), note in the activity log that confirmation depends on the rescan in §3.5.
|
||||
|
||||
### 3.5 Loop
|
||||
|
||||
Move to the next `[FAIL]` requirement and repeat from section 3.3.
|
||||
|
||||
> **Checkpoint — Rescan trigger** *(conditional: no `[FAIL]` requirements remain; all are `[FIXED-UNVERIFIED]` or `[SKIPPED]`)*
|
||||
>
|
||||
> Summarize what was applied, list any `[SKIPPED]` items with reasons, and ask whether to trigger a fresh scan with `prowler_app_trigger_scan` to verify the fixes end-to-end. Wait for the answer.
|
||||
|
||||
On confirmation, trigger the rescan. When it completes, restart section 2.1 with the carry-forward path — requirements no longer in the new FAIL list move to `[PASS]`, anything still failing reverts to `[FAIL]` with the previous fix attempt visible in the activity log.
|
||||
@@ -590,13 +590,16 @@ resources: {}
|
||||
# memory: 128Mi
|
||||
|
||||
# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
|
||||
# /health/live succeeds while the process answers; /health/ready also
|
||||
# checks PostgreSQL, Valkey and Neo4j connectivity and returns 503 when
|
||||
# any of them is unreachable.
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
path: /health/live
|
||||
port: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
path: /health/ready
|
||||
port: http
|
||||
|
||||
#This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/
|
||||
|
||||
@@ -270,20 +270,23 @@ api:
|
||||
# 3m30s to setup DB
|
||||
# startupProbe:
|
||||
# httpGet:
|
||||
# path: /api/v1/docs
|
||||
# path: /health/live
|
||||
# port: http
|
||||
|
||||
# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
|
||||
# /health/live succeeds while the process answers; /health/ready also
|
||||
# checks PostgreSQL, Valkey and Neo4j connectivity and returns 503 when
|
||||
# any of them is unreachable.
|
||||
livenessProbe:
|
||||
failureThreshold: 10
|
||||
httpGet:
|
||||
path: /api/v1/docs
|
||||
path: /health/live
|
||||
port: http
|
||||
periodSeconds: 20
|
||||
readinessProbe:
|
||||
failureThreshold: 10
|
||||
httpGet:
|
||||
path: /api/v1/docs
|
||||
path: /health/ready
|
||||
port: http
|
||||
periodSeconds: 20
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ services:
|
||||
neo4j:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:${DJANGO_PORT:-8080}/api/v1/ || exit 1"]
|
||||
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:${DJANGO_PORT:-8080}/health/live || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
@@ -211,6 +211,7 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
volumes:
|
||||
outputs:
|
||||
|
||||
+2
-1
@@ -33,7 +33,7 @@ services:
|
||||
neo4j:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:${DJANGO_PORT:-8080}/api/v1/ || exit 1"]
|
||||
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:${DJANGO_PORT:-8080}/health/live || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
@@ -176,6 +176,7 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
volumes:
|
||||
output:
|
||||
|
||||
@@ -73,6 +73,12 @@
|
||||
"getting-started/products/prowler-lighthouse-ai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Prowler for Claude Code",
|
||||
"pages": [
|
||||
"getting-started/products/prowler-claude-code-plugin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Prowler MCP Server",
|
||||
"pages": [
|
||||
@@ -326,6 +332,13 @@
|
||||
"user-guide/providers/openstack/authentication"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Scaleway",
|
||||
"pages": [
|
||||
"user-guide/providers/scaleway/getting-started-scaleway",
|
||||
"user-guide/providers/scaleway/authentication"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Vercel",
|
||||
"pages": [
|
||||
|
||||
@@ -118,8 +118,8 @@ To update the environment file:
|
||||
Edit the `.env` file and change version values:
|
||||
|
||||
```env
|
||||
PROWLER_UI_VERSION="5.26.1"
|
||||
PROWLER_API_VERSION="5.26.1"
|
||||
PROWLER_UI_VERSION="5.27.0"
|
||||
PROWLER_API_VERSION="5.27.0"
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: 'Prowler for Claude Code'
|
||||
---
|
||||
|
||||
End-to-end cloud security and compliance from inside [Claude Code](https://www.claude.com/product/claude-code), powered by the [Prowler MCP server](/getting-started/products/prowler-mcp). The plugin lets Claude walk a Prowler Cloud-connected account through a compliance assessment and remediate findings until the chosen security or industry framework is compliant.
|
||||
|
||||
<Warning>
|
||||
**Preview**: this plugin is under active development. Please report issues on [GitHub](https://github.com/prowler-cloud/prowler/issues) or join the [Slack community](https://goto.prowler.com/slack) for feedback.
|
||||
</Warning>
|
||||
|
||||
## Requirements
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Claude Code" icon="terminal">
|
||||
Installed and signed in. See the [official install guide](https://www.claude.com/product/claude-code).
|
||||
</Card>
|
||||
<Card title="Prowler Cloud account" icon="cloud">
|
||||
The free tier is enough to start. Sign up at [cloud.prowler.com](https://cloud.prowler.com).
|
||||
</Card>
|
||||
<Card title="Prowler API key" icon="key">
|
||||
Create one at [cloud.prowler.com/profile](https://cloud.prowler.com/profile).
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs>
|
||||
<Tab title="From GitHub (recommended)">
|
||||
Inside a Claude Code session:
|
||||
|
||||
```text
|
||||
/plugin marketplace add prowler-cloud/prowler
|
||||
/plugin install prowler@prowler-plugins
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="From a local clone">
|
||||
If you already have the repository checked out:
|
||||
|
||||
```text
|
||||
/plugin marketplace add /absolute/path/to/prowler
|
||||
/plugin install prowler@prowler-plugins
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Configuration
|
||||
|
||||
On first install, Claude Code prompts for your **Prowler API key**. The value is stored securely (macOS keychain or `~/.claude/.credentials.json`) and used to authenticate against Prowler Cloud.
|
||||
|
||||
<Note>
|
||||
To rotate the key, uninstall and reinstall the plugin — Claude Code will prompt again.
|
||||
</Note>
|
||||
|
||||
## Verify the installation
|
||||
|
||||
In a Claude Code session:
|
||||
|
||||
```text
|
||||
/mcp → "prowler" appears as a connected server
|
||||
/plugin → "prowler" enabled, skill listed as prowler:framework-compliance-triage
|
||||
```
|
||||
|
||||
If `/mcp` reports the `prowler` server as failed, the most common cause is a rejected API key — re-issue one in Prowler Cloud and reinstall the plugin so it re-prompts.
|
||||
|
||||
## Usage
|
||||
|
||||
Open a conversation that mentions the framework you want to comply with. Examples:
|
||||
|
||||
- *"Make my AWS production account compliant with CIS 4.0."*
|
||||
- *"Make my current Terraform project compliant with Prowler ThreatScore Compliance Framework based on the latest scan results."*
|
||||
- *"Help me get to 100% on PCI-DSS for this GCP project."*
|
||||
|
||||
You pick a **primary tool** (Terraform, gh / az / aws CLI, web console, or mixed) and a **mode**:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Claude-assisted (default)" icon="hand">
|
||||
Claude shows each fix — target resource, exact commands, side effects, reversibility — and waits for your go-ahead before applying.
|
||||
</Card>
|
||||
<Card title="Claude autonomous" icon="robot">
|
||||
Claude presents a single up-front plan grouped by shared fixes, waits for one confirmation, then proceeds. It pauses mid-loop if a fix has wide blast radius or a finding is not applicable.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
Claude tracks progress in a markdown report under `.prowler/` at your project root — one file per framework × account. Open it any time to see exactly where the flow is. When all findings are addressed, Claude proposes a fresh Prowler scan to verify everything end-to-end.
|
||||
|
||||
## Uninstalling
|
||||
|
||||
```text
|
||||
/plugin uninstall prowler@prowler-plugins
|
||||
/plugin marketplace remove prowler-plugins
|
||||
```
|
||||
|
||||
The stored API key is removed automatically.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Likely cause | Fix |
|
||||
| --- | --- | --- |
|
||||
| `/mcp` shows `prowler` as failed | Rejected API key | Generate a new one in Prowler Cloud and reinstall the plugin to re-prompt. |
|
||||
| Skill not invoked when expected | The skill description didn't match the prompt | Mention the framework name plus "compliance" or "compliant" in your prompt. |
|
||||
| "Framework not supported" | Prowler Hub does not list the framework for that provider | Open an issue or PR at [github.com/prowler-cloud/prowler](https://github.com/prowler-cloud/prowler). |
|
||||
@@ -32,9 +32,10 @@ Prowler supports a wide range of providers organized by category:
|
||||
| [Azure](/user-guide/providers/azure/getting-started-azure) | Official | Subscriptions | UI, API, CLI |
|
||||
| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare) | Official | Accounts | UI, API, CLI |
|
||||
| [Google Cloud](/user-guide/providers/gcp/getting-started-gcp) | Official | Projects | UI, API, CLI |
|
||||
| **NHN** | Unofficial | Tenants | CLI |
|
||||
| **NHN** | [Contact us](https://prowler.com/contact) | 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) | Organizations | CLI |
|
||||
|
||||
### Infrastructure as Code Providers
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ Prowler Cloud runs on AWS with high availability built in.
|
||||
| Region | URL | Location |
|
||||
|--------|-----|----------|
|
||||
| **EU** | [cloud.prowler.com](https://cloud.prowler.com) | Ireland (`eu-west-1`) |
|
||||
| **US** | On-Demand | On-Demand |
|
||||
|
||||
|
||||
## Business Continuity
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ All Prowler code goes through the same security pipeline, whether running on Pro
|
||||
Security tools and practices applied to all Prowler code.
|
||||
</Card>
|
||||
|
||||
## Prowler Cloud vs Self-Managed
|
||||
## Prowler Cloud vs Prowler OSS (Self-Managed)
|
||||
|
||||
| | Prowler Cloud | Self-Managed |
|
||||
|--|---------------|--------------|
|
||||
|
||||
@@ -2,97 +2,205 @@
|
||||
title: 'Software Security'
|
||||
---
|
||||
|
||||
Prowler follows a **security-by-design approach** throughout the software development lifecycle. All changes go through automated checks at every stage, from local development to production deployment.
|
||||
Prowler applies security-by-design across the development lifecycle. Every change passes automated checks at each stage: pre-commit hooks locally, multiple CI gates on pull requests, branch protection before merge, container scanning before publish, and registry monitoring after release.
|
||||
|
||||
[Pre-commit](https://github.com/prowler-cloud/prowler/blob/master/.pre-commit-config.yaml) validations catch issues early, and [CI/CD pipelines](https://github.com/prowler-cloud/prowler/tree/master/.github) include multiple security gates ensuring code quality, secure configurations, and compliance with internal standards.
|
||||
All security tooling and configuration lives in the [Prowler GitHub repository](https://github.com/prowler-cloud/prowler): [pre-commit hooks](https://github.com/prowler-cloud/prowler/blob/master/.pre-commit-config.yaml), [CI/CD workflows](https://github.com/prowler-cloud/prowler/tree/master/.github/workflows), and [Dependabot configuration](https://github.com/prowler-cloud/prowler/blob/master/.github/dependabot.yml).
|
||||
|
||||
Container registries are continuously scanned for vulnerabilities, with findings automatically reported to the security team for assessment and remediation. This process evolves alongside the stack as new languages, frameworks, and technologies are adopted, ensuring security practices remain comprehensive, proactive, and adaptable.
|
||||
## Coverage
|
||||
|
||||
Security controls cover six domains, each detailed below:
|
||||
|
||||
| Domain | What It Protects |
|
||||
|--------|------------------|
|
||||
| [**CI/CD**](#cicd-security) | GitHub Actions workflows, runners, third-party actions |
|
||||
| [**SAST**](#static-application-security-testing-sast) | Application source code |
|
||||
| [**SCA**](#software-composition-analysis-sca) | Third-party dependencies and their known vulnerabilities |
|
||||
| [**Supply-Chain Pinning**](#supply-chain-pinning) | Reproducible installs across Python, npm, GitHub Actions, container base images |
|
||||
| [**Containers**](#container-security) | Runtime images for UI, API, SDK, Model Context Protocol (MCP) Server |
|
||||
| [**Secrets**](#secrets-detection) | Credentials, tokens, API keys in code and git history |
|
||||
|
||||
## CI/CD Security
|
||||
|
||||
Every GitHub Actions workflow uses runner hardening, pinned action versions, and audited permissions.
|
||||
|
||||
### Runner Hardening With StepSecurity
|
||||
|
||||
- [**`step-security/harden-runner`**](https://github.com/step-security/harden-runner) runs as the first step in every workflow, pinned by commit SHA.
|
||||
- Workflows are being migrated to explicit egress controls: some already declare an egress allow-list with `egress-policy: block`, while others still run in `egress-policy: audit` until their allowed endpoints are fully defined.
|
||||
- **Global Block Policy** (StepSecurity) blocks known-malicious domains and IP addresses across every workflow run. This protection applies even in audit mode, so workflows that have not yet moved to `block` still resist known-bad egress.
|
||||
|
||||
### Third-Party Action Pinning
|
||||
|
||||
- Every third-party action reference uses a commit SHA with the version as a comment: `uses: org/action@<sha> # v1.2.3`.
|
||||
- Dependabot tracks the comment and proposes SHA-pinned upgrades on a monthly cadence.
|
||||
|
||||
### Workflow Permissions
|
||||
|
||||
- Workflows declare `permissions: {}` at the top level and grant the minimum required scopes per job.
|
||||
- Code review covers permission changes; zizmor enforces the rules (see below).
|
||||
|
||||
### Workflow Security Audit With Zizmor
|
||||
|
||||
- **[zizmor](https://github.com/zizmorcore/zizmor)** audits every workflow file for known security anti-patterns. Runs via [`ci-zizmor.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/ci-zizmor.yml).
|
||||
- Triggers on every push, every pull request that touches `.github/`, and on a daily schedule.
|
||||
- Results upload to the GitHub Security tab via Static Analysis Results Interchange Format (SARIF).
|
||||
- Key [audit rules](https://docs.zizmor.sh/audits/) the build gates on:
|
||||
- **[PWN Request](https://docs.zizmor.sh/audits/#dangerous-triggers)** (`dangerous-triggers`): unsafe use of `pull_request_target` with checked-out PR code.
|
||||
- **[Script Injection](https://docs.zizmor.sh/audits/#template-injection)** (`template-injection`): unsanitized `${{ github.event.* }}` expressions in `run:` blocks.
|
||||
- **[artipacked](https://docs.zizmor.sh/audits/#artipacked)**: credential leakage through artifacts.
|
||||
- **[Excessive permissions](https://docs.zizmor.sh/audits/#excessive-permissions)** (`excessive-permissions`): workflows with unneeded `write` scopes.
|
||||
|
||||
### Branch Protection
|
||||
|
||||
Pull requests to `master` and the active `v5.*` release branches must pass several required workflows before merge. These gates prevent specific classes of supply-chain and pipeline attacks from reaching the main branch:
|
||||
|
||||
- **Compromised packages:** the **npm Package Compromised Updates** and **PyPI Package Compromised Updates** checks (StepSecurity) fail any PR that introduces a package version present in the compromised-package feed. Layered on top of osv-scanner.
|
||||
- **Premature releases:** the **npm Package Cooldown** and **PyPI Package Cooldown** checks (StepSecurity) fail any PR that introduces a package version published within the cooldown window. Layered on top of pnpm's `minimumReleaseAge`.
|
||||
- **Workflow exploitation:** the **PWN Request** and **Script Injection** checks (StepSecurity) reject the corresponding zizmor-detected anti-patterns at PR time. Layered on top of zizmor's audit.
|
||||
- **Vulnerable code or dependencies:** CodeQL (UI, API, SDK), osv-scanner (SDK, API, UI), Bandit (SDK, API), and Trivy (container images) must all pass.
|
||||
|
||||
## Static Application Security Testing (SAST)
|
||||
|
||||
Multiple SAST tools are employed across the codebase to identify security vulnerabilities, code quality issues, and potential bugs during development.
|
||||
Multiple SAST tools run on every push and pull request to catch vulnerabilities and code-quality issues before merge.
|
||||
|
||||
### CodeQL Analysis
|
||||
### Cross-Language
|
||||
|
||||
- **Scope:** UI (JavaScript/TypeScript), API (Python), and SDK (Python)
|
||||
- **Frequency:** On every push and pull request, plus daily scheduled scans
|
||||
- **Integration:** Results uploaded to GitHub Security tab via SARIF format
|
||||
- **Purpose:** Identifies security vulnerabilities, coding errors, and potential exploits in source code
|
||||
- **CodeQL:** semantic code analysis for the UI (JavaScript/TypeScript), API (Python), and SDK (Python). Runs on every push and pull request, plus a daily scheduled scan, via [`sdk-codeql.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/sdk-codeql.yml), [`api-codeql.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/api-codeql.yml), and [`ui-codeql.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/ui-codeql.yml). Results upload to the GitHub Security tab via SARIF.
|
||||
|
||||
### Python Security Scanners
|
||||
### Python (SDK + API)
|
||||
|
||||
- **Bandit:** Detects common security issues in Python code (SQL injection, hardcoded passwords, etc.)
|
||||
- Configured to ignore test files and report only high-severity issues
|
||||
- Runs on both SDK and API codebases
|
||||
- **Pylint:** Static code analysis with security-focused checks
|
||||
- Integrated into pre-commit hooks and CI/CD pipelines
|
||||
- **Bandit:** detects common Python security issues (SQL injection, hardcoded credentials, insecure deserialization). Runs in pre-commit and on every PR/push in [`sdk-security.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/sdk-security.yml) and [`api-security.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/api-security.yml).
|
||||
- **Pylint:** analyzes your code without actually running it. It checks for errors, enforces a coding standard, looks for code smells, and can suggest refactors. Runs in pre-commit and on every PR/push in [`sdk-code-quality.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/sdk-code-quality.yml) and [`api-code-quality.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/api-code-quality.yml).
|
||||
- **Vulture:** dead-code detection at `--min-confidence 100`. Unused code can hide incomplete implementations or stale security paths. Runs in pre-commit and on every PR/push in `sdk-security.yml` and `api-security.yml`.
|
||||
- **Flake8:** style and correctness checks for the SDK. Runs in pre-commit and on every PR/push in [`sdk-code-quality.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/sdk-code-quality.yml).
|
||||
|
||||
### Code Quality & Dead Code Detection
|
||||
### JavaScript/TypeScript (UI)
|
||||
|
||||
- **Vulture:** Identifies unused code that could indicate incomplete implementations or security gaps
|
||||
- **Flake8:** Style guide enforcement with security-relevant checks
|
||||
- **Shellcheck:** Security and correctness checks for shell scripts
|
||||
- **TypeScript (`tsc`):** strict type checking for the UI. Catches whole classes of null/undefined and type-confusion bugs at build time. Runs on every PR/push via `pnpm run healthcheck` in [`ui-tests.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/ui-tests.yml).
|
||||
- **ESLint:** UI linting with a capped warning budget (`--max-warnings 40`). Runs on every PR/push via `pnpm run healthcheck` in `ui-tests.yml`.
|
||||
- **Knip:** dead-code and unused-export detection for the UI. The UI analogue to Vulture.
|
||||
|
||||
<Note>
|
||||
Knip runs locally on demand via `pnpm run lint:knip` and is not yet wired into CI.
|
||||
</Note>
|
||||
|
||||
### Shell
|
||||
|
||||
- **Shellcheck:** correctness and security checks for shell scripts in `.github/scripts/` and `scripts/`. Runs in pre-commit on staged files.
|
||||
|
||||
## Software Composition Analysis (SCA)
|
||||
|
||||
Dependencies are continuously monitored for known vulnerabilities with timely updates ensured.
|
||||
Dependencies are scanned against public vulnerability databases on every pull request and push, with results posted directly on the PR.
|
||||
|
||||
### Dependency Vulnerability Scanning
|
||||
### Cross-Language
|
||||
|
||||
- **osv-scanner:** Scans lockfiles against the [OSV.dev](https://osv.dev) vulnerability database
|
||||
- Runs in CI on every pull request and push for SDK, API, and UI
|
||||
- Fails the build on `HIGH`, `CRITICAL`, and `UNKNOWN` severity findings
|
||||
- Posts a per-lockfile report as a PR comment
|
||||
- Per-vulnerability ignores (with reason and expiry) live in `osv-scanner.toml` at the repo root
|
||||
- **Trivy:** Multi-purpose scanner for containers and dependencies
|
||||
- Scans all container images (UI, API, SDK, MCP Server)
|
||||
- Checks for vulnerabilities in OS packages and application dependencies
|
||||
- Reports findings to GitHub Security tab
|
||||
- **osv-scanner:** scans lockfiles against the [OSV.dev](https://osv.dev) vulnerability database for SDK (`uv.lock`), API (`api/uv.lock`), and UI (`ui/pnpm-lock.yaml`). Runs via [`sdk-security.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/sdk-security.yml), [`api-security.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/api-security.yml), and [`ui-security.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/ui-security.yml).
|
||||
- The action installs the `osv-scanner` binary and verifies its SHA-256 checksum against the upstream-signed `SHA256SUMS` manifest before running. Any mismatch aborts the scan.
|
||||
- Gates the build on `HIGH`, `CRITICAL`, and `UNKNOWN` severity findings.
|
||||
- Posts and updates a per-lockfile report as a pull request comment.
|
||||
- Per-vulnerability ignores live in [`osv-scanner.toml`](https://github.com/prowler-cloud/prowler/blob/master/osv-scanner.toml) at the repo root, each with a reason and an expiry date.
|
||||
- **Trivy:** scans container images for OS-package and application-dependency vulnerabilities. Runs in [`sdk-container-checks.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/sdk-container-checks.yml), [`api-container-checks.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/api-container-checks.yml), [`ui-container-checks.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/ui-container-checks.yml), and [`mcp-container-checks.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/mcp-container-checks.yml). Trivy uploads SARIF to the GitHub Security tab and posts a scan summary on the PR.
|
||||
- **Dependabot:** [configured](https://github.com/prowler-cloud/prowler/blob/master/.github/dependabot.yml) for monthly updates of the SDK Python dependencies, GitHub Actions, Docker base images, and pre-commit hooks. Dependabot opens pull requests for known security advisories, so critical patches reach the team without delay. A 7-day default cooldown reduces exposure to compromised package releases.
|
||||
- **Renovate:** [configured](https://github.com/prowler-cloud/prowler/blob/master/.github/renovate.json) dependency update automation is transitioning from Dependabot to **Renovate** to gain finer control over update cadence, grouping, and per-component scope. Both tools currently run in parallel during the migration.
|
||||
|
||||
### Automated Dependency Updates
|
||||
#### Renovate (Primary)
|
||||
|
||||
- **Dependabot:** Automated pull requests for dependency updates
|
||||
- **Python (pip):** Monthly updates for SDK
|
||||
- **GitHub Actions:** Monthly updates for workflow dependencies
|
||||
- **Docker:** Monthly updates for base images
|
||||
- Temporarily paused for API and UI to maintain stability during active development
|
||||
- **Security-first approach:** Even when paused, Dependabot automatically creates pull requests for security vulnerabilities, ensuring critical security patches are never delayed
|
||||
Configuration: [`.github/renovate.json`](https://github.com/prowler-cloud/prowler/blob/master/.github/renovate.json)
|
||||
|
||||
- **Coverage:** Python (SDK, API, MCP Server), npm (UI), GitHub Actions, Docker images, and Pre-commit hooks
|
||||
- **Range Strategy:** Versions are pinned to ensure reproducible builds
|
||||
- **Vulnerability Alerts:** GitHub Security Advisories generate immediate pull requests that bypass rate limits and scheduled windows, labeled `security` for prioritized triage
|
||||
|
||||
#### Dependabot (Legacy)
|
||||
|
||||
Configuration: [`.github/dependabot.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/dependabot.yml)
|
||||
|
||||
Dependabot remains active for the SDK and shared automation ecosystems until the Renovate migration completes:
|
||||
|
||||
- **Python (pip):** Monthly updates for SDK
|
||||
- **GitHub Actions:** Monthly updates for workflow dependencies
|
||||
- **Docker:** Monthly updates for base images
|
||||
- **Pre-commit:** Monthly updates for hook revisions
|
||||
|
||||
Dependabot is paused for the API and UI; Renovate now handles those components. Even when paused, Dependabot continues to open pull requests for security vulnerabilities, ensuring critical patches are never delayed.
|
||||
|
||||
### JavaScript/TypeScript (UI)
|
||||
|
||||
- **pnpm audit:** runs `pnpm audit --audit-level critical` on every UI pull request and push as part of `pnpm run audit` in [`ui-tests.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/ui-tests.yml). Cross-checks the npm registry's advisory database in addition to the OSV scan and surfaces npm-specific advisories that may not yet have an OSV identifier.
|
||||
|
||||
## Supply-Chain Pinning
|
||||
|
||||
Pinning runs across Python, npm, GitHub Actions, and container base images. Every install resolves to the exact set of versions already vetted in CI, and any drift fails loudly instead of slipping in silently.
|
||||
|
||||
### Python (uv)
|
||||
|
||||
The SDK, API, and MCP Server all use [uv](https://docs.astral.sh/uv/) for dependency management. Each component has its own project manifest and lock file:
|
||||
|
||||
| Component | Manifest | Lock File |
|
||||
|-----------|----------|-----------|
|
||||
| SDK | `pyproject.toml` | `uv.lock` |
|
||||
| API | `api/pyproject.toml` | `api/uv.lock` |
|
||||
| MCP Server | `mcp_server/pyproject.toml` | `mcp_server/uv.lock` |
|
||||
|
||||
The controls applied across all three:
|
||||
|
||||
- **Direct dependencies pinned to exact versions** (`==`). No version ranges in dependency lists.
|
||||
- **Transitive dependencies pinned** via `[tool.uv].constraint-dependencies` in the SDK and API manifests. The constraint set mirrors the versions locked in the corresponding `uv.lock`. A future `uv lock` preserves these versions instead of silently picking up newer releases, and the resolver fails when a constraint becomes infeasible, signaling that a deliberate bump is needed.
|
||||
- **Lock files committed.** CI installs strictly from the lock.
|
||||
- **uv itself pinned** in the [`setup-python-uv`](https://github.com/prowler-cloud/prowler/tree/master/.github/actions/setup-python-uv) composite action.
|
||||
|
||||
<Note>
|
||||
The MCP Server has a small direct-dependency surface and does not yet declare a separate constraint set. Its lock file is the source of truth.
|
||||
</Note>
|
||||
|
||||
### JavaScript/TypeScript (pnpm)
|
||||
|
||||
The UI uses [pnpm](https://pnpm.io) with supply-chain controls configured in [`ui/pnpm-workspace.yaml`](https://github.com/prowler-cloud/prowler/blob/master/ui/pnpm-workspace.yaml).
|
||||
|
||||
- **Minimum release age** (`minimumReleaseAge: 1440`): packages must publish at least 24 hours before install. This reduces exposure during the window when a compromised release has not yet been detected and yanked.
|
||||
- **Lifecycle script allow-list** (`strictDepBuilds: true` + `allowBuilds`): only explicitly approved packages may run `install` or `postinstall` scripts (currently `sharp`, `esbuild`, `@sentry/cli`, `@heroui/shared-utils`, `unrs-resolver`, `msw`). Any unlisted package with lifecycle scripts fails the install.
|
||||
- **Trust policy** (`trustPolicy: no-downgrade`): the install fails when a package's trust evidence drops, for example after a new publisher takes over.
|
||||
- **Block exotic subdeps** (`blockExoticSubdeps: true`): transitive dependencies cannot ship as git URLs or tarballs. Every package in the tree resolves from the configured registry.
|
||||
- **Transitive overrides** in [`ui/package.json`](https://github.com/prowler-cloud/prowler/blob/master/ui/package.json) force specific versions for transitive packages (`lodash`, `serialize-javascript`, `qs`, `rollup`, `minimatch`, `ajv`, and others).
|
||||
- **`pnpm-lock.yaml` committed** and CI installs strictly from the lock.
|
||||
- **pnpm itself pinned** via the `packageManager` field in `package.json` with an integrity hash.
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
- Every third-party action reference uses a commit SHA, with the version in a trailing comment.
|
||||
- Dependabot opens monthly PRs to bump pinned SHAs.
|
||||
|
||||
### Container Base Images
|
||||
|
||||
- Every Dockerfile references base images by digest (`image@sha256:...`).
|
||||
- Dependabot opens monthly PRs to bump digests.
|
||||
|
||||
## Container Security
|
||||
|
||||
All container images are scanned before deployment.
|
||||
Container images get scanned twice: once in CI before they push to a registry, and continuously after publish by the registries themselves.
|
||||
|
||||
### Trivy Vulnerability Scanning
|
||||
### Pre-Publish (CI)
|
||||
|
||||
- Scans images for vulnerabilities and misconfigurations
|
||||
- Generates SARIF reports uploaded to GitHub Security tab
|
||||
- Creates PR comments with scan summaries
|
||||
- Configurable to fail builds on critical findings
|
||||
- Reports include CVE counts and remediation guidance
|
||||
- **Trivy** scans for OS-package and application-dependency vulnerabilities. Runs in [`sdk-container-checks.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/sdk-container-checks.yml), [`api-container-checks.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/api-container-checks.yml), [`ui-container-checks.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/ui-container-checks.yml), and [`mcp-container-checks.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/mcp-container-checks.yml). Trivy uploads SARIF to the GitHub Security tab and posts a summary on the PR. Builds can fail on critical findings when configured to.
|
||||
- **Hadolint** validates Dockerfile syntax and structure against secure-build best practices. Runs in pre-commit and in the same `*-container-checks.yml` workflows linked above.
|
||||
|
||||
### Hadolint
|
||||
### Post-Publish (Registries)
|
||||
|
||||
- Validates Dockerfile syntax and structure
|
||||
- Ensures secure image building practices
|
||||
- **Amazon ECR:** ECR continuously scans published images for vulnerabilities. New advisories disclosed after publish surface on the image without requiring a rebuild.
|
||||
- **Docker Hub:** Docker Hub continuously scans the same images mirrored from ECR.
|
||||
- The security team reviews findings from both registries for triage and remediation.
|
||||
|
||||
## Secrets Detection
|
||||
|
||||
Prowler protects against accidental exposure of sensitive credentials.
|
||||
|
||||
### TruffleHog
|
||||
|
||||
- Scans entire codebase and Git history for secrets
|
||||
- Runs on every push and pull request
|
||||
- Pre-commit hook prevents committing secrets
|
||||
- Detects high-entropy strings, API keys, tokens, and credentials
|
||||
- Configured to report verified and unknown findings
|
||||
- **[TruffleHog](https://github.com/trufflesecurity/trufflehog)** scans the codebase and git history on every push and pull request via [`find-secrets.yml`](https://github.com/prowler-cloud/prowler/blob/master/.github/workflows/find-secrets.yml). Detects high-entropy strings, API keys, tokens, and credentials, and reports verified and unknown findings.
|
||||
- A pre-commit hook runs the same check locally and blocks secrets before they leave the developer machine.
|
||||
|
||||
## Security Monitoring
|
||||
|
||||
- **GitHub Security Tab:** Centralized view of all security findings from CodeQL, Trivy, and other SARIF-compatible tools
|
||||
- **Artifact Retention:** Security scan reports retained for post-deployment analysis
|
||||
- **PR Comments:** Automated security feedback on pull requests for rapid remediation
|
||||
- **GitHub Security tab:** centralized view of findings from CodeQL, Trivy, zizmor, and any other SARIF-compatible tool.
|
||||
- **PR comments:** osv-scanner and Trivy post per-PR summaries so issues surface during review, not after merge.
|
||||
- **Artifact retention:** the build retains security scan reports for post-deployment analysis.
|
||||
|
||||
## Contact
|
||||
|
||||
For questions regarding software security, visit the [Support page](/support).
|
||||
For questions about software security, see the [Support page](/support). To report a vulnerability, follow the [responsible disclosure process](https://prowler.com/.well-known/security.txt).
|
||||
|
||||
@@ -91,6 +91,7 @@ The following list includes all the Azure checks with configurable variables tha
|
||||
| `sqlserver_recommended_minimal_tls_version` | `recommended_minimal_tls_versions` | List of Strings |
|
||||
| `vm_sufficient_daily_backup_retention_period` | `vm_backup_min_daily_retention_days` | Integer |
|
||||
| `vm_desired_sku_size` | `desired_vm_sku_sizes` | List of Strings |
|
||||
| `storage_smb_channel_encryption_with_secure_algorithm` | `recommended_smb_channel_encryption_algorithms` | List of Strings |
|
||||
| `defender_attack_path_notifications_properly_configured` | `defender_attack_path_minimal_risk_level` | String |
|
||||
| `apim_threat_detection_llm_jacking` | `apim_threat_detection_llm_jacking_threshold` | Float |
|
||||
| `apim_threat_detection_llm_jacking` | `apim_threat_detection_llm_jacking_minutes` | Integer |
|
||||
@@ -534,6 +535,18 @@ azure:
|
||||
"1.3"
|
||||
]
|
||||
|
||||
# Azure Storage
|
||||
# azure.storage_smb_channel_encryption_with_secure_algorithm
|
||||
# List of SMB channel encryption algorithms allowed on file shares. A storage
|
||||
# account passes only if every enabled algorithm is in this list. Defaults to
|
||||
# the value required by CIS (AES-256-GCM only, excluding weaker AES-128 ciphers).
|
||||
recommended_smb_channel_encryption_algorithms:
|
||||
[
|
||||
"AES-256-GCM",
|
||||
# "AES-128-CCM",
|
||||
# "AES-128-GCM",
|
||||
]
|
||||
|
||||
# Azure Virtual Machines
|
||||
# azure.vm_desired_sku_size
|
||||
# List of desired VM SKU sizes that are allowed in the organization
|
||||
|
||||
@@ -30,15 +30,17 @@ If a different authentication method is needed (SSWS API token, OAuth with user
|
||||
|
||||
### Required OAuth Scopes
|
||||
|
||||
For the initial check (`signon_global_session_idle_timeout_15min`) only one scope is required:
|
||||
The bundled signon checks require the following read-only scopes:
|
||||
|
||||
- `okta.policies.read`
|
||||
- `okta.brands.read`
|
||||
|
||||
Additional scopes will be needed as more services and checks are added, this are the current ones needed:
|
||||
Additional scopes will be needed as more services and checks are added. These are the current ones needed:
|
||||
|
||||
| Scope | Used by |
|
||||
|---|---|
|
||||
| `okta.policies.read` | Sign-on / password / authentication policies |
|
||||
| `okta.brands.read` | Sign-in page customizations (DOD Notice and Consent Banner check) |
|
||||
|
||||
### Required Admin Role
|
||||
|
||||
@@ -96,7 +98,7 @@ Okta displays the private key **only once**. If you close the modal without copy
|
||||
|
||||
### 5. Grant the required OAuth scopes
|
||||
|
||||
On the app, open the **Okta API Scopes** tab and click **Grant** on every scope Prowler needs. For the initial release, granting only `okta.policies.read` is sufficient.
|
||||
On the app, open the **Okta API Scopes** tab and click **Grant** on every scope Prowler needs. The bundled signon checks require `okta.policies.read` and `okta.brands.read`.
|
||||
|
||||

|
||||
|
||||
@@ -130,8 +132,8 @@ export OKTA_PRIVATE_KEY_FILE="/secure/path/to/prowler-okta.pem"
|
||||
# or
|
||||
export OKTA_PRIVATE_KEY="$(cat /secure/path/to/prowler-okta.pem)"
|
||||
|
||||
# Optional — defaults to "okta.policies.read"
|
||||
export OKTA_SCOPES="okta.policies.read"
|
||||
# Optional — defaults to "okta.policies.read,okta.brands.read"
|
||||
export OKTA_SCOPES="okta.policies.read,okta.brands.read"
|
||||
|
||||
uv run python prowler-cli.py okta
|
||||
```
|
||||
@@ -172,7 +174,7 @@ Prowler validates credentials at startup by listing one sign-on policy. This err
|
||||
|
||||
Raised when the credential probe succeeds at the OAuth layer but the request is rejected because the service app lacks the required scope or admin role:
|
||||
|
||||
- **`invalid_scope`** — the `okta.policies.read` scope is not granted on the service app. Grant it from **Okta API Scopes**.
|
||||
- **`invalid_scope`** — one of the requested scopes (`okta.policies.read` or `okta.brands.read`) is not granted on the service app. Grant the missing scope from **Okta API Scopes**.
|
||||
- **`Forbidden` / `not authorized`** — the **Read-Only Administrator** role is not assigned to the service app. Assign it from **Admin roles**.
|
||||
|
||||
### `invalid_dpop_proof`
|
||||
|
||||
@@ -12,7 +12,7 @@ Set up authentication for Okta with the [Okta Authentication](/user-guide/provid
|
||||
|
||||
- An Okta organization. The UI examples below use **Identity Engine** terminology such as **Global Session Policy**; Classic Engine exposes the equivalent sign-on policy concepts under older names.
|
||||
- A **Super Administrator** account on that organization for the one-time service-app setup.
|
||||
- An **API Services** app integration in the Okta Admin Console with the `okta.policies.read` scope granted and the **Read-Only Administrator** role assigned.
|
||||
- An **API Services** app integration in the Okta Admin Console with the `okta.policies.read` and `okta.brands.read` scopes granted and the **Read-Only Administrator** role assigned.
|
||||
- Python 3.10+ and Prowler 5.27.0 or later installed locally.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
@@ -26,10 +26,51 @@ Set up authentication for Okta with the [Okta Authentication](/user-guide/provid
|
||||
|
||||
## Prowler Cloud
|
||||
|
||||
<VersionBadge version="5.28.0" />
|
||||
|
||||
### Step 1: Add the Provider
|
||||
|
||||
1. Go to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler App](/user-guide/tutorials/prowler-app).
|
||||
2. Navigate to "Configuration" > "Providers".
|
||||
|
||||

|
||||
|
||||
3. Click "Add Provider".
|
||||
|
||||

|
||||
|
||||
4. Select "Okta".
|
||||
|
||||

|
||||
|
||||
5. Enter the **Org Domain** of the target Okta organization and an optional alias, then click "Next".
|
||||
|
||||

|
||||
|
||||
<Note>
|
||||
Prowler Cloud onboarding for Okta is coming soon. Track the [Prowler GitHub repository](https://github.com/prowler-cloud/prowler) for release updates. Use the [Prowler CLI](#prowler-cli) workflow below in the meantime.
|
||||
The Org Domain must be the bare hostname of an Okta-managed organization — for example, `acme.okta.com`, `acme.oktapreview.com`, `acme.okta-emea.com`, `acme.okta-gov.com`, `acme.okta.mil`, `acme.okta-miltest.com`, or `acme.trex-govcloud.com`. Omit the `https://` scheme, any path, and any trailing slash.
|
||||
</Note>
|
||||
|
||||
### Step 2: Provide Credentials
|
||||
|
||||
Prowler Cloud authenticates to Okta with the **OAuth 2.0 Private Key JWT** flow exposed by an Okta **API Services** app. The service application, keypair, scope grants, and Read-Only Administrator role are set up once in the Okta Admin Console — full instructions are in the [Okta Authentication](/user-guide/providers/okta/authentication) guide.
|
||||
|
||||
1. Enter the **Client ID** of the Okta API Services app (for example, `0oa123456789abcdef`).
|
||||
2. Paste the **Private Key** whose matching public key (JWK) is registered on the service app. Both PEM-encoded RSA keys and JWK JSON documents are accepted.
|
||||
3. Click "Next".
|
||||
|
||||

|
||||
|
||||
<Note>
|
||||
The private key is transmitted over TLS and stored as an encrypted secret in the backend. Rotate or revoke the matching public key from the Okta Admin Console at any time to invalidate the credential without changes on the Prowler side.
|
||||
</Note>
|
||||
|
||||
### Step 3: Launch the Scan
|
||||
|
||||
1. Review the connection summary. Prowler Cloud runs a credential probe against the Okta Management API before saving — a failed probe surfaces the underlying Okta error (`invalid_scope`, `Forbidden`, invalid credentials, etc.) so the configuration can be corrected before the first scan.
|
||||
2. Choose the scan schedule: run a single scan or set up daily scans (every 24 hours).
|
||||
3. Click **Launch Scan** to start auditing the Okta organization.
|
||||
|
||||
---
|
||||
|
||||
## Prowler CLI
|
||||
@@ -44,8 +85,8 @@ Follow the [Okta Authentication](/user-guide/providers/okta/authentication) guid
|
||||
export OKTA_ORG_DOMAIN="acme.okta.com"
|
||||
export OKTA_CLIENT_ID="0oa1234567890abcdef"
|
||||
export OKTA_PRIVATE_KEY_FILE="/secure/path/to/prowler-okta.pem"
|
||||
# Optional — defaults to "okta.policies.read"
|
||||
export OKTA_SCOPES="okta.policies.read"
|
||||
# Optional — defaults to "okta.policies.read,okta.brands.read"
|
||||
export OKTA_SCOPES="okta.policies.read,okta.brands.read"
|
||||
```
|
||||
|
||||
The private key file may contain either a PEM-encoded RSA key or a JWK JSON document.
|
||||
@@ -113,20 +154,21 @@ This is stricter than simply finding the same timeout value somewhere else in th
|
||||
|
||||
### Default Scopes
|
||||
|
||||
Prowler requests a fixed set of OAuth scopes on every token exchange. The default is a single scope that covers the bundled initial check:
|
||||
Prowler requests a fixed set of OAuth scopes on every token exchange. The defaults cover the bundled signon checks:
|
||||
|
||||
- `okta.policies.read`
|
||||
- `okta.brands.read`
|
||||
|
||||
The service app must have that scope granted in the **Okta API Scopes** tab. When the granted set is narrower than the requested set, the token request fails with an `invalid_scope` error and the scan stops at provider initialization.
|
||||
The service app must have these scopes granted in the **Okta API Scopes** tab. When the granted set is narrower than the requested set, the token request fails with an `invalid_scope` error and the scan stops at provider initialization.
|
||||
|
||||
When additional checks are enabled — or when running against a service app that exposes a different scope set — override the default with `OKTA_SCOPES` (comma-separated string for the env var) or `--okta-scopes` (space-separated list for the CLI):
|
||||
|
||||
```bash
|
||||
# Environment variable — comma-separated
|
||||
export OKTA_SCOPES="okta.policies.read,okta.apps.read,okta.users.read"
|
||||
export OKTA_SCOPES="okta.policies.read,okta.brands.read,okta.apps.read,okta.users.read"
|
||||
|
||||
# CLI flag — space-separated
|
||||
prowler okta --okta-scopes okta.policies.read okta.apps.read okta.users.read
|
||||
prowler okta --okta-scopes okta.policies.read okta.brands.read okta.apps.read okta.users.read
|
||||
```
|
||||
|
||||
For the full catalog of OAuth scopes exposed by the Okta Management API, refer to the [Okta OAuth 2.0 scopes documentation](https://developer.okta.com/docs/api/oauth2/).
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 306 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 220 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user