mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-19 17:40:25 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b21ddad32c |
@@ -158,7 +158,7 @@ SENTRY_RELEASE=local
|
||||
# REO_DEV_CLIENT_ID=
|
||||
|
||||
#### Prowler release version ####
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.39.0
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.38.0
|
||||
|
||||
# Social login credentials
|
||||
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
|
||||
|
||||
@@ -69,7 +69,7 @@ runs:
|
||||
|
||||
- name: Upload Grype results to GitHub Security tab
|
||||
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
|
||||
uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # v3.37.4
|
||||
uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
|
||||
with:
|
||||
sarif_file: 'grype-results.sarif'
|
||||
category: 'grype-container'
|
||||
|
||||
@@ -86,7 +86,7 @@ runs:
|
||||
run: pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir "uv==${UV_VERSION}"
|
||||
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
cache: 'pip'
|
||||
|
||||
@@ -88,7 +88,7 @@ runs:
|
||||
|
||||
- name: Upload Trivy results to GitHub Security tab
|
||||
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
|
||||
uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # v3.37.4
|
||||
uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
category: 'trivy-container'
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
raw.githubusercontent.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
|
||||
@@ -58,17 +58,17 @@ jobs:
|
||||
objects.githubusercontent.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.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@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
category: '/language:${{ matrix.language }}'
|
||||
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
www.powershellgallery.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -147,18 +147,18 @@ jobs:
|
||||
(cd api && uv lock --upgrade-package prowler)
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- 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@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
push: true
|
||||
@@ -200,7 +200,7 @@ jobs:
|
||||
registry-1.docker.io:443
|
||||
release-assets.githubusercontent.com:443
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -255,7 +255,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Lint Dockerfile with Hadolint
|
||||
if: steps.dockerfile-changed.outputs.any_changed == 'true'
|
||||
uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0
|
||||
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
|
||||
with:
|
||||
dockerfile: api/Dockerfile
|
||||
ignore: DL3013
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
releases.astral.sh:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -133,11 +133,11 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build container
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.API_WORKING_DIR }}
|
||||
push: false
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
osv-vulnerabilities.storage.googleapis.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: master
|
||||
persist-credentials: false
|
||||
@@ -207,7 +207,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout version branch
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
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
|
||||
@@ -312,7 +312,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -22,12 +22,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -46,11 +46,11 @@ jobs:
|
||||
api.github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1
|
||||
uses: zizmorcore/zizmor-action@a16621b09c6db4281f81a93cb393b05dcd7b7165 # v0.5.5
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
files.pythonhosted.org:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ inputs.target_branch }}
|
||||
fetch-depth: 0 # PR attribution resolves each fragment's adding commit from history
|
||||
@@ -73,9 +73,9 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: '3.12.13'
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install towncrier
|
||||
run: pip install --no-cache-dir towncrier==25.8.0
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
hub.docker.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
hub.docker.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -28,14 +28,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
www.formbucket.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# PRs only need the diff range; push to master/release walks the new range from event.before.
|
||||
# 50 is enough headroom for the longest realistic PR/push chain without paying for a full clone.
|
||||
@@ -45,6 +45,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@6f3c981e7b77f235fd2702dd74af25fc4b72bf11 # v3.96.0
|
||||
uses: trufflesecurity/trufflehog@37b77001d0174ebec2fcca2bd83ff83a6d45a3ab # v3.95.3
|
||||
with:
|
||||
extra_args: --results=verified,unknown
|
||||
|
||||
@@ -38,12 +38,12 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
|
||||
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
||||
|
||||
- name: Update chart dependencies
|
||||
run: helm dependency update ${{ env.CHART_PATH }}
|
||||
|
||||
@@ -31,12 +31,12 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
|
||||
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
||||
|
||||
- name: Set chart version and appVersion from release tag
|
||||
run: |
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Apply labels to PR
|
||||
uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
|
||||
uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
release-assets.githubusercontent.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -124,23 +124,23 @@ jobs:
|
||||
registry-1.docker.io:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- 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@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
push: true
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
release-assets.githubusercontent.com:443
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -238,7 +238,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Lint Dockerfile with Hadolint
|
||||
if: steps.dockerfile-changed.outputs.any_changed == 'true'
|
||||
uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0
|
||||
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
|
||||
with:
|
||||
dockerfile: mcp_server/Dockerfile
|
||||
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
get.anchore.io:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -114,11 +114,11 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build MCP container
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.MCP_WORKING_DIR }}
|
||||
push: false
|
||||
|
||||
@@ -72,17 +72,17 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
|
||||
with:
|
||||
enable-cache: false
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
|
||||
- name: Publish prowler-mcp package to PyPI
|
||||
if: steps.pypi-check.outputs.skip != 'true'
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
with:
|
||||
packages-dir: ${{ env.WORKING_DIRECTORY }}/dist/
|
||||
print-hash: true
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
osv-vulnerabilities.storage.googleapis.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
api.github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -53,15 +53,15 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build ${{ matrix.component }} container (linux/arm64)
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ matrix.context }}
|
||||
file: ${{ matrix.dockerfile }}
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
files.pythonhosted.org:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
@@ -63,9 +63,9 @@ jobs:
|
||||
|
||||
- name: Set up Python
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: '3.12.13'
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Test changelog attribution
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
# zizmor: ignore[artipacked]
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
# zizmor: ignore[artipacked]
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR head
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
gh variable set RELEASE_FREEZE --body true --repo "${GITHUB_REPOSITORY}"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
@@ -376,7 +376,7 @@ jobs:
|
||||
no-changelog
|
||||
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
|
||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
|
||||
with:
|
||||
tag_name: ${{ env.PROWLER_VERSION }}
|
||||
name: Prowler ${{ env.PROWLER_VERSION }}
|
||||
|
||||
@@ -45,12 +45,12 @@ jobs:
|
||||
releases.astral.sh:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
|
||||
|
||||
- name: Install prek
|
||||
run: uv tool install "prek==${PREK_VERSION}"
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
files.pythonhosted.org:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
|
||||
@@ -63,17 +63,17 @@ jobs:
|
||||
uploads.github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.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@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
category: '/language:${{ matrix.language }}'
|
||||
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
pypi.org:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -168,18 +168,18 @@ jobs:
|
||||
www.powershellgallery.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Configure AWS credentials (OIDC)
|
||||
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
|
||||
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: ${{ secrets.PUBLIC_ECR_PUSH_ROLE_ARN }}
|
||||
@@ -190,12 +190,12 @@ jobs:
|
||||
registry-type: public
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- 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@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
file: ${{ env.DOCKERFILE_PATH }}
|
||||
@@ -245,13 +245,13 @@ jobs:
|
||||
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Configure AWS credentials (OIDC)
|
||||
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
|
||||
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
role-to-assume: ${{ secrets.PUBLIC_ECR_PUSH_ROLE_ARN }}
|
||||
@@ -290,7 +290,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@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.TONIBLYX_DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.TONIBLYX_DOCKERHUB_PASSWORD }}
|
||||
@@ -315,7 +315,7 @@ jobs:
|
||||
# Re-login as prowlercloud for cleanup of intermediate tags
|
||||
- name: Login to DockerHub (prowlercloud)
|
||||
if: always()
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -348,7 +348,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
- name: Lint Dockerfile with Hadolint
|
||||
if: steps.dockerfile-changed.outputs.any_changed == 'true'
|
||||
uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0
|
||||
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
|
||||
with:
|
||||
dockerfile: Dockerfile
|
||||
ignore: DL3013
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
releases.astral.sh:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -128,11 +128,11 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build SDK container
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
run: uv build
|
||||
|
||||
- name: Publish Prowler package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
with:
|
||||
print-hash: true
|
||||
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -129,6 +129,6 @@ jobs:
|
||||
run: uv build
|
||||
|
||||
- name: Publish prowler-cloud package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
with:
|
||||
print-hash: true
|
||||
|
||||
@@ -32,13 +32,13 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: 'master'
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
run: pip install boto3
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
|
||||
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
|
||||
with:
|
||||
aws-region: ${{ env.AWS_REGION }}
|
||||
role-to-assume: ${{ secrets.DEV_IAM_ROLE_ARN }}
|
||||
|
||||
@@ -30,13 +30,13 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: 'master'
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
osv-vulnerabilities.storage.googleapis.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
aka.ms:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -225,7 +225,7 @@ jobs:
|
||||
|
||||
- name: Upload AWS coverage to Codecov
|
||||
if: steps.changed-aws.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -249,7 +249,7 @@ jobs:
|
||||
|
||||
- name: Upload Azure coverage to Codecov
|
||||
if: steps.changed-azure.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -273,7 +273,7 @@ jobs:
|
||||
|
||||
- name: Upload GCP coverage to Codecov
|
||||
if: steps.changed-gcp.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -297,7 +297,7 @@ jobs:
|
||||
|
||||
- name: Upload Kubernetes coverage to Codecov
|
||||
if: steps.changed-kubernetes.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -321,7 +321,7 @@ jobs:
|
||||
|
||||
- name: Upload GitHub coverage to Codecov
|
||||
if: steps.changed-github.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -345,7 +345,7 @@ jobs:
|
||||
|
||||
- name: Upload Okta coverage to Codecov
|
||||
if: steps.changed-okta.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -369,7 +369,7 @@ jobs:
|
||||
|
||||
- name: Upload NHN coverage to Codecov
|
||||
if: steps.changed-nhn.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -393,7 +393,7 @@ jobs:
|
||||
|
||||
- name: Upload M365 coverage to Codecov
|
||||
if: steps.changed-m365.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -417,7 +417,7 @@ jobs:
|
||||
|
||||
- name: Upload IaC coverage to Codecov
|
||||
if: steps.changed-iac.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -441,7 +441,7 @@ jobs:
|
||||
|
||||
- name: Upload MongoDB Atlas coverage to Codecov
|
||||
if: steps.changed-mongodbatlas.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -465,7 +465,7 @@ jobs:
|
||||
|
||||
- name: Upload OCI coverage to Codecov
|
||||
if: steps.changed-oraclecloud.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -489,7 +489,7 @@ jobs:
|
||||
|
||||
- name: Upload OpenStack coverage to Codecov
|
||||
if: steps.changed-openstack.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -513,7 +513,7 @@ jobs:
|
||||
|
||||
- name: Upload Google Workspace coverage to Codecov
|
||||
if: steps.changed-googleworkspace.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -537,7 +537,7 @@ jobs:
|
||||
|
||||
- name: Upload Vercel coverage to Codecov
|
||||
if: steps.changed-vercel.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -561,7 +561,7 @@ jobs:
|
||||
|
||||
- name: Upload Scaleway coverage to Codecov
|
||||
if: steps.changed-scaleway.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -585,7 +585,7 @@ jobs:
|
||||
|
||||
- name: Upload StackIT coverage to Codecov
|
||||
if: steps.changed-stackit.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -609,7 +609,7 @@ jobs:
|
||||
|
||||
- name: Upload Linode coverage to Codecov
|
||||
if: steps.changed-linode.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -633,7 +633,7 @@ jobs:
|
||||
|
||||
- name: Upload E2E Networks coverage to Codecov
|
||||
if: steps.changed-e2enetworks.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -644,7 +644,7 @@ jobs:
|
||||
- name: Check if External Provider files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: changed-external
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
|
||||
with:
|
||||
files: |
|
||||
./prowler/providers/common/**
|
||||
@@ -660,7 +660,7 @@ jobs:
|
||||
- name: Upload External Provider coverage to Codecov
|
||||
if: steps.changed-external.outputs.any_changed == 'true'
|
||||
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -684,7 +684,7 @@ jobs:
|
||||
|
||||
- name: Upload Lib coverage to Codecov
|
||||
if: steps.changed-lib.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
@@ -708,7 +708,7 @@ jobs:
|
||||
|
||||
- name: Upload Config coverage to Codecov
|
||||
if: steps.changed-config.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
files.pythonhosted.org:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -71,9 +71,9 @@ jobs:
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.14.6'
|
||||
python-version: '3.12.13'
|
||||
|
||||
- name: Install PyYAML
|
||||
run: pip install pyyaml
|
||||
|
||||
@@ -59,17 +59,17 @@ jobs:
|
||||
uploads.github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.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@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
category: '/language:${{ matrix.language }}'
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -125,23 +125,23 @@ jobs:
|
||||
registry.npmjs.org:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- 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@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
build-args: |
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
release-assets.githubusercontent.com:443
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -241,7 +241,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Lint Dockerfile with Hadolint
|
||||
if: steps.dockerfile-changed.outputs.any_changed == 'true'
|
||||
uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0
|
||||
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
|
||||
with:
|
||||
dockerfile: ui/Dockerfile
|
||||
ignore: DL3018
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
get.anchore.io:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -116,11 +116,11 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build UI container
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: ${{ env.UI_WORKING_DIR }}
|
||||
target: prod
|
||||
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -218,7 +218,7 @@ jobs:
|
||||
echo "E2E prerequisite preflight passed."
|
||||
|
||||
- name: Create k8s Kind Cluster
|
||||
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
|
||||
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1
|
||||
with:
|
||||
cluster_name: kind
|
||||
|
||||
@@ -290,7 +290,7 @@ jobs:
|
||||
'
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version-file: 'ui/.nvmrc'
|
||||
|
||||
@@ -304,7 +304,7 @@ jobs:
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup pnpm and Next.js cache
|
||||
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
${{ env.STORE_PATH }}
|
||||
@@ -324,7 +324,7 @@ jobs:
|
||||
run: pnpm run build
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
@@ -392,7 +392,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload test reports
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
if: failure()
|
||||
with:
|
||||
name: playwright-report
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
osv-vulnerabilities.storage.googleapis.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
playwright.download.prss.microsoft.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version-file: 'ui/.nvmrc'
|
||||
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
|
||||
- name: Setup pnpm and Next.js cache
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
${{ env.STORE_PATH }}
|
||||
@@ -171,7 +171,7 @@ jobs:
|
||||
- name: Cache Playwright browsers
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
id: playwright-cache
|
||||
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-chromium-${{ hashFiles('ui/pnpm-lock.yaml') }}
|
||||
|
||||
+1
-1
@@ -167,7 +167,7 @@ runs:
|
||||
|
||||
- name: Upload SARIF to GitHub Code Scanning
|
||||
if: always() && inputs.upload-sarif == 'true' && steps.find-sarif.outputs.sarif_path != ''
|
||||
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
sarif_file: ${{ steps.find-sarif.outputs.sarif_path }}
|
||||
category: ${{ inputs.sarif-category }}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
`/api/v1/accounts/saml/{organization_slug}/acs/` rejects non-POST requests before SAML response processing
|
||||
@@ -1 +0,0 @@
|
||||
Tenant deletion no longer leaves memberships partially removed when exclusive-user cleanup fails
|
||||
@@ -1 +0,0 @@
|
||||
`GET /api/v1/users/me` membership relationships identify the active tenant with `meta.active` for JWT and API key authentication
|
||||
+2
-2
@@ -45,7 +45,7 @@ dependencies = [
|
||||
"gunicorn==26.0.0",
|
||||
"uvloop==0.22.1",
|
||||
"lxml==6.1.0",
|
||||
"prowler @ git+https://github.com/prowler-cloud/prowler.git@master",
|
||||
"prowler @ git+https://github.com/prowler-cloud/prowler.git@v5.38",
|
||||
"psycopg2-binary==2.9.9",
|
||||
"pytest-celery[redis] (==1.3.0)",
|
||||
"sentry-sdk[django] (==2.56.0)",
|
||||
@@ -71,7 +71,7 @@ name = "prowler-api"
|
||||
package-mode = false
|
||||
# Needed for the SDK compatibility
|
||||
requires-python = ">=3.11,<3.13"
|
||||
version = "1.40.0"
|
||||
version = "1.39.0"
|
||||
|
||||
# Shared ruff baseline (kept in sync with mcp_server/pyproject.toml).
|
||||
# target-version tracks this project's lowest supported Python.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Prowler API
|
||||
version: 1.40.0
|
||||
version: 1.39.0
|
||||
description: |-
|
||||
Prowler API specification.
|
||||
|
||||
|
||||
@@ -1590,8 +1590,8 @@ class TestAPIKeyMultiTenantWorkflows:
|
||||
tenant1 = tenants_fixture[0]
|
||||
tenant2 = tenants_fixture[1]
|
||||
|
||||
membership1 = Membership.objects.create(user=user, tenant=tenant1)
|
||||
membership2 = Membership.objects.create(user=user, tenant=tenant2)
|
||||
Membership.objects.create(user=user, tenant=tenant1)
|
||||
Membership.objects.create(user=user, tenant=tenant2)
|
||||
|
||||
role1 = Role.objects.create(
|
||||
tenant_id=tenant1.id,
|
||||
@@ -1646,27 +1646,6 @@ class TestAPIKeyMultiTenantWorkflows:
|
||||
assert me_response1.json()["data"]["id"] == str(user.id)
|
||||
assert me_response2.json()["data"]["id"] == str(user.id)
|
||||
|
||||
memberships1 = {
|
||||
item["id"]: item["meta"]["active"]
|
||||
for item in me_response1.json()["data"]["relationships"]["memberships"][
|
||||
"data"
|
||||
]
|
||||
}
|
||||
memberships2 = {
|
||||
item["id"]: item["meta"]["active"]
|
||||
for item in me_response2.json()["data"]["relationships"]["memberships"][
|
||||
"data"
|
||||
]
|
||||
}
|
||||
assert memberships1 == {
|
||||
str(membership1.id): True,
|
||||
str(membership2.id): False,
|
||||
}
|
||||
assert memberships2 == {
|
||||
str(membership1.id): False,
|
||||
str(membership2.id): True,
|
||||
}
|
||||
|
||||
def test_api_key_cannot_access_different_tenant_resources(
|
||||
self, tenants_fixture, aws_provider
|
||||
):
|
||||
|
||||
@@ -78,9 +78,8 @@ from conftest import (
|
||||
today_after_n_days,
|
||||
)
|
||||
from django.conf import settings
|
||||
from django.db import close_old_connections, connection, connections
|
||||
from django.db import close_old_connections, connection
|
||||
from django.db.models import Count
|
||||
from django.db.models.signals import pre_delete
|
||||
from django.http import JsonResponse
|
||||
from django.test import RequestFactory
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
@@ -520,50 +519,6 @@ class TestUserViewSet:
|
||||
assert error_field in response.json()["errors"][0]["source"]["pointer"]
|
||||
|
||||
|
||||
@pytest.mark.requires_test_admin_alias
|
||||
@pytest.mark.django_db(transaction=True, databases=["default", "admin"])
|
||||
class TestTenantDeletionTransactions:
|
||||
@patch("api.v1.views.delete_tenant_task.apply_async")
|
||||
def test_delete_rolls_back_memberships_when_user_cleanup_fails(
|
||||
self,
|
||||
delete_tenant_mock,
|
||||
authenticated_client,
|
||||
tenants_fixture,
|
||||
):
|
||||
assert connections["default"] is not connections["admin"]
|
||||
|
||||
_, tenant, _ = tenants_fixture
|
||||
exclusive_user = User.objects.create_user(
|
||||
name="exclusive user",
|
||||
password=TEST_PASSWORD,
|
||||
email="exclusive-user@example.com",
|
||||
)
|
||||
membership = Membership.objects.create(
|
||||
user=exclusive_user,
|
||||
tenant=tenant,
|
||||
role=Membership.RoleChoices.MEMBER,
|
||||
)
|
||||
|
||||
def fail_user_cleanup(*, instance, **kwargs):
|
||||
if instance.pk == exclusive_user.pk:
|
||||
raise RuntimeError("Simulated user cleanup failure.")
|
||||
|
||||
pre_delete.connect(fail_user_cleanup, sender=User)
|
||||
try:
|
||||
with (
|
||||
patch.object(MainRouter, "admin_db", "admin"),
|
||||
pytest.raises(RuntimeError, match=r"Simulated user cleanup failure\."),
|
||||
):
|
||||
authenticated_client.delete(
|
||||
reverse("tenant-detail", kwargs={"pk": tenant.id})
|
||||
)
|
||||
finally:
|
||||
pre_delete.disconnect(fail_user_cleanup, sender=User)
|
||||
|
||||
assert Membership.objects.using("admin").filter(pk=membership.pk).exists()
|
||||
delete_tenant_mock.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestTenantViewSet:
|
||||
@pytest.fixture
|
||||
@@ -14673,37 +14628,6 @@ class TestSAMLConfigurationViewSet:
|
||||
assert not SAMLConfiguration.objects.filter(id=config.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestSAMLACSView:
|
||||
def test_get_is_not_allowed(self, client, saml_setup):
|
||||
response = client.get(
|
||||
reverse(
|
||||
"saml_acs",
|
||||
kwargs={"organization_slug": saml_setup["domain"]},
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
|
||||
assert response.headers["Allow"] == "POST"
|
||||
assert "saml-acs-session" not in response.cookies
|
||||
|
||||
def test_post_is_forwarded_to_allauth(self, client, saml_setup):
|
||||
response = client.post(
|
||||
reverse(
|
||||
"saml_acs",
|
||||
kwargs={"organization_slug": saml_setup["domain"]},
|
||||
),
|
||||
data={"SAMLResponse": "test-saml-response"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_302_FOUND
|
||||
assert response.url == reverse(
|
||||
"saml_finish_acs",
|
||||
kwargs={"organization_slug": saml_setup["domain"]},
|
||||
)
|
||||
assert "saml-acs-session" in response.cookies
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestTenantFinishACSView:
|
||||
def test_dispatch_skips_if_user_not_authenticated(self, monkeypatch):
|
||||
|
||||
@@ -329,15 +329,6 @@ class TokenSwitchTenantSerializer(BaseSerializerV1):
|
||||
# Users
|
||||
|
||||
|
||||
class ActiveMembershipRelatedField(SerializerMethodResourceRelatedField):
|
||||
def to_representation(self, value):
|
||||
representation = super().to_representation(value)
|
||||
representation["meta"] = {
|
||||
"active": str(value.tenant_id) == str(self.context["request"].tenant_id),
|
||||
}
|
||||
return representation
|
||||
|
||||
|
||||
class UserSerializer(BaseModelSerializerV1):
|
||||
"""
|
||||
Serializer for the User model.
|
||||
@@ -399,12 +390,6 @@ class UserSerializer(BaseModelSerializerV1):
|
||||
)
|
||||
|
||||
|
||||
class UserMeSerializer(UserSerializer):
|
||||
memberships = ActiveMembershipRelatedField(
|
||||
many=True, read_only=True, source="memberships", method_name="get_memberships"
|
||||
)
|
||||
|
||||
|
||||
class UserIncludeSerializer(UserSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
|
||||
@@ -46,7 +46,6 @@ from api.v1.views import (
|
||||
from django.http import JsonResponse
|
||||
from django.urls import include, path
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
from drf_spectacular.views import SpectacularRedocView
|
||||
from rest_framework_nested import routers
|
||||
|
||||
@@ -195,7 +194,7 @@ urlpatterns = [
|
||||
),
|
||||
path(
|
||||
"accounts/saml/<organization_slug>/acs/",
|
||||
require_POST(ACSView.as_view()),
|
||||
ACSView.as_view(),
|
||||
name="saml_acs",
|
||||
),
|
||||
path(
|
||||
|
||||
@@ -238,7 +238,6 @@ from api.v1.serializers import (
|
||||
TokenSocialLoginSerializer,
|
||||
TokenSwitchTenantSerializer,
|
||||
UserCreateSerializer,
|
||||
UserMeSerializer,
|
||||
UserRoleRelationshipSerializer,
|
||||
UserSerializer,
|
||||
UserUpdateSerializer,
|
||||
@@ -1114,8 +1113,6 @@ class UserViewSet(BaseUserViewset):
|
||||
return UserCreateSerializer
|
||||
elif self.action == "partial_update":
|
||||
return UserUpdateSerializer
|
||||
elif self.action == "me":
|
||||
return UserMeSerializer
|
||||
else:
|
||||
return UserSerializer
|
||||
|
||||
@@ -1133,7 +1130,7 @@ class UserViewSet(BaseUserViewset):
|
||||
@action(detail=False, methods=["get"], url_name="me")
|
||||
def me(self, request):
|
||||
user = self.request.user
|
||||
serializer = self.get_serializer(user)
|
||||
serializer = UserSerializer(user, context=self.get_serializer_context())
|
||||
return Response(
|
||||
data=serializer.data,
|
||||
status=status.HTTP_200_OK,
|
||||
@@ -1445,7 +1442,7 @@ class TenantViewSet(BaseTenantViewset):
|
||||
if not membership or membership.role != Membership.RoleChoices.OWNER:
|
||||
raise PermissionDenied("Only owners can delete a tenant.")
|
||||
|
||||
with transaction.atomic(using=MainRouter.admin_db):
|
||||
with transaction.atomic():
|
||||
# Collect user IDs from this tenant's memberships before deleting them
|
||||
tenant_user_ids = set(
|
||||
Membership.objects.using(MainRouter.admin_db)
|
||||
|
||||
Generated
+3
-3
@@ -4836,7 +4836,7 @@ wheels = [
|
||||
[[package]]
|
||||
name = "prowler"
|
||||
version = "5.38.0"
|
||||
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=master#b3d174d0c1eb202ed7cb9a9daf0500683f4443be" }
|
||||
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=v5.38#226504982b1d1fb9887f00f2773aafc9adf2a2a8" }
|
||||
dependencies = [
|
||||
{ name = "alibabacloud-actiontrail20200706" },
|
||||
{ name = "alibabacloud-credentials" },
|
||||
@@ -4935,7 +4935,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prowler-api"
|
||||
version = "1.40.0"
|
||||
version = "1.39.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "cartography" },
|
||||
@@ -5035,7 +5035,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.38" },
|
||||
{ name = "psycopg2-binary", specifier = "==2.9.9" },
|
||||
{ name = "pytest-celery", extras = ["redis"], specifier = "==1.3.0" },
|
||||
{ name = "reportlab", specifier = "==4.4.10" },
|
||||
|
||||
@@ -4,81 +4,6 @@ description: "New features and improvements in each Prowler release"
|
||||
rss: true
|
||||
---
|
||||
|
||||
<Update label="v5.38.0" description="August 6, 2026">
|
||||
### 📌 Compliance Watchlist
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
Compliance Watchlist keeps the frameworks an organization tracks in one shared list. Pin frameworks from any compliance view, manage several at once through a searchable catalog, and filter the Compliance section to show only the pinned frameworks.
|
||||
|
||||
The Overview page now reports the latest score for every pinned framework, while finding details highlight the watched frameworks associated with each check. Universal frameworks remain a single watchlist entry across provider views, keeping the organization's priorities consistent everywhere.
|
||||
|
||||

|
||||
|
||||
Read more in the [Compliance Watchlist documentation](https://docs.prowler.com/user-guide/compliance/tutorials/compliance#tracking-frameworks-with-the-compliance-watchlist).
|
||||
|
||||
### 🔐 SAML SSO - Multiple Email Domains
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
One SAML configuration can now authorize a primary email domain and up to 19 additional domains through the same Identity Provider. Every domain shares one stable Assertion Consumer Service (ACS) URL based on the primary domain, so subsidiaries, acquired companies, regional domains, and multiple brands no longer require separate tenants or duplicated SAML applications.
|
||||
|
||||
Domain ownership remains tenant-bound throughout the authentication flow. During service provider-initiated sign-in, the discovery domain and the domain asserted by the Identity Provider must resolve to the same tenant before provisioning continues.
|
||||
|
||||

|
||||
|
||||
Read more in the [SAML SSO documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-sso#add-multiple-saml-domains).
|
||||
|
||||
### 👥 User Sign-In Methods
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
The Users table now shows each account's sign-in methods as tags, including email/password, Google, GitHub, SAML with linked domains, and Partner SSO. Accounts without a reported method display a placeholder.
|
||||
|
||||

|
||||
|
||||
### 🕸️ Attack Paths - Expanded AWS Privilege-Escalation Coverage
|
||||
|
||||
Attack Paths adds 20 AWS privilege-escalation queries from [pathfinding.cloud](https://pathfinding.cloud), while `iam_policy_allows_privilege_escalation` gains 22 additional escalation combinations.
|
||||
|
||||
The new coverage includes service `iam:PassRole` paths across AWS Batch, Braket, Cognito Identity, ECS, EMR, EMR Serverless, GameLift, Glue, EC2 Image Builder, Kinesis Analytics, HealthOmics, EventBridge Scheduler, Systems Manager, and Step Functions. It also covers existing-resource abuse, permissions-boundary removal, role assumption, and IAM Identity Center permission-set policy injection.
|
||||
|
||||
The query catalog now exposes each AWS query's outcome category, distinguishing code execution, privilege escalation, public exposure, and resource inventory.
|
||||
|
||||
Explore the full Attack Paths query catalog at [Prowler Hub](https://hub.prowler.com/attack-paths).
|
||||
|
||||
Read more in the [Attack Paths documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-attack-paths).
|
||||
|
||||
### 🔍 Checks
|
||||
|
||||
#### Microsoft 365
|
||||
|
||||
Twelve new checks expand the coverage of CIS Microsoft 365 Foundations Benchmark v7.0.0:
|
||||
|
||||
- **Admin Center:** Shared Bookings is disabled.
|
||||
- **Defender:** Priority account protection and strict preset security policies are enabled.
|
||||
- **Entra ID:** Six checks cover device registration restrictions, local administrator behavior, device limits, LAPS, and BitLocker key visibility.
|
||||
- **Exchange Online:** Personal accounts in Outlook on the web are disabled and Direct Send is rejected.
|
||||
- **Microsoft Teams:** External access from trial-only tenants is blocked.
|
||||
|
||||
Explore all Microsoft 365 checks at [Prowler Hub](https://hub.prowler.com/check?provider=m365).
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- Prowler API, UI, SDK, and MCP container images now publish per-architecture Software Bills of Materials (SBOMs) and build-provenance attestations. Prowler Cloud production and Prowler Private Cloud images carry the same attestations.
|
||||
- SDK and API container builds verify the checksums of downloaded PowerShell, Trivy, and zizmor binaries before installation.
|
||||
- Grype now complements Trivy across the container-image security gates, detecting components and vulnerabilities that manifest-based scanners can miss and blocking fixable high and critical findings.
|
||||
- `aiohttp` was upgraded to 3.14.3 to address CVE-2026-69244. `cryptography` was upgraded to 50.0.0 to address CVE-2026-69247 and CVE-2026-69249.
|
||||
|
||||
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.38.0) for the complete list of changes.
|
||||
</Update>
|
||||
|
||||
<Update label="v5.37.0" description="August 3, 2026">
|
||||
### 💬 Lighthouse AI — Context-Aware Chat and a Bigger Toolbox
|
||||
|
||||
|
||||
@@ -128,8 +128,8 @@ To update the environment file:
|
||||
Edit the `.env` file and change version values:
|
||||
|
||||
```env
|
||||
PROWLER_UI_VERSION="5.38.0"
|
||||
PROWLER_API_VERSION="5.38.0"
|
||||
PROWLER_UI_VERSION="5.37.0"
|
||||
PROWLER_API_VERSION="5.37.0"
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 218 KiB |
@@ -37,7 +37,6 @@
|
||||
"glue:SearchTables",
|
||||
"glue:GetMLTransforms",
|
||||
"lambda:GetFunction*",
|
||||
"lambda:GetLayerVersion",
|
||||
"logs:FilterLogEvents",
|
||||
"lightsail:GetRelationalDatabases",
|
||||
"macie2:GetMacieSession",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
`awslambda_layer_no_secrets_in_content` check for AWS provider, scanning Lambda layer package content for hardcoded secrets
|
||||
@@ -1 +0,0 @@
|
||||
`batch_job_definition_no_secrets` check for AWS provider, scanning Batch job definition environment variables and command parameters for hardcoded secrets
|
||||
@@ -1 +0,0 @@
|
||||
7 M365 Entra checks covering CIS Microsoft 365 Foundations Benchmark v7.0.0 password protection, default user permissions, and guest invitation domain restrictions
|
||||
@@ -1166,9 +1166,7 @@
|
||||
{
|
||||
"Id": "5.1.3.1",
|
||||
"Description": "This setting allows users in the organization to create new security groups and add members to these groups in the Azure portal, API, or PowerShell. These new groups also show up in the Access Panel for all other users. If the policy setting on the group allows it, other users can create requests to join these groups. The recommended state is Users can create security groups in Azure portals, API or PowerShell set to No.",
|
||||
"Checks": [
|
||||
"entra_policy_default_user_cannot_create_security_groups"
|
||||
],
|
||||
"Checks": [],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "5 Microsoft Entra admin center",
|
||||
@@ -1231,9 +1229,7 @@
|
||||
{
|
||||
"Id": "5.1.3.4",
|
||||
"Description": "All users within a Microsoft Entra organization are permitted to create new Microsoft 365 groups and add members to those groups through the Azure portal, API, or PowerShell. Newly created groups also appear in the Access Panel for all other users. When the applicable group policy settings allow it, users can submit requests to join these groups. The recommended state is No.",
|
||||
"Checks": [
|
||||
"entra_policy_default_user_cannot_create_m365_groups"
|
||||
],
|
||||
"Checks": [],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "5 Microsoft Entra admin center",
|
||||
@@ -1530,9 +1526,7 @@
|
||||
{
|
||||
"Id": "5.1.6.1",
|
||||
"Description": "B2B collaboration is a feature within Microsoft Entra External ID that allows for guest invitations to an organization. Ensure users can only send invitations to specified domains. Note: This list works independently from OneDrive for Business and SharePoint Online allow/block lists. To restrict individual file sharing in SharePoint Online, set up an allow or blocklist for OneDrive for Business and SharePoint Online. For instance, in SharePoint or OneDrive users can still share with external users from prohibited domains by using Anyone links if they haven't been disabled.",
|
||||
"Checks": [
|
||||
"entra_policy_guest_invitations_restricted_to_allowed_domains"
|
||||
],
|
||||
"Checks": [],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "5 Microsoft Entra admin center",
|
||||
@@ -2022,9 +2016,7 @@
|
||||
{
|
||||
"Id": "5.2.3.2",
|
||||
"Description": "With Entra Password Protection, default global banned password lists are automatically applied to all users in an Entra ID tenant. To support business and security needs, custom banned password lists can be defined. When users change or reset their passwords, these banned password lists are checked to enforce the use of strong passwords. A custom banned password list should include some of the following examples: - Brand names - Product names - Locations, such as company headquarters - Company-specific internal terms - Abbreviations that have specific company meaning",
|
||||
"Checks": [
|
||||
"entra_password_protection_custom_banned_list_enforced"
|
||||
],
|
||||
"Checks": [],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "5 Microsoft Entra admin center",
|
||||
@@ -2045,9 +2037,7 @@
|
||||
{
|
||||
"Id": "5.2.3.3",
|
||||
"Description": "Microsoft Entra Password Protection provides a global and custom banned password list. A password change request fails if there's a match in these banned password list. To protect on-premises Active Directory Domain Services (AD DS) environment, install and configure Entra Password Protection. Note: This recommendation applies to Hybrid deployments only and will have no impact unless working with on-premises Active Directory.",
|
||||
"Checks": [
|
||||
"entra_password_protection_on_premises_enforced"
|
||||
],
|
||||
"Checks": [],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "5 Microsoft Entra admin center",
|
||||
@@ -2156,9 +2146,7 @@
|
||||
{
|
||||
"Id": "5.2.3.8",
|
||||
"Description": "The account lockout threshold determines how many failed login attempts are permitted prior to placing the account in a locked-out state and initiating a variable lockout duration. The recommended Lockout threshold is 10 or less.",
|
||||
"Checks": [
|
||||
"entra_password_protection_lockout_threshold_limited"
|
||||
],
|
||||
"Checks": [],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "5 Microsoft Entra admin center",
|
||||
@@ -2179,9 +2167,7 @@
|
||||
{
|
||||
"Id": "5.2.3.9",
|
||||
"Description": "The account lockout duration value determines how long an account retains the status of lockout, and therefore how long before a user can continue to attempt to login after passing the lockout threshold. The recommended state is Lockout duration in seconds is at least 60.",
|
||||
"Checks": [
|
||||
"entra_password_protection_lockout_duration_configured"
|
||||
],
|
||||
"Checks": [],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "5 Microsoft Entra admin center",
|
||||
|
||||
@@ -49,7 +49,7 @@ class _MutableTimestamp:
|
||||
|
||||
timestamp = _MutableTimestamp(datetime.today())
|
||||
timestamp_utc = _MutableTimestamp(datetime.now(timezone.utc))
|
||||
prowler_version = "5.39.0"
|
||||
prowler_version = "5.38.0"
|
||||
html_logo_url = "https://github.com/prowler-cloud/prowler/"
|
||||
square_logo_img = "https://raw.githubusercontent.com/prowler-cloud/prowler/dc7d2d5aeb92fdf12e8604f42ef6472cd3e8e889/docs/img/prowler-logo-black.png"
|
||||
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
|
||||
|
||||
@@ -27,8 +27,6 @@ aws:
|
||||
max_lambda_functions: null
|
||||
# aws.max_ecs_task_definitions --> ecs_task_definitions_* checks
|
||||
max_ecs_task_definitions: null
|
||||
# aws.max_batch_job_definitions --> batch_job_definition_* checks
|
||||
max_batch_job_definitions: null
|
||||
# aws.max_codeartifact_packages --> codeartifact_packages_* checks
|
||||
max_codeartifact_packages: null
|
||||
# aws.disallowed_regions --> List of AWS regions to exclude from the scan.
|
||||
|
||||
@@ -153,12 +153,6 @@ class AWSProviderConfig(ProviderConfigBase):
|
||||
le=1_000_000,
|
||||
description="Resource scan limit for ECS task definitions. Use 0 or -1 to disable.",
|
||||
)
|
||||
max_batch_job_definitions: ResourceScanLimit = Field(
|
||||
default=None,
|
||||
ge=-1,
|
||||
le=1_000_000,
|
||||
description="Resource scan limit for Batch job definitions. Use 0 or -1 to disable.",
|
||||
)
|
||||
max_codeartifact_packages: ResourceScanLimit = Field(
|
||||
default=None,
|
||||
ge=-1,
|
||||
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "awslambda_layer_no_secrets_in_content",
|
||||
"CheckTitle": "Lambda layer content contains no hardcoded secrets",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/AWS Security Best Practices",
|
||||
"Sensitive Data Identifications/Passwords",
|
||||
"Effects/Data Exposure"
|
||||
],
|
||||
"ServiceName": "awslambda",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AwsLambdaLayerVersion",
|
||||
"ResourceGroup": "serverless",
|
||||
"Description": "**Lambda layer content** is analyzed for **embedded secrets** across files in the layer's package, detecting patterns like API keys, passwords, tokens, and connection strings. Findings reference file names and line numbers where potential secrets appear.",
|
||||
"Risk": "**Hardcoded secrets** undermine confidentiality and integrity: a secret baked into a layer is pulled into every function that uses it, and is not covered by a function-code-only scan. If exposed, attackers can reuse credentials to access databases, APIs, or cloud resources, enabling data exfiltration and unauthorized changes. Rotation is harder, increasing dwell time and blast radius.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html",
|
||||
"https://docs.aws.amazon.com/secretsmanager/latest/userguide/best-practices.html"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws secretsmanager create-secret --name <secret-name> --secret-string <value>\naws iam put-role-policy --role-name <function-execution-role> --policy-name allow-get-secret --policy-document '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"secretsmanager:GetSecretValue\",\"Resource\":\"<secret-arn>\"}]}'\n# Remove the hardcoded value from the layer's code, then:\naws lambda publish-layer-version --layer-name <layer-name> --zip-file fileb://layer.zip",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. In AWS Secrets Manager, click Store a new secret and create a secret for the value you hardcoded. Note the secret name/ARN.\n2. In IAM > Roles, open the execution role of every function that uses this layer and add an inline policy allowing secretsmanager:GetSecretValue on that secret only.\n3. Remove the hardcoded value from the layer's code and repackage it, retrieving the secret at runtime using the AWS SDK (GetSecretValue) with the secret name/ARN.\n4. Publish a new layer version and update dependent functions to use it.",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Use **AWS Secrets Manager** (or Parameter Store) to store secrets and retrieve at runtime; never put them in layer code or packaged dependencies.\n- Apply **least privilege** IAM\n- Enable **rotation**\n- Prevent secret logging; encrypt\n- Add CI/CD secret scanning",
|
||||
"Url": "https://hub.prowler.com/check/awslambda_layer_no_secrets_in_content"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"secrets"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
import fnmatch
|
||||
import os
|
||||
import tempfile
|
||||
from collections import defaultdict
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.utils.utils import (
|
||||
SecretsScanError,
|
||||
annotate_verified_secrets,
|
||||
detect_secrets_scan_batch,
|
||||
)
|
||||
from prowler.providers.aws.services.awslambda.awslambda_client import awslambda_client
|
||||
|
||||
|
||||
class awslambda_layer_no_secrets_in_content(Check):
|
||||
"""Check if Lambda layer content contains hardcoded secrets.
|
||||
|
||||
Scans every file inside each Lambda layer version's package with the
|
||||
secret scanner.
|
||||
|
||||
- PASS: No secrets are detected in the layer content.
|
||||
- FAIL: At least one potential secret is detected in the layer content.
|
||||
- MANUAL: The layer content could not be fetched or scanned.
|
||||
"""
|
||||
|
||||
def execute(self) -> list[Check_Report_AWS]:
|
||||
"""Execute the Lambda layer secrets scan.
|
||||
|
||||
Returns:
|
||||
list[Check_Report_AWS]: One report per Lambda layer version used by
|
||||
the audited functions, or an empty list when there are no layers.
|
||||
"""
|
||||
findings = []
|
||||
if not awslambda_client.layers:
|
||||
return findings
|
||||
|
||||
secrets_ignore_patterns = awslambda_client.audit_config.get(
|
||||
"secrets_ignore_patterns", []
|
||||
)
|
||||
# Glob patterns of file names inside the layer package to skip
|
||||
# when scanning for secrets (e.g. "*.deps.json" for .NET layers).
|
||||
secrets_ignore_files = (
|
||||
awslambda_client.audit_config.get("secrets_ignore_files", []) or []
|
||||
)
|
||||
validate = awslambda_client.audit_config.get("secrets_validate", False)
|
||||
|
||||
# Scan files of every layer version's package in batched
|
||||
# Kingfisher invocations instead of one subprocess per file per layer.
|
||||
# Each package is extracted one at a time and its files are
|
||||
# read (byte-faithfully via latin-1) before the extraction is released,
|
||||
# so only a single package is on disk at a time. Findings are keyed by
|
||||
# (layer index, package-relative file name) so they can be grouped
|
||||
# back per layer.
|
||||
layers_with_code = []
|
||||
|
||||
def code_payloads():
|
||||
for layer, layer_code in awslambda_client._get_layers_code():
|
||||
if not layer_code:
|
||||
continue
|
||||
with tempfile.TemporaryDirectory() as tmp_dir_name:
|
||||
try:
|
||||
layer_code.code_zip.extractall(tmp_dir_name)
|
||||
except Exception as error:
|
||||
# A corrupt or truncated package must not abort the
|
||||
# scan of the remaining layers: keep this layer out of
|
||||
# layers_with_code so it is reported as MANUAL below.
|
||||
logger.error(
|
||||
f"{layer.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
continue
|
||||
index = len(layers_with_code)
|
||||
layers_with_code.append(layer)
|
||||
for root, _, files in os.walk(tmp_dir_name):
|
||||
for file_name in files:
|
||||
file_path = os.path.join(root, file_name)
|
||||
relative_file_path = os.path.relpath(
|
||||
file_path, tmp_dir_name
|
||||
)
|
||||
if any(
|
||||
fnmatch.fnmatch(relative_file_path, pattern)
|
||||
for pattern in secrets_ignore_files
|
||||
):
|
||||
continue
|
||||
try:
|
||||
with open(file_path, "rb") as code_file:
|
||||
content = code_file.read().decode("latin-1")
|
||||
except Exception:
|
||||
continue
|
||||
yield (index, relative_file_path), content
|
||||
|
||||
scan_error = None
|
||||
try:
|
||||
batch_results = detect_secrets_scan_batch(
|
||||
code_payloads(),
|
||||
excluded_secrets=secrets_ignore_patterns,
|
||||
validate=validate,
|
||||
)
|
||||
except SecretsScanError as error:
|
||||
batch_results = {}
|
||||
scan_error = error
|
||||
|
||||
if scan_error:
|
||||
# The scan failed before any layer's code could be cleared. Report
|
||||
# MANUAL for every layer rather than risk a false PASS.
|
||||
for layer in awslambda_client.layers.values():
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=layer)
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = (
|
||||
f"Could not scan Lambda layer {layer.name} (version "
|
||||
f"{layer.version}) content for secrets: {scan_error}; "
|
||||
"manual review is required."
|
||||
)
|
||||
findings.append(report)
|
||||
return findings
|
||||
|
||||
findings_by_layer = defaultdict(dict)
|
||||
for (index, file_name), file_findings in batch_results.items():
|
||||
findings_by_layer[index][file_name] = file_findings
|
||||
|
||||
for index, layer in enumerate(layers_with_code):
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=layer)
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"No secrets found in Lambda layer {layer.name} "
|
||||
f"(version {layer.version}) content."
|
||||
)
|
||||
|
||||
files_with_secrets = findings_by_layer.get(index)
|
||||
if files_with_secrets:
|
||||
all_secrets = []
|
||||
secrets_findings = []
|
||||
for file_name, file_findings in files_with_secrets.items():
|
||||
all_secrets.extend(file_findings)
|
||||
secrets_string = ", ".join(
|
||||
f"{secret['type']} on line {secret['line_number']}"
|
||||
for secret in file_findings
|
||||
)
|
||||
secrets_findings.append(f"{file_name}: {secrets_string}")
|
||||
|
||||
final_output_string = "; ".join(secrets_findings)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Potential {'secrets' if len(secrets_findings) > 1 else 'secret'} found in Lambda layer {layer.name} (version {layer.version}) content -> {final_output_string}."
|
||||
annotate_verified_secrets(report, all_secrets)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
# Layers whose content could not be fetched (network error, missing
|
||||
# permissions, etc.) never reach layers_with_code above, so report
|
||||
# them as MANUAL rather than silently omitting them from the scan.
|
||||
fetched_arns = {layer.arn for layer in layers_with_code}
|
||||
for layer in awslambda_client.layers.values():
|
||||
if layer.arn in fetched_arns:
|
||||
continue
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=layer)
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = (
|
||||
f"Could not retrieve content of Lambda layer {layer.name} "
|
||||
f"(version {layer.version}) to scan for secrets; manual "
|
||||
"review is required."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -17,11 +17,6 @@ from prowler.lib.resource_limit import (
|
||||
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
|
||||
from prowler.providers.aws.lib.service.service import AWSService
|
||||
|
||||
# Presigned code/layer download URLs are short-lived S3 URLs, not AWS API
|
||||
# calls, so a hung request here would otherwise block a worker thread
|
||||
# indefinitely instead of failing like the surrounding boto3 calls do.
|
||||
CODE_DOWNLOAD_TIMEOUT_SECONDS = 30
|
||||
|
||||
|
||||
class Lambda(AWSService):
|
||||
def __init__(self, provider):
|
||||
@@ -30,7 +25,6 @@ class Lambda(AWSService):
|
||||
# Functions are listed first, then trimmed to the subset selected for
|
||||
# analysis before expensive per-function detail is hydrated.
|
||||
self.functions = {}
|
||||
self.layers = {}
|
||||
self.security_groups_in_use = set()
|
||||
self.regions_with_functions = set()
|
||||
self.function_limit = get_resource_scan_limit(
|
||||
@@ -38,7 +32,6 @@ class Lambda(AWSService):
|
||||
)
|
||||
self.__threading_call__(self._list_functions)
|
||||
self._select_functions_for_analysis()
|
||||
self._collect_layers()
|
||||
self._list_tags_for_resource()
|
||||
self.__threading_call__(self._get_policy)
|
||||
self.__threading_call__(self._get_function_url_config)
|
||||
@@ -113,11 +106,6 @@ class Lambda(AWSService):
|
||||
)
|
||||
}
|
||||
|
||||
def _collect_layers(self):
|
||||
for function in self.functions.values():
|
||||
for layer in function.layers:
|
||||
self.layers.setdefault(layer.arn, layer)
|
||||
|
||||
def _list_event_source_mappings(self, regional_client):
|
||||
logger.info("Lambda - Listing Event Source Mappings...")
|
||||
try:
|
||||
@@ -205,15 +193,6 @@ class Lambda(AWSService):
|
||||
f"{function.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _download_code(self, code_location_uri):
|
||||
raw_code_zip = requests.get(
|
||||
code_location_uri, timeout=CODE_DOWNLOAD_TIMEOUT_SECONDS
|
||||
).content
|
||||
return LambdaCode(
|
||||
location=code_location_uri,
|
||||
code_zip=zipfile.ZipFile(io.BytesIO(raw_code_zip)),
|
||||
)
|
||||
|
||||
def _fetch_function_code(self, function_name, function_region):
|
||||
try:
|
||||
regional_client = self.regional_clients[function_region]
|
||||
@@ -221,52 +200,18 @@ class Lambda(AWSService):
|
||||
FunctionName=function_name
|
||||
)
|
||||
if "Location" in function_information["Code"]:
|
||||
return self._download_code(function_information["Code"]["Location"])
|
||||
code_location_uri = function_information["Code"]["Location"]
|
||||
raw_code_zip = requests.get(code_location_uri).content
|
||||
return LambdaCode(
|
||||
location=code_location_uri,
|
||||
code_zip=zipfile.ZipFile(io.BytesIO(raw_code_zip)),
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
raise
|
||||
|
||||
def _get_layers_code(self):
|
||||
logger.info("Lambda - Getting Layer Code...")
|
||||
# Use a thread pool to handle the queueing and execution of the
|
||||
# _fetch_layer_code tasks, up to max_workers tasks concurrently.
|
||||
layers_to_fetch = {
|
||||
self.thread_pool.submit(
|
||||
self._fetch_layer_code, layer.arn, layer.region
|
||||
): layer
|
||||
for layer in self.layers.values()
|
||||
}
|
||||
|
||||
for fetched_layer_code in as_completed(layers_to_fetch):
|
||||
layer = layers_to_fetch[fetched_layer_code]
|
||||
try:
|
||||
layer_code = fetched_layer_code.result()
|
||||
if layer_code:
|
||||
yield layer, layer_code
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{layer.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _fetch_layer_code(self, layer_arn, layer_region):
|
||||
try:
|
||||
regional_client = self.regional_clients[layer_region]
|
||||
# Fetch by the full layer-version ARN: layers attached to a
|
||||
# function may be owned by another account (e.g. vendor or
|
||||
# AWS-provided layers), where a bare layer name would resolve
|
||||
# against the audited account instead.
|
||||
layer_version = regional_client.get_layer_version_by_arn(Arn=layer_arn)
|
||||
if "Location" in (layer_version.get("Content") or {}):
|
||||
return self._download_code(layer_version["Content"]["Location"])
|
||||
return None
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{layer_region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
raise
|
||||
|
||||
def _get_policy(self, regional_client):
|
||||
logger.info("Lambda - Getting Policy...")
|
||||
try:
|
||||
@@ -363,28 +308,6 @@ class Layer(BaseModel):
|
||||
parts = self.arn.split(":")
|
||||
return parts[4] if len(parts) >= 5 else ""
|
||||
|
||||
@property
|
||||
def region(self) -> str:
|
||||
"""Extract the region from the layer ARN.
|
||||
|
||||
A layer can only be attached to a function in the same region, so
|
||||
this is always one of the regions already being audited.
|
||||
"""
|
||||
parts = self.arn.split(":")
|
||||
return parts[3] if len(parts) >= 4 else ""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Extract the layer name from the ARN."""
|
||||
parts = self.arn.split(":")
|
||||
return parts[6] if len(parts) >= 7 else self.arn
|
||||
|
||||
@property
|
||||
def version(self) -> str:
|
||||
"""Extract the layer version from the ARN."""
|
||||
parts = self.arn.split(":")
|
||||
return parts[7] if len(parts) >= 8 else ""
|
||||
|
||||
|
||||
class DeadLetterConfig(BaseModel):
|
||||
target_arn: str
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
"""AWS Batch service client singleton."""
|
||||
|
||||
from prowler.providers.aws.services.batch.batch_service import Batch
|
||||
from prowler.providers.common.provider import Provider
|
||||
|
||||
batch_client = Batch(Provider.get_global_provider())
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "batch_job_definition_no_secrets",
|
||||
"CheckTitle": "AWS Batch job definitions have no secrets in environment variables or command parameters",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/AWS Security Best Practices",
|
||||
"Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices",
|
||||
"Sensitive Data Identifications/Passwords",
|
||||
"TTPs/Credential Access"
|
||||
],
|
||||
"ServiceName": "batch",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AwsBatchJobDefinition",
|
||||
"ResourceGroup": "container",
|
||||
"Description": "**AWS Batch job definitions** are analyzed for **plaintext secrets** placed in container `environment` variables and `command` parameters. It identifies values that resemble credentials (keys, tokens, passwords) within job definitions.",
|
||||
"Risk": "Exposed secrets in env vars or command parameters undermine confidentiality via logs, job metadata, and introspection.\n\nWith container or read-only API access, attackers can reuse credentials to read databases, modify records (integrity), pivot to other services, and trigger outages or unauthorized costs (availability).",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://docs.aws.amazon.com/batch/latest/userguide/job_definition_parameters.html"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws batch register-job-definition --job-definition-name <JOB_DEFINITION_NAME> --type container --container-properties '{\"image\":\"<IMAGE>\",\"secrets\":[{\"name\":\"<SECRET_NAME>\",\"valueFrom\":\"arn:aws:secretsmanager:<REGION>:<ACCOUNT_ID>:secret:<SECRET_NAME>-<RANDOM>\"}]}' # Register a new revision without plaintext secrets; reference Secrets Manager or SSM Parameter Store via valueFrom",
|
||||
"NativeIaC": "```yaml\nResources:\n <example_resource_name>:\n Type: AWS::Batch::JobDefinition\n Properties:\n Type: container\n ContainerProperties:\n Image: <image>\n Environment:\n - Name: DB_PASSWORD\n Value: !Ref <secret_parameter> # Reference SSM Parameter or Secrets Manager\n```",
|
||||
"Other": "1. In the AWS Console, go to Batch > Job Definitions and open your job definition\n2. Create a new revision\n3. Remove any sensitive values from Environment variables and command parameters\n4. Reference secrets from AWS Secrets Manager or SSM Parameter Store instead\n5. Save to create the new revision\n6. Update any Batch job queues to use the new job definition revision",
|
||||
"Terraform": "```hcl\nresource \"aws_batch_job_definition\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n type = \"container\"\n\n container_properties = jsonencode({\n image = \"<image>\"\n environment = [\n {\n name = \"DB_PASSWORD\"\n value = var.db_password # Use variable from Secrets Manager or SSM\n }\n ]\n })\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Store secrets in **AWS Secrets Manager** or **SSM Parameter Store** and inject them at runtime instead of plaintext env vars.\n\nApply **least privilege** via job role, enable regular **rotation**, avoid logging secret values, and prefer **ephemeral credentials** for downstream services.",
|
||||
"Url": "https://hub.prowler.com/check/batch_job_definition_no_secrets"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"secrets"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Only container job definitions are evaluated (containerProperties.environment and command). Multi-node parallel (nodeProperties) and EKS (eksProperties) job definitions are not analyzed."
|
||||
}
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
from json import dumps
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.lib.utils.utils import (
|
||||
SecretsScanError,
|
||||
annotate_verified_secrets,
|
||||
detect_secrets_scan_batch,
|
||||
)
|
||||
from prowler.providers.aws.services.batch.batch_client import batch_client
|
||||
|
||||
|
||||
class batch_job_definition_no_secrets(Check):
|
||||
"""Detect secrets in AWS Batch job definition environment variables and commands."""
|
||||
|
||||
def execute(self) -> list[Check_Report_AWS]:
|
||||
"""Scan job definitions for hardcoded secrets in env vars and commands."""
|
||||
findings = []
|
||||
|
||||
secrets_ignore_patterns = batch_client.audit_config.get(
|
||||
"secrets_ignore_patterns", []
|
||||
)
|
||||
validate = batch_client.audit_config.get("secrets_validate", False)
|
||||
|
||||
job_definitions = list(batch_client.job_definitions.values())
|
||||
|
||||
def scan_payloads():
|
||||
"""Yield index-keyed payloads for each env var and the command."""
|
||||
for jd_index, job_definition in enumerate(job_definitions):
|
||||
container = job_definition.container_properties
|
||||
|
||||
for env_index, env_var in enumerate(container.environment):
|
||||
yield (jd_index, env_index), dumps(
|
||||
{env_var.name: env_var.value}, indent=2
|
||||
)
|
||||
|
||||
if container.command:
|
||||
yield (
|
||||
(jd_index, "command"),
|
||||
" ".join(container.command),
|
||||
)
|
||||
|
||||
scan_error = None
|
||||
try:
|
||||
batch_results = detect_secrets_scan_batch(
|
||||
scan_payloads(),
|
||||
excluded_secrets=secrets_ignore_patterns,
|
||||
validate=validate,
|
||||
)
|
||||
except SecretsScanError as error:
|
||||
batch_results = {}
|
||||
scan_error = error
|
||||
|
||||
for jd_index, job_definition in enumerate(job_definitions):
|
||||
report = Check_Report_AWS(
|
||||
metadata=self.metadata(),
|
||||
resource=job_definition,
|
||||
)
|
||||
|
||||
report.resource_id = f"{job_definition.name}:{job_definition.revision}"
|
||||
report.status = "PASS"
|
||||
|
||||
extended_status_parts = []
|
||||
all_secrets = []
|
||||
|
||||
container = job_definition.container_properties
|
||||
|
||||
if scan_error and (container.environment or container.command):
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = (
|
||||
f"Could not scan Batch job definition "
|
||||
f"{job_definition.name} with revision "
|
||||
f"{job_definition.revision} for secrets: "
|
||||
f"{scan_error}; manual review is required."
|
||||
)
|
||||
findings.append(report)
|
||||
continue
|
||||
|
||||
for env_index, env_var in enumerate(container.environment):
|
||||
env_secrets = batch_results.get((jd_index, env_index))
|
||||
if env_secrets:
|
||||
all_secrets.extend(env_secrets)
|
||||
secrets_string = ", ".join(
|
||||
f"{secret['type']} on the environment variable {env_var.name}"
|
||||
for secret in env_secrets
|
||||
)
|
||||
extended_status_parts.append(
|
||||
f"Secrets in environment variables -> {secrets_string}"
|
||||
)
|
||||
|
||||
if container.command:
|
||||
command_secrets = batch_results.get((jd_index, "command"))
|
||||
if command_secrets:
|
||||
all_secrets.extend(command_secrets)
|
||||
secrets_string = ", ".join(
|
||||
secret["type"] for secret in command_secrets
|
||||
)
|
||||
extended_status_parts.append(
|
||||
f"Secrets in command -> {secrets_string}"
|
||||
)
|
||||
|
||||
if extended_status_parts:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Potential secrets found in Batch job definition "
|
||||
f"{job_definition.name} with revision "
|
||||
f"{job_definition.revision}: "
|
||||
+ "; ".join(extended_status_parts)
|
||||
+ "."
|
||||
)
|
||||
annotate_verified_secrets(report, all_secrets)
|
||||
else:
|
||||
report.status_extended = (
|
||||
f"No secrets found in Batch job definition "
|
||||
f"{job_definition.name} with revision "
|
||||
f"{job_definition.revision}."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -1,105 +0,0 @@
|
||||
from itertools import zip_longest
|
||||
from typing import Optional
|
||||
|
||||
from pydantic.v1 import BaseModel
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.resource_limit import get_resource_scan_limit, limit_resources
|
||||
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
|
||||
from prowler.providers.aws.lib.service.service import AWSService
|
||||
|
||||
|
||||
class ContainerEnvVariable(BaseModel):
|
||||
"""An environment variable name-value pair."""
|
||||
|
||||
name: str
|
||||
value: str
|
||||
|
||||
|
||||
class BatchContainerProperties(BaseModel):
|
||||
"""Container properties for an AWS Batch job definition."""
|
||||
|
||||
image: Optional[str]
|
||||
command: list[str] = []
|
||||
environment: list[ContainerEnvVariable] = []
|
||||
|
||||
|
||||
class BatchJobDefinition(BaseModel):
|
||||
"""An AWS Batch job definition with its container properties."""
|
||||
|
||||
name: str
|
||||
arn: str
|
||||
revision: int
|
||||
region: str
|
||||
container_properties: BatchContainerProperties
|
||||
|
||||
|
||||
class Batch(AWSService):
|
||||
"""AWS Batch service client for listing job definitions."""
|
||||
|
||||
def __init__(self, provider):
|
||||
super().__init__(__class__.__name__, provider)
|
||||
self.job_definitions = {}
|
||||
self._job_definitions_by_region = {}
|
||||
self.job_definition_limit = get_resource_scan_limit(
|
||||
self.audit_config, "max_batch_job_definitions"
|
||||
)
|
||||
self.__threading_call__(self._list_job_definitions)
|
||||
self._select_job_definitions_for_analysis()
|
||||
|
||||
def _list_job_definitions(self, regional_client):
|
||||
"""List ACTIVE job definitions for a regional client."""
|
||||
logger.info("Batch - Listing Job Definitions...")
|
||||
try:
|
||||
paginator = regional_client.get_paginator("describe_job_definitions")
|
||||
regional_job_definitions = []
|
||||
# Deregistered (INACTIVE) revisions are excluded: they cannot run
|
||||
# new jobs, and reporting them would only produce noise.
|
||||
for page in paginator.paginate(status="ACTIVE"):
|
||||
for job in page.get("jobDefinitions", []):
|
||||
if self.audit_resources and not is_resource_filtered(
|
||||
job["jobDefinitionArn"], self.audit_resources
|
||||
):
|
||||
continue
|
||||
container = job.get("containerProperties", {})
|
||||
environment = [
|
||||
ContainerEnvVariable(
|
||||
name=env["name"], value=env.get("value", "")
|
||||
)
|
||||
for env in container.get("environment", [])
|
||||
]
|
||||
regional_job_definitions.append(
|
||||
BatchJobDefinition(
|
||||
name=job["jobDefinitionName"],
|
||||
arn=job["jobDefinitionArn"],
|
||||
revision=job["revision"],
|
||||
region=regional_client.region,
|
||||
container_properties=BatchContainerProperties(
|
||||
image=container.get("image"),
|
||||
command=container.get("command", []),
|
||||
environment=environment,
|
||||
),
|
||||
)
|
||||
)
|
||||
self._job_definitions_by_region[regional_client.region] = (
|
||||
regional_job_definitions
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def _select_job_definitions_for_analysis(self):
|
||||
"""Apply the global resource limit, interleaving regions fairly."""
|
||||
interleaved = [
|
||||
job_definition
|
||||
for region_batch in zip_longest(*self._job_definitions_by_region.values())
|
||||
for job_definition in region_batch
|
||||
if job_definition
|
||||
]
|
||||
self.job_definitions = {
|
||||
job_definition.arn: job_definition
|
||||
for job_definition in limit_resources(
|
||||
interleaved, self.job_definition_limit
|
||||
)
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_password_protection_custom_banned_list_enforced",
|
||||
"CheckTitle": "Entra custom banned password list is enforced",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "NotDefined",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "The tenant Password Rule Settings should enforce a **custom banned password list** (**EnableBannedPasswordCheck** true with a non-empty **BannedPasswordList**). This blocks organization-specific weak or predictable passwords (e.g., company name, products, locations) in addition to Microsoft's global banned list.",
|
||||
"Risk": "Without a custom **banned password** list, users can choose passwords that are predictable for the specific organization (brand names, local terms), which are easy targets for **password spraying** and guessing attacks.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/entra/identity/authentication/concept-password-ban-bad"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Protection** > **Authentication methods** > **Password protection**\n3. Set **Enforce custom list** to **Yes**\n4. Add organization-specific terms to the **Custom banned password list**\n5. Click **Save**",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable the custom banned password list and populate it with terms relevant to the organization to strengthen protection against weak passwords beyond the global banned list.",
|
||||
"Url": "https://hub.prowler.com/check/entra_password_protection_custom_banned_list_enforced"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access",
|
||||
"e3"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
|
||||
)
|
||||
|
||||
|
||||
class entra_password_protection_custom_banned_list_enforced(Check):
|
||||
"""Check if the Entra custom banned password list is enforced.
|
||||
|
||||
The Password Rule Settings directory setting should enforce a custom banned
|
||||
password list (EnableBannedPasswordCheck) with a non-empty BannedPasswordList so
|
||||
that organization-specific weak passwords are rejected in addition to the global
|
||||
banned list.
|
||||
|
||||
- PASS: The custom banned password list is enforced and non-empty.
|
||||
- FAIL: The custom banned password list is not enforced or is empty.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the custom banned password list enforcement check.
|
||||
|
||||
Evaluate whether the Password Rule Settings directory setting enforces a
|
||||
non-empty custom banned password list. When the settings object is absent,
|
||||
no finding is produced.
|
||||
|
||||
Returns:
|
||||
List[CheckReportM365]: A list with a single report when the Password Rule
|
||||
Settings exist, or an empty list when they are absent.
|
||||
"""
|
||||
findings = []
|
||||
settings = entra_client.directory_settings.get(
|
||||
PASSWORD_RULE_SETTINGS_TEMPLATE_ID
|
||||
)
|
||||
if not settings:
|
||||
return findings
|
||||
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=settings or {},
|
||||
resource_name="Password Rule Settings",
|
||||
resource_id=PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
"The custom banned password list is not enforced in the tenant."
|
||||
)
|
||||
|
||||
if settings:
|
||||
enforced = (
|
||||
str(settings.get("EnableBannedPasswordCheck", "")).lower() == "true"
|
||||
)
|
||||
banned_list = settings.get("BannedPasswordList", "") or ""
|
||||
if enforced and banned_list.strip():
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
"The custom banned password list is enforced in the tenant."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
return findings
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_password_protection_lockout_duration_configured",
|
||||
"CheckTitle": "Smart lockout duration is set to 60 seconds or more",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "low",
|
||||
"ResourceType": "NotDefined",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "The tenant Password Rule Settings should set the smart **LockoutDurationInSeconds** to **60 or more**. The lockout duration determines how long an account remains locked out before the user can attempt to sign in again.",
|
||||
"Risk": "A short lockout duration allows attackers to resume **brute-force** or **password-spray** attempts sooner, reducing the effectiveness of **smart lockout** as a throttling control.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/entra/identity/authentication/howto-password-smart-lockout"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Protection** > **Authentication methods** > **Password protection**\n3. Set **Lockout duration in seconds** to **60** or higher\n4. Click **Save**",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Set the smart lockout duration to 60 seconds or more so locked-out accounts remain locked long enough to throttle automated password attacks.",
|
||||
"Url": "https://hub.prowler.com/check/entra_password_protection_lockout_duration_configured"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access",
|
||||
"e3"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
|
||||
)
|
||||
|
||||
# CIS recommends a lockout duration of 60 seconds or more.
|
||||
MIN_LOCKOUT_DURATION_SECONDS = 60
|
||||
|
||||
|
||||
class entra_password_protection_lockout_duration_configured(Check):
|
||||
"""Check if the smart lockout duration is set to 60 seconds or more.
|
||||
|
||||
The Password Rule Settings directory setting should set LockoutDurationInSeconds
|
||||
to 60 or more so a locked-out account remains locked long enough to slow down
|
||||
automated attacks.
|
||||
|
||||
- PASS: The lockout duration is 60 seconds or more.
|
||||
- FAIL: The lockout duration is less than 60 seconds or not configured.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the smart lockout duration check.
|
||||
|
||||
Returns:
|
||||
List[CheckReportM365]: Reports for the Password Rule Settings, or an
|
||||
empty list when the settings are absent.
|
||||
"""
|
||||
findings = []
|
||||
settings = entra_client.directory_settings.get(
|
||||
PASSWORD_RULE_SETTINGS_TEMPLATE_ID
|
||||
)
|
||||
if not settings:
|
||||
return findings
|
||||
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=settings or {},
|
||||
resource_name="Password Rule Settings",
|
||||
resource_id=PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
"The smart lockout duration is not set to 60 seconds or more."
|
||||
)
|
||||
|
||||
if settings:
|
||||
try:
|
||||
duration = int(settings.get("LockoutDurationInSeconds"))
|
||||
except (TypeError, ValueError):
|
||||
duration = None
|
||||
if duration is not None and duration >= MIN_LOCKOUT_DURATION_SECONDS:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"The smart lockout duration is set to {duration} seconds, at or "
|
||||
f"above the recommended minimum of {MIN_LOCKOUT_DURATION_SECONDS}."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
return findings
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_password_protection_lockout_threshold_limited",
|
||||
"CheckTitle": "Smart lockout threshold is set to 10 or less",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "NotDefined",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "The tenant Password Rule Settings should set the smart **LockoutThreshold** to **10 or less**. The lockout threshold determines how many failed sign-in attempts are permitted before an account is placed in a locked-out state.",
|
||||
"Risk": "A high lockout threshold gives attackers more attempts per account during **password spraying** and **brute-force** attacks before lockout is triggered, increasing the chance of a successful credential compromise.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/entra/identity/authentication/howto-password-smart-lockout"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Protection** > **Authentication methods** > **Password protection**\n3. Set **Lockout threshold** to **10** or less\n4. Click **Save**",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Set the smart lockout threshold to 10 or less so accounts lock after a small number of failed sign-in attempts, limiting brute-force and password-spray attacks.",
|
||||
"Url": "https://hub.prowler.com/check/entra_password_protection_lockout_threshold_limited"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access",
|
||||
"e3"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
|
||||
)
|
||||
|
||||
# CIS recommends a smart lockout threshold of 10 or less.
|
||||
MAX_LOCKOUT_THRESHOLD = 10
|
||||
|
||||
|
||||
class entra_password_protection_lockout_threshold_limited(Check):
|
||||
"""Check if the smart lockout threshold is set to 10 or less.
|
||||
|
||||
The Password Rule Settings directory setting should set LockoutThreshold to 10 or
|
||||
less so that accounts are locked after a small number of failed sign-in attempts.
|
||||
|
||||
- PASS: The lockout threshold is 10 or less.
|
||||
- FAIL: The lockout threshold is greater than 10 or not configured.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the smart lockout threshold check.
|
||||
|
||||
Evaluate whether the Password Rule Settings directory setting limits the smart
|
||||
lockout threshold to the recommended maximum. When the settings object is
|
||||
absent, no finding is produced.
|
||||
|
||||
Returns:
|
||||
List[CheckReportM365]: A list with a single report when the Password Rule
|
||||
Settings exist, or an empty list when they are absent.
|
||||
"""
|
||||
findings = []
|
||||
settings = entra_client.directory_settings.get(
|
||||
PASSWORD_RULE_SETTINGS_TEMPLATE_ID
|
||||
)
|
||||
if not settings:
|
||||
return findings
|
||||
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=settings or {},
|
||||
resource_name="Password Rule Settings",
|
||||
resource_id=PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = "The smart lockout threshold is not set to 10 or less."
|
||||
|
||||
if settings:
|
||||
try:
|
||||
threshold = int(settings.get("LockoutThreshold"))
|
||||
except (TypeError, ValueError):
|
||||
threshold = None
|
||||
if threshold is not None and threshold <= MAX_LOCKOUT_THRESHOLD:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"The smart lockout threshold is set to {threshold}, within the "
|
||||
f"recommended limit of {MAX_LOCKOUT_THRESHOLD}."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
return findings
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_password_protection_on_premises_enforced",
|
||||
"CheckTitle": "Entra password protection is enforced on on-premises Active Directory",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "NotDefined",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "The tenant Password Rule Settings should enable **password protection on Windows Server Active Directory** (**EnableBannedPasswordCheckOnPremises**) with the mode set to **Enforced**. This extends Entra banned-password checks to on-premises password changes in hybrid environments. This control only applies to tenants with on-premises directory synchronization.",
|
||||
"Risk": "Without **on-premises** enforcement, users in hybrid environments can set weak or banned passwords directly in Active Directory, bypassing Entra password protection and weakening the organization's overall password posture.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/entra/identity/authentication/concept-password-ban-bad-on-premises"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Protection** > **Authentication methods** > **Password protection**\n3. Set **Enable password protection on Windows Server Active Directory** to **Yes**\n4. Set **Mode** to **Enforced**\n5. Click **Save** (requires the Entra Password Protection agents deployed on-premises)",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable on-premises Entra password protection in Enforced mode and deploy the password protection proxy and DC agents so banned-password rules apply to on-premises password changes.",
|
||||
"Url": "https://hub.prowler.com/check/entra_password_protection_on_premises_enforced"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access",
|
||||
"e3"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
|
||||
)
|
||||
|
||||
|
||||
class entra_password_protection_on_premises_enforced(Check):
|
||||
"""Check if Entra password protection is enforced on on-premises Active Directory.
|
||||
|
||||
The Password Rule Settings directory setting should enable password protection on
|
||||
Windows Server Active Directory (EnableBannedPasswordCheckOnPremises) with the
|
||||
mode set to Enforced, so banned-password rules apply to hybrid on-premises
|
||||
password changes.
|
||||
|
||||
This check applies only to hybrid tenants with on-premises synchronization.
|
||||
|
||||
- PASS: On-premises password protection is enabled and set to Enforced.
|
||||
- FAIL: On-premises password protection is disabled or set to Audit only.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the on-premises password protection enforcement check.
|
||||
|
||||
Evaluate whether the Password Rule Settings directory setting enables and
|
||||
enforces banned-password protection for on-premises Active Directory. When the
|
||||
settings object is absent or the tenant is confirmed cloud-only, no finding is
|
||||
produced.
|
||||
|
||||
Returns:
|
||||
List[CheckReportM365]: A list with a single report when the Password Rule
|
||||
Settings exist for a hybrid or unknown tenant, or an empty list when they
|
||||
are absent or the tenant is confirmed cloud-only.
|
||||
"""
|
||||
findings = []
|
||||
organizations = entra_client.organizations or []
|
||||
if organizations and not any(
|
||||
organization.on_premises_sync_enabled for organization in organizations
|
||||
):
|
||||
return findings
|
||||
|
||||
settings = entra_client.directory_settings.get(
|
||||
PASSWORD_RULE_SETTINGS_TEMPLATE_ID
|
||||
)
|
||||
if not settings:
|
||||
return findings
|
||||
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=settings or {},
|
||||
resource_name="Password Rule Settings",
|
||||
resource_id=PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
"On-premises password protection is not enforced in the tenant."
|
||||
)
|
||||
|
||||
if settings:
|
||||
enabled = (
|
||||
str(settings.get("EnableBannedPasswordCheckOnPremises", "")).lower()
|
||||
== "true"
|
||||
)
|
||||
mode = str(settings.get("BannedPasswordCheckOnPremisesMode", "")).lower()
|
||||
if enabled and mode == "enforced":
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
"On-premises password protection is enabled and enforced in the "
|
||||
"tenant."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
return findings
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_policy_default_user_cannot_create_m365_groups",
|
||||
"CheckTitle": "Non-admin users cannot create Microsoft 365 groups",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "NotDefined",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "The tenant Group.Unified directory setting should have **EnableGroupCreation** set to false so that non-admin users cannot create Microsoft 365 groups through the portal, API, or PowerShell. Microsoft 365 group creation should be delegated to a controlled set of users.",
|
||||
"Risk": "When any user can create Microsoft 365 groups, they can provision associated resources (SharePoint sites, Teams, mailboxes) without oversight, leading to group sprawl, ungoverned data locations, and a larger attack surface.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/microsoft-365/solutions/manage-creation-of-groups"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Identity** > **Groups** > **General**\n3. Set **Users can create Microsoft 365 groups in Azure portals, API or PowerShell** to **No**\n4. Optionally grant creation rights to a specific security group\n5. Click **Save**",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Disable self-service Microsoft 365 group creation for non-admin users and delegate creation to an approved security group as needed.",
|
||||
"Url": "https://hub.prowler.com/check/entra_policy_default_user_cannot_create_m365_groups"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access",
|
||||
"e3"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
GROUP_UNIFIED_SETTINGS_TEMPLATE_ID,
|
||||
)
|
||||
|
||||
|
||||
class entra_policy_default_user_cannot_create_m365_groups(Check):
|
||||
"""Check if default users are restricted from creating Microsoft 365 groups.
|
||||
|
||||
The Group.Unified directory setting should have EnableGroupCreation set to false
|
||||
so that non-admin users cannot create Microsoft 365 groups. If the setting does
|
||||
not exist, the tenant uses the default, which allows all users to create groups.
|
||||
|
||||
- PASS: Non-admin users cannot create Microsoft 365 groups.
|
||||
- FAIL: Non-admin users are allowed to create Microsoft 365 groups.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Evaluate whether default users can create Microsoft 365 groups.
|
||||
|
||||
Inspects the Group.Unified directory setting to determine whether non-admin
|
||||
users are allowed to create Microsoft 365 groups. When the setting is absent
|
||||
the tenant default (group creation allowed) applies.
|
||||
|
||||
Returns:
|
||||
List[CheckReportM365]: A single report indicating whether non-admin users
|
||||
are restricted from creating Microsoft 365 groups.
|
||||
"""
|
||||
findings = []
|
||||
settings = entra_client.directory_settings.get(
|
||||
GROUP_UNIFIED_SETTINGS_TEMPLATE_ID
|
||||
)
|
||||
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=settings or {},
|
||||
resource_name="Group.Unified Settings",
|
||||
resource_id=GROUP_UNIFIED_SETTINGS_TEMPLATE_ID,
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
"Non-admin users are allowed to create Microsoft 365 groups."
|
||||
)
|
||||
|
||||
if settings and str(settings.get("EnableGroupCreation", "")).lower() == "false":
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
"Non-admin users are not allowed to create Microsoft 365 groups."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
return findings
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_policy_default_user_cannot_create_security_groups",
|
||||
"CheckTitle": "Non-admin users cannot create security groups",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "NotDefined",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "Microsoft Entra tenant's authorization policy should restrict **non-admin users** from creating **security groups**. Security groups can be used to grant access to resources across Microsoft 365, so their creation should be limited to administrators to preserve least privilege and prevent uncontrolled access grants.",
|
||||
"Risk": "When any user can create security groups, they may grant themselves or others access to resources, circumventing governance controls. Uncontrolled group sprawl also complicates access reviews and increases the attack surface for privilege escalation.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/entra/identity/users/groups-self-service-management"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Identity** > **Groups** > **All groups** > **General**\n3. Under **Security groups**, set **Users can create security groups in Azure portals, API or PowerShell** to **No**\n4. Click **Save**",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Restrict security group creation to administrators by disabling self-service security group creation for non-admin users. Grant group-creation rights only to specific roles or delegated owners as required.",
|
||||
"Url": "https://hub.prowler.com/check/entra_policy_default_user_cannot_create_security_groups"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access",
|
||||
"e3"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
|
||||
|
||||
class entra_policy_default_user_cannot_create_security_groups(Check):
|
||||
"""Check if default users are restricted from creating security groups.
|
||||
|
||||
This check verifies whether the authorization policy prevents non-admin users
|
||||
from creating security groups in Microsoft Entra ID.
|
||||
|
||||
- PASS: Non-admin users cannot create security groups.
|
||||
- FAIL: Non-admin users are allowed to create security groups.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the check for security group creation restrictions.
|
||||
|
||||
This method examines the authorization policy settings to determine if
|
||||
non-admin users are allowed to create security groups. If security group
|
||||
creation is restricted, the check passes.
|
||||
|
||||
Returns:
|
||||
List[CheckReportM365]: A list containing the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
auth_policy = entra_client.authorization_policy
|
||||
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=auth_policy if auth_policy else {},
|
||||
resource_name=auth_policy.name if auth_policy else "Authorization Policy",
|
||||
resource_id=auth_policy.id if auth_policy else "authorizationPolicy",
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
"Non-admin users are allowed to create security groups."
|
||||
)
|
||||
|
||||
permissions = getattr(auth_policy, "default_user_role_permissions", None)
|
||||
if permissions and permissions.allowed_to_create_security_groups is False:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
"Non-admin users are not allowed to create security groups."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
return findings
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user