mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-06-13 05:59:47 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f03d83872e | |||
| bb620022f5 | |||
| 27a81defec | |||
| a81293d2ea | |||
| 80427dd127 | |||
| 14e9506b87 | |||
| 3e72d575d4 | |||
| 79825d35fc | |||
| 6215c1ba46 |
@@ -48,34 +48,8 @@ jobs:
|
||||
id: set-short-sha
|
||||
run: echo "short-sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
|
||||
|
||||
notify-release-started:
|
||||
if: github.repository == 'prowler-cloud/prowler' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
message-ts: ${{ steps.slack-notification.outputs.ts }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Notify container push started
|
||||
id: slack-notification
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
COMPONENT: API
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-started.json"
|
||||
|
||||
container-build-push:
|
||||
needs: [setup, notify-release-started]
|
||||
if: always() && needs.setup.result == 'success' && (needs.notify-release-started.result == 'success' || needs.notify-release-started.result == 'skipped')
|
||||
needs: setup
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -104,6 +78,21 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Notify container push started
|
||||
id: slack-notification-started
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
COMPONENT: API
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-started.json"
|
||||
|
||||
- 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'
|
||||
@@ -117,6 +106,23 @@ jobs:
|
||||
cache-from: type=gha,scope=${{ matrix.arch }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.arch }}
|
||||
|
||||
- name: Notify container push completed
|
||||
if: (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && always()
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
MESSAGE_TS: ${{ steps.slack-notification-started.outputs.ts }}
|
||||
COMPONENT: API
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
||||
step-outcome: ${{ steps.container-push.outcome }}
|
||||
update-ts: ${{ steps.slack-notification-started.outputs.ts }}
|
||||
|
||||
# Create and push multi-architecture manifest
|
||||
create-manifest:
|
||||
needs: [setup, container-build-push]
|
||||
@@ -163,40 +169,6 @@ jobs:
|
||||
regctl tag delete "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.short-sha }}-arm64" || true
|
||||
echo "Cleanup completed"
|
||||
|
||||
notify-release-completed:
|
||||
if: always() && needs.notify-release-started.result == 'success' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
needs: [setup, notify-release-started, container-build-push, create-manifest]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Determine overall outcome
|
||||
id: outcome
|
||||
run: |
|
||||
if [[ "${{ needs.container-build-push.result }}" == "success" && "${{ needs.create-manifest.result }}" == "success" ]]; then
|
||||
echo "outcome=success" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "outcome=failure" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Notify container push completed
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
MESSAGE_TS: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
COMPONENT: API
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
||||
step-outcome: ${{ steps.outcome.outputs.outcome }}
|
||||
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
|
||||
trigger-deployment:
|
||||
if: github.event_name == 'push'
|
||||
needs: [setup, container-build-push]
|
||||
|
||||
@@ -47,34 +47,8 @@ jobs:
|
||||
id: set-short-sha
|
||||
run: echo "short-sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
|
||||
|
||||
notify-release-started:
|
||||
if: github.repository == 'prowler-cloud/prowler' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
message-ts: ${{ steps.slack-notification.outputs.ts }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Notify container push started
|
||||
id: slack-notification
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
COMPONENT: MCP
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-started.json"
|
||||
|
||||
container-build-push:
|
||||
needs: [setup, notify-release-started]
|
||||
if: always() && needs.setup.result == 'success' && (needs.notify-release-started.result == 'success' || needs.notify-release-started.result == 'skipped')
|
||||
needs: setup
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -102,6 +76,21 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Notify container push started
|
||||
id: slack-notification-started
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
COMPONENT: MCP
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-started.json"
|
||||
|
||||
- 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'
|
||||
@@ -123,6 +112,23 @@ jobs:
|
||||
cache-from: type=gha,scope=${{ matrix.arch }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.arch }}
|
||||
|
||||
- name: Notify container push completed
|
||||
if: (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && always()
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
MESSAGE_TS: ${{ steps.slack-notification-started.outputs.ts }}
|
||||
COMPONENT: MCP
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
||||
step-outcome: ${{ steps.container-push.outcome }}
|
||||
update-ts: ${{ steps.slack-notification-started.outputs.ts }}
|
||||
|
||||
# Create and push multi-architecture manifest
|
||||
create-manifest:
|
||||
needs: [setup, container-build-push]
|
||||
@@ -169,40 +175,6 @@ jobs:
|
||||
regctl tag delete "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.short-sha }}-arm64" || true
|
||||
echo "Cleanup completed"
|
||||
|
||||
notify-release-completed:
|
||||
if: always() && needs.notify-release-started.result == 'success' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
needs: [setup, notify-release-started, container-build-push, create-manifest]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Determine overall outcome
|
||||
id: outcome
|
||||
run: |
|
||||
if [[ "${{ needs.container-build-push.result }}" == "success" && "${{ needs.create-manifest.result }}" == "success" ]]; then
|
||||
echo "outcome=success" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "outcome=failure" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Notify container push completed
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
MESSAGE_TS: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
COMPONENT: MCP
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
||||
step-outcome: ${{ steps.outcome.outputs.outcome }}
|
||||
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
|
||||
trigger-deployment:
|
||||
if: github.event_name == 'push'
|
||||
needs: [setup, container-build-push]
|
||||
|
||||
@@ -50,15 +50,30 @@ env:
|
||||
AWS_REGION: us-east-1
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
container-build-push:
|
||||
if: github.repository == 'prowler-cloud/prowler'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-latest
|
||||
arch: amd64
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
arch: arm64
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
prowler_version: ${{ steps.get-prowler-version.outputs.prowler_version }}
|
||||
prowler_version_major: ${{ steps.get-prowler-version.outputs.prowler_version_major }}
|
||||
latest_tag: ${{ steps.get-prowler-version.outputs.latest_tag }}
|
||||
stable_tag: ${{ steps.get-prowler-version.outputs.stable_tag }}
|
||||
env:
|
||||
POETRY_VIRTUALENVS_CREATE: 'false'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -78,24 +93,32 @@ jobs:
|
||||
run: |
|
||||
PROWLER_VERSION="$(poetry version -s 2>/dev/null)"
|
||||
echo "prowler_version=${PROWLER_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_ENV}"
|
||||
|
||||
# Extract major version
|
||||
PROWLER_VERSION_MAJOR="${PROWLER_VERSION%%.*}"
|
||||
echo "prowler_version_major=${PROWLER_VERSION_MAJOR}" >> "${GITHUB_OUTPUT}"
|
||||
echo "PROWLER_VERSION_MAJOR=${PROWLER_VERSION_MAJOR}" >> "${GITHUB_ENV}"
|
||||
|
||||
# Set version-specific tags
|
||||
case ${PROWLER_VERSION_MAJOR} in
|
||||
3)
|
||||
echo "LATEST_TAG=v3-latest" >> "${GITHUB_ENV}"
|
||||
echo "STABLE_TAG=v3-stable" >> "${GITHUB_ENV}"
|
||||
echo "latest_tag=v3-latest" >> "${GITHUB_OUTPUT}"
|
||||
echo "stable_tag=v3-stable" >> "${GITHUB_OUTPUT}"
|
||||
echo "✓ Prowler v3 detected - tags: v3-latest, v3-stable"
|
||||
;;
|
||||
4)
|
||||
echo "LATEST_TAG=v4-latest" >> "${GITHUB_ENV}"
|
||||
echo "STABLE_TAG=v4-stable" >> "${GITHUB_ENV}"
|
||||
echo "latest_tag=v4-latest" >> "${GITHUB_OUTPUT}"
|
||||
echo "stable_tag=v4-stable" >> "${GITHUB_OUTPUT}"
|
||||
echo "✓ Prowler v4 detected - tags: v4-latest, v4-stable"
|
||||
;;
|
||||
5)
|
||||
echo "LATEST_TAG=latest" >> "${GITHUB_ENV}"
|
||||
echo "STABLE_TAG=stable" >> "${GITHUB_ENV}"
|
||||
echo "latest_tag=latest" >> "${GITHUB_OUTPUT}"
|
||||
echo "stable_tag=stable" >> "${GITHUB_OUTPUT}"
|
||||
echo "✓ Prowler v5 detected - tags: latest, stable"
|
||||
@@ -106,53 +129,6 @@ jobs:
|
||||
;;
|
||||
esac
|
||||
|
||||
notify-release-started:
|
||||
if: github.repository == 'prowler-cloud/prowler' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
message-ts: ${{ steps.slack-notification.outputs.ts }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Notify container push started
|
||||
id: slack-notification
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
COMPONENT: SDK
|
||||
RELEASE_TAG: ${{ needs.setup.outputs.prowler_version }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-started.json"
|
||||
|
||||
container-build-push:
|
||||
needs: [setup, notify-release-started]
|
||||
if: always() && needs.setup.result == 'success' && (needs.notify-release-started.result == 'success' || needs.notify-release-started.result == 'skipped')
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-latest
|
||||
arch: amd64
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
arch: arm64
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
@@ -171,6 +147,21 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Notify container push started
|
||||
id: slack-notification-started
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
COMPONENT: SDK
|
||||
RELEASE_TAG: ${{ env.PROWLER_VERSION }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-started.json"
|
||||
|
||||
- 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'
|
||||
@@ -181,13 +172,30 @@ jobs:
|
||||
push: true
|
||||
platforms: ${{ matrix.platform }}
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.latest_tag }}-${{ matrix.arch }}
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }}-${{ matrix.arch }}
|
||||
cache-from: type=gha,scope=${{ matrix.arch }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.arch }}
|
||||
|
||||
- name: Notify container push completed
|
||||
if: (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && always()
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
MESSAGE_TS: ${{ steps.slack-notification-started.outputs.ts }}
|
||||
COMPONENT: SDK
|
||||
RELEASE_TAG: ${{ env.PROWLER_VERSION }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
||||
step-outcome: ${{ steps.container-push.outcome }}
|
||||
update-ts: ${{ steps.slack-notification-started.outputs.ts }}
|
||||
|
||||
# Create and push multi-architecture manifest
|
||||
create-manifest:
|
||||
needs: [setup, container-build-push]
|
||||
needs: [container-build-push]
|
||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -214,24 +222,24 @@ jobs:
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.latest_tag }} \
|
||||
-t ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.latest_tag }} \
|
||||
-t ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.latest_tag }} \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.latest_tag }}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.latest_tag }}-arm64
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.container-build-push.outputs.latest_tag }} \
|
||||
-t ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.container-build-push.outputs.latest_tag }} \
|
||||
-t ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.container-build-push.outputs.latest_tag }} \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.container-build-push.outputs.latest_tag }}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.container-build-push.outputs.latest_tag }}-arm64
|
||||
|
||||
- name: Create and push manifests for release event
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ needs.setup.outputs.prowler_version }} \
|
||||
-t ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ needs.setup.outputs.stable_tag }} \
|
||||
-t ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ needs.setup.outputs.prowler_version }} \
|
||||
-t ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ needs.setup.outputs.stable_tag }} \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.prowler_version }} \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.stable_tag }} \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.latest_tag }}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.latest_tag }}-arm64
|
||||
-t ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ needs.container-build-push.outputs.prowler_version }} \
|
||||
-t ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ needs.container-build-push.outputs.stable_tag }} \
|
||||
-t ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ needs.container-build-push.outputs.prowler_version }} \
|
||||
-t ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ needs.container-build-push.outputs.stable_tag }} \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.container-build-push.outputs.prowler_version }} \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.container-build-push.outputs.stable_tag }} \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.container-build-push.outputs.latest_tag }}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.container-build-push.outputs.latest_tag }}-arm64
|
||||
|
||||
- name: Install regctl
|
||||
if: always()
|
||||
@@ -241,47 +249,13 @@ jobs:
|
||||
if: always()
|
||||
run: |
|
||||
echo "Cleaning up intermediate tags..."
|
||||
regctl tag delete "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.latest_tag }}-amd64" || true
|
||||
regctl tag delete "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.latest_tag }}-arm64" || true
|
||||
regctl tag delete "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.container-build-push.outputs.latest_tag }}-amd64" || true
|
||||
regctl tag delete "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.container-build-push.outputs.latest_tag }}-arm64" || true
|
||||
echo "Cleanup completed"
|
||||
|
||||
notify-release-completed:
|
||||
if: always() && needs.notify-release-started.result == 'success' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
needs: [setup, notify-release-started, container-build-push, create-manifest]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Determine overall outcome
|
||||
id: outcome
|
||||
run: |
|
||||
if [[ "${{ needs.container-build-push.result }}" == "success" && "${{ needs.create-manifest.result }}" == "success" ]]; then
|
||||
echo "outcome=success" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "outcome=failure" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Notify container push completed
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
MESSAGE_TS: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
COMPONENT: SDK
|
||||
RELEASE_TAG: ${{ needs.setup.outputs.prowler_version }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
||||
step-outcome: ${{ steps.outcome.outputs.outcome }}
|
||||
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
|
||||
dispatch-v3-deployment:
|
||||
if: needs.setup.outputs.prowler_version_major == '3'
|
||||
needs: [setup, container-build-push]
|
||||
if: needs.container-build-push.outputs.prowler_version_major == '3'
|
||||
needs: container-build-push
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
@@ -308,4 +282,4 @@ jobs:
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
repository: ${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}
|
||||
event-type: dispatch
|
||||
client-payload: '{"version":"release","tag":"${{ needs.setup.outputs.prowler_version }}"}'
|
||||
client-payload: '{"version":"release","tag":"${{ needs.container-build-push.outputs.prowler_version }}"}'
|
||||
|
||||
@@ -82,110 +82,9 @@ jobs:
|
||||
./tests/**/aws/**
|
||||
./poetry.lock
|
||||
|
||||
- name: Resolve AWS services under test
|
||||
if: steps.changed-aws.outputs.any_changed == 'true'
|
||||
id: aws-services
|
||||
shell: bash
|
||||
run: |
|
||||
python3 <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
dependents = {
|
||||
"acm": ["elb"],
|
||||
"autoscaling": ["dynamodb"],
|
||||
"awslambda": ["ec2", "inspector2"],
|
||||
"backup": ["dynamodb", "ec2", "rds"],
|
||||
"cloudfront": ["shield"],
|
||||
"cloudtrail": ["awslambda", "cloudwatch"],
|
||||
"cloudwatch": ["bedrock"],
|
||||
"ec2": ["dlm", "dms", "elbv2", "emr", "inspector2", "rds", "redshift", "route53", "shield", "ssm"],
|
||||
"ecr": ["inspector2"],
|
||||
"elb": ["shield"],
|
||||
"elbv2": ["shield"],
|
||||
"globalaccelerator": ["shield"],
|
||||
"iam": ["bedrock", "cloudtrail", "cloudwatch", "codebuild"],
|
||||
"kafka": ["firehose"],
|
||||
"kinesis": ["firehose"],
|
||||
"kms": ["kafka"],
|
||||
"organizations": ["iam", "servicecatalog"],
|
||||
"route53": ["shield"],
|
||||
"s3": ["bedrock", "cloudfront", "cloudtrail", "macie"],
|
||||
"ssm": ["ec2"],
|
||||
"vpc": ["awslambda", "ec2", "efs", "elasticache", "neptune", "networkfirewall", "rds", "redshift", "workspaces"],
|
||||
"waf": ["elbv2"],
|
||||
"wafv2": ["cognito", "elbv2"],
|
||||
}
|
||||
|
||||
changed_raw = """${{ steps.changed-aws.outputs.all_changed_files }}"""
|
||||
# all_changed_files is space-separated, not newline-separated
|
||||
# Strip leading "./" if present for consistent path handling
|
||||
changed_files = [Path(f.lstrip("./")) for f in changed_raw.split() if f]
|
||||
|
||||
services = set()
|
||||
run_all = False
|
||||
|
||||
for path in changed_files:
|
||||
path_str = path.as_posix()
|
||||
parts = path.parts
|
||||
if path_str.startswith("prowler/providers/aws/services/"):
|
||||
if len(parts) > 4 and "." not in parts[4]:
|
||||
services.add(parts[4])
|
||||
else:
|
||||
run_all = True
|
||||
elif path_str.startswith("tests/providers/aws/services/"):
|
||||
if len(parts) > 4 and "." not in parts[4]:
|
||||
services.add(parts[4])
|
||||
else:
|
||||
run_all = True
|
||||
elif path_str.startswith("prowler/providers/aws/") or path_str.startswith("tests/providers/aws/"):
|
||||
run_all = True
|
||||
|
||||
# Expand with direct dependent services (one level only)
|
||||
# We only test services that directly depend on the changed services,
|
||||
# not transitive dependencies (services that depend on dependents)
|
||||
original_services = set(services)
|
||||
for svc in original_services:
|
||||
for dep in dependents.get(svc, []):
|
||||
services.add(dep)
|
||||
|
||||
if run_all or not services:
|
||||
run_all = True
|
||||
services = set()
|
||||
|
||||
service_paths = " ".join(sorted(f"tests/providers/aws/services/{svc}" for svc in services))
|
||||
|
||||
output_lines = [
|
||||
f"run_all={'true' if run_all else 'false'}",
|
||||
f"services={' '.join(sorted(services))}",
|
||||
f"service_paths={service_paths}",
|
||||
]
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a") as gh_out:
|
||||
for line in output_lines:
|
||||
gh_out.write(line + "\n")
|
||||
|
||||
print(f"AWS changed files (filtered): {changed_raw or 'none'}")
|
||||
print(f"Run all AWS tests: {run_all}")
|
||||
if services:
|
||||
print(f"AWS service test paths: {service_paths}")
|
||||
else:
|
||||
print("AWS service test paths: none detected")
|
||||
PY
|
||||
|
||||
- name: Run AWS tests
|
||||
if: steps.changed-aws.outputs.any_changed == 'true'
|
||||
run: |
|
||||
echo "AWS run_all=${{ steps.aws-services.outputs.run_all }}"
|
||||
echo "AWS service_paths='${{ steps.aws-services.outputs.service_paths }}'"
|
||||
|
||||
if [ "${{ steps.aws-services.outputs.run_all }}" = "true" ]; then
|
||||
poetry run pytest -n auto --cov=./prowler/providers/aws --cov-report=xml:aws_coverage.xml tests/providers/aws
|
||||
elif [ -z "${{ steps.aws-services.outputs.service_paths }}" ]; then
|
||||
echo "No AWS service paths detected; skipping AWS tests."
|
||||
else
|
||||
poetry run pytest -n auto --cov=./prowler/providers/aws --cov-report=xml:aws_coverage.xml ${{ steps.aws-services.outputs.service_paths }}
|
||||
fi
|
||||
run: poetry run pytest -n auto --cov=./prowler/providers/aws --cov-report=xml:aws_coverage.xml tests/providers/aws
|
||||
|
||||
- name: Upload AWS coverage to Codecov
|
||||
if: steps.changed-aws.outputs.any_changed == 'true'
|
||||
|
||||
@@ -50,34 +50,8 @@ jobs:
|
||||
id: set-short-sha
|
||||
run: echo "short-sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
|
||||
|
||||
notify-release-started:
|
||||
if: github.repository == 'prowler-cloud/prowler' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
message-ts: ${{ steps.slack-notification.outputs.ts }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Notify container push started
|
||||
id: slack-notification
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
COMPONENT: UI
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-started.json"
|
||||
|
||||
container-build-push:
|
||||
needs: [setup, notify-release-started]
|
||||
if: always() && needs.setup.result == 'success' && (needs.notify-release-started.result == 'success' || needs.notify-release-started.result == 'skipped')
|
||||
needs: setup
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -106,6 +80,21 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Notify container push started
|
||||
id: slack-notification-started
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
COMPONENT: UI
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-started.json"
|
||||
|
||||
- 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'
|
||||
@@ -122,6 +111,23 @@ jobs:
|
||||
cache-from: type=gha,scope=${{ matrix.arch }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.arch }}
|
||||
|
||||
- name: Notify container push completed
|
||||
if: (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && always()
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
MESSAGE_TS: ${{ steps.slack-notification-started.outputs.ts }}
|
||||
COMPONENT: UI
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
||||
step-outcome: ${{ steps.container-push.outcome }}
|
||||
update-ts: ${{ steps.slack-notification-started.outputs.ts }}
|
||||
|
||||
# Create and push multi-architecture manifest
|
||||
create-manifest:
|
||||
needs: [setup, container-build-push]
|
||||
@@ -168,40 +174,6 @@ jobs:
|
||||
regctl tag delete "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.short-sha }}-arm64" || true
|
||||
echo "Cleanup completed"
|
||||
|
||||
notify-release-completed:
|
||||
if: always() && needs.notify-release-started.result == 'success' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
needs: [setup, notify-release-started, container-build-push, create-manifest]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Determine overall outcome
|
||||
id: outcome
|
||||
run: |
|
||||
if [[ "${{ needs.container-build-push.result }}" == "success" && "${{ needs.create-manifest.result }}" == "success" ]]; then
|
||||
echo "outcome=success" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "outcome=failure" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Notify container push completed
|
||||
uses: ./.github/actions/slack-notification
|
||||
env:
|
||||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_PLATFORM_DEPLOYMENTS }}
|
||||
MESSAGE_TS: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
COMPONENT: UI
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: "./.github/scripts/slack-messages/container-release-completed.json"
|
||||
step-outcome: ${{ steps.outcome.outputs.outcome }}
|
||||
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
|
||||
trigger-deployment:
|
||||
if: github.event_name == 'push'
|
||||
needs: [setup, container-build-push]
|
||||
|
||||
+113
-210
@@ -1,4 +1,4 @@
|
||||
name: UI - E2E Cloud Tests
|
||||
name: UI - E2E Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -6,185 +6,125 @@ on:
|
||||
- master
|
||||
- "v5.*"
|
||||
paths:
|
||||
- ".github/workflows/ui-e2e-tests.yml"
|
||||
- "ui/**"
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- "v5.*"
|
||||
paths:
|
||||
- ".github/workflows/ui-e2e-cloud-tests.yml"
|
||||
- "ui/**"
|
||||
workflow_run:
|
||||
workflows:
|
||||
- "API - Build, Push and Deploy"
|
||||
- "UI - Build, Push and Deploy"
|
||||
types: [completed]
|
||||
branches: [master, v5.*]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: "Environment to test"
|
||||
required: true
|
||||
default: "dev"
|
||||
type: choice
|
||||
options:
|
||||
- dev
|
||||
- stg
|
||||
- pro
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
actions: read
|
||||
- '.github/workflows/ui-e2e-tests.yml'
|
||||
- 'ui/**'
|
||||
|
||||
jobs:
|
||||
|
||||
e2e-tests:
|
||||
if: github.repository == 'prowler-cloud/prowler-cloud'
|
||||
if: github.repository == 'prowler-cloud/prowler'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NEXTAUTH_URL: "http://localhost:3000"
|
||||
AUTH_SECRET: "fallback-ci-secret-for-testing"
|
||||
AUTH_TRUST_HOST: "true"
|
||||
AUTH_SECRET: 'fallback-ci-secret-for-testing'
|
||||
AUTH_TRUST_HOST: true
|
||||
NEXTAUTH_URL: 'http://localhost:3000'
|
||||
NEXT_PUBLIC_API_BASE_URL: 'http://localhost:8080/api/v1'
|
||||
E2E_ADMIN_USER: ${{ secrets.E2E_ADMIN_USER }}
|
||||
E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD }}
|
||||
E2E_AWS_PROVIDER_ACCOUNT_ID: ${{ secrets.E2E_AWS_PROVIDER_ACCOUNT_ID }}
|
||||
E2E_AWS_PROVIDER_ACCESS_KEY: ${{ secrets.E2E_AWS_PROVIDER_ACCESS_KEY }}
|
||||
E2E_AWS_PROVIDER_SECRET_KEY: ${{ secrets.E2E_AWS_PROVIDER_SECRET_KEY }}
|
||||
E2E_AWS_PROVIDER_ROLE_ARN: ${{ secrets.E2E_AWS_PROVIDER_ROLE_ARN }}
|
||||
E2E_AZURE_SUBSCRIPTION_ID: ${{ secrets.E2E_AZURE_SUBSCRIPTION_ID }}
|
||||
E2E_AZURE_CLIENT_ID: ${{ secrets.E2E_AZURE_CLIENT_ID }}
|
||||
E2E_AZURE_SECRET_ID: ${{ secrets.E2E_AZURE_SECRET_ID }}
|
||||
E2E_AZURE_TENANT_ID: ${{ secrets.E2E_AZURE_TENANT_ID }}
|
||||
E2E_M365_DOMAIN_ID: ${{ secrets.E2E_M365_DOMAIN_ID }}
|
||||
E2E_M365_CLIENT_ID: ${{ secrets.E2E_M365_CLIENT_ID }}
|
||||
E2E_M365_SECRET_ID: ${{ secrets.E2E_M365_SECRET_ID }}
|
||||
E2E_M365_TENANT_ID: ${{ secrets.E2E_M365_TENANT_ID }}
|
||||
E2E_M365_CERTIFICATE_CONTENT: ${{ secrets.E2E_M365_CERTIFICATE_CONTENT }}
|
||||
E2E_KUBERNETES_CONTEXT: 'kind-kind'
|
||||
E2E_KUBERNETES_KUBECONFIG_PATH: /home/runner/.kube/config
|
||||
E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY: ${{ secrets.E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY }}
|
||||
E2E_GCP_PROJECT_ID: ${{ secrets.E2E_GCP_PROJECT_ID }}
|
||||
E2E_GITHUB_APP_ID: ${{ secrets.E2E_GITHUB_APP_ID }}
|
||||
E2E_GITHUB_BASE64_APP_PRIVATE_KEY: ${{ secrets.E2E_GITHUB_BASE64_APP_PRIVATE_KEY }}
|
||||
E2E_GITHUB_USERNAME: ${{ secrets.E2E_GITHUB_USERNAME }}
|
||||
E2E_GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.E2E_GITHUB_PERSONAL_ACCESS_TOKEN }}
|
||||
E2E_GITHUB_ORGANIZATION: ${{ secrets.E2E_GITHUB_ORGANIZATION }}
|
||||
E2E_GITHUB_ORGANIZATION_ACCESS_TOKEN: ${{ secrets.E2E_GITHUB_ORGANIZATION_ACCESS_TOKEN }}
|
||||
E2E_ORGANIZATION_ID: ${{ secrets.E2E_ORGANIZATION_ID }}
|
||||
E2E_OCI_TENANCY_ID: ${{ secrets.E2E_OCI_TENANCY_ID }}
|
||||
E2E_OCI_USER_ID: ${{ secrets.E2E_OCI_USER_ID }}
|
||||
E2E_OCI_FINGERPRINT: ${{ secrets.E2E_OCI_FINGERPRINT }}
|
||||
E2E_OCI_KEY_CONTENT: ${{ secrets.E2E_OCI_KEY_CONTENT }}
|
||||
E2E_OCI_REGION: ${{ secrets.E2E_OCI_REGION }}
|
||||
E2E_NEW_USER_PASSWORD: ${{ secrets.E2E_NEW_USER_PASSWORD }}
|
||||
|
||||
steps:
|
||||
- name: Determine environment
|
||||
id: env
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "pull_request" || "${{ github.event_name }}" == "push" ]]; then
|
||||
echo "environment=dev" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event_name }}" == "workflow_run" && "${{ github.event.workflow_run.conclusion }}" == "success" && "${{ github.event.workflow_run.event }}" == "release" ]]; then
|
||||
echo "environment=stg" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
echo "environment=${{ github.event.inputs.environment }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Unknown trigger, skipping..."
|
||||
exit 1
|
||||
fi
|
||||
- name: Set environment variables
|
||||
id: vars
|
||||
run: |
|
||||
case "${{ steps.env.outputs.environment }}" in
|
||||
"dev")
|
||||
echo "api_url=https://api.dev.prowler.com/api/v1" >> $GITHUB_OUTPUT
|
||||
echo "e2e_user_secret=DEV_E2E_USER" >> $GITHUB_OUTPUT
|
||||
echo "e2e_password_secret=DEV_E2E_PASSWORD" >> $GITHUB_OUTPUT
|
||||
echo "environment_name=DEV" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
"stg")
|
||||
echo "api_url=https://api.stg.prowler.com/api/v1" >> $GITHUB_OUTPUT
|
||||
echo "e2e_user_secret=STG_E2E_USER" >> $GITHUB_OUTPUT
|
||||
echo "e2e_password_secret=STG_E2E_PASSWORD" >> $GITHUB_OUTPUT
|
||||
echo "environment_name=STG" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
"pro")
|
||||
echo "api_url=https://api.prowler.com/api/v1" >> $GITHUB_OUTPUT
|
||||
echo "e2e_user_secret=PRO_E2E_USER" >> $GITHUB_OUTPUT
|
||||
echo "e2e_password_secret=PRO_E2E_PASSWORD" >> $GITHUB_OUTPUT
|
||||
echo "environment_name=PRO" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
esac
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Environment info
|
||||
env:
|
||||
ENV_NAME: ${{ steps.vars.outputs.environment_name }}
|
||||
API_URL: ${{ steps.vars.outputs.api_url }}
|
||||
run: |
|
||||
echo "Environment: $ENV_NAME"
|
||||
echo "API URL: $API_URL"
|
||||
echo "Workflow: ${{ github.workflow }}"
|
||||
echo "Event: ${{ github.event_name }}"
|
||||
echo "Started at: $(date)"
|
||||
- name: Verify both STG deployments completed
|
||||
if: steps.env.outputs.environment == 'stg'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
echo "Verifying that both API and UI deployments completed successfully..."
|
||||
|
||||
# Get the latest runs for both workflows triggered by the same release
|
||||
API_RUN=$(gh run list --workflow="API - Build, Push and Deploy" --event=release --limit=1 --json status,conclusion,createdAt --jq '.[0]')
|
||||
API_STATUS=$(echo "$API_RUN" | jq -r '.status')
|
||||
API_CONCLUSION=$(echo "$API_RUN" | jq -r '.conclusion')
|
||||
|
||||
UI_RUN=$(gh run list --workflow="UI - Build, Push and Deploy" --event=release --limit=1 --json status,conclusion,createdAt --jq '.[0]')
|
||||
UI_STATUS=$(echo "$UI_RUN" | jq -r '.status')
|
||||
UI_CONCLUSION=$(echo "$UI_RUN" | jq -r '.conclusion')
|
||||
|
||||
echo "API workflow - Status: $API_STATUS, Conclusion: $API_CONCLUSION"
|
||||
echo "UI workflow - Status: $UI_STATUS, Conclusion: $UI_CONCLUSION"
|
||||
|
||||
# Verify both workflows completed successfully
|
||||
if [[ "$API_STATUS" != "completed" || "$API_CONCLUSION" != "success" ]]; then
|
||||
echo "API deployment not ready (Status: $API_STATUS, Conclusion: $API_CONCLUSION)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$UI_STATUS" != "completed" || "$UI_CONCLUSION" != "success" ]]; then
|
||||
echo "UI deployment not ready (Status: $UI_STATUS, Conclusion: $UI_CONCLUSION)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Both API and UI deployments completed successfully for STG"
|
||||
- name: Verify both PRO deployments completed
|
||||
if: steps.env.outputs.environment == 'pro'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
echo "Verifying that both API and UI deployments completed successfully..."
|
||||
|
||||
# Get the latest manual runs for both workflows
|
||||
API_RUN=$(gh run list --workflow="API - Build, Push and Deploy" --event=workflow_dispatch --limit=1 --json status,conclusion,createdAt --jq '.[0]')
|
||||
API_STATUS=$(echo "$API_RUN" | jq -r '.status')
|
||||
API_CONCLUSION=$(echo "$API_RUN" | jq -r '.conclusion')
|
||||
|
||||
UI_RUN=$(gh run list --workflow="UI - Build, Push and Deploy" --event=workflow_dispatch --limit=1 --json status,conclusion,createdAt --jq '.[0]')
|
||||
UI_STATUS=$(echo "$UI_RUN" | jq -r '.status')
|
||||
UI_CONCLUSION=$(echo "$UI_RUN" | jq -r '.conclusion')
|
||||
|
||||
echo "API workflow - Status: $API_STATUS, Conclusion: $API_CONCLUSION"
|
||||
echo "UI workflow - Status: $UI_STATUS, Conclusion: $UI_CONCLUSION"
|
||||
|
||||
# Verify both workflows completed successfully
|
||||
if [[ "$API_STATUS" != "completed" || "$API_CONCLUSION" != "success" ]]; then
|
||||
echo "API deployment not ready (Status: $API_STATUS, Conclusion: $API_CONCLUSION)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$UI_STATUS" != "completed" || "$UI_CONCLUSION" != "success" ]]; then
|
||||
echo "UI deployment not ready (Status: $UI_STATUS, Conclusion: $UI_CONCLUSION)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Both API and UI deployments completed successfully for PRO"
|
||||
- name: Setup Tailscale
|
||||
if: steps.env.outputs.environment != 'pro'
|
||||
uses: tailscale/github-action@84a3f23bb4d843bcf4da6cf824ec1be473daf4de # v3.2.3
|
||||
- name: Create k8s Kind Cluster
|
||||
uses: helm/kind-action@v1
|
||||
with:
|
||||
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
||||
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
|
||||
tags: tag:github-actions
|
||||
- name: Verify API is accessible
|
||||
env:
|
||||
API_URL: ${{ steps.vars.outputs.api_url }}
|
||||
ENV_NAME: ${{ steps.vars.outputs.environment_name }}
|
||||
cluster_name: kind
|
||||
- name: Modify kubeconfig
|
||||
run: |
|
||||
echo "Checking $ENV_NAME API at $API_URL/docs..."
|
||||
curl -f --connect-timeout 30 --max-time 60 ${API_URL}/docs
|
||||
echo "$ENV_NAME API is accessible"
|
||||
# Modify the kubeconfig to use the kind cluster server to https://kind-control-plane:6443
|
||||
# from worker service into docker-compose.yml
|
||||
kubectl config set-cluster kind-kind --server=https://kind-control-plane:6443
|
||||
kubectl config view
|
||||
- name: Add network kind to docker compose
|
||||
run: |
|
||||
# Add the network kind to the docker compose to interconnect to kind cluster
|
||||
yq -i '.networks.kind.external = true' docker-compose.yml
|
||||
# Add network kind to worker service and default network too
|
||||
yq -i '.services.worker.networks = ["kind","default"]' docker-compose.yml
|
||||
- name: Fix API data directory permissions
|
||||
run: docker run --rm -v $(pwd)/_data/api:/data alpine chown -R 1000:1000 /data
|
||||
- name: Add AWS credentials for testing AWS SDK Default Adding Provider
|
||||
run: |
|
||||
echo "Adding AWS credentials for testing AWS SDK Default Adding Provider..."
|
||||
echo "AWS_ACCESS_KEY_ID=${{ secrets.E2E_AWS_PROVIDER_ACCESS_KEY }}" >> .env
|
||||
echo "AWS_SECRET_ACCESS_KEY=${{ secrets.E2E_AWS_PROVIDER_SECRET_KEY }}" >> .env
|
||||
- name: Start API services
|
||||
run: |
|
||||
# Override docker-compose image tag to use latest instead of stable
|
||||
# This overrides any PROWLER_API_VERSION set in .env file
|
||||
export PROWLER_API_VERSION=latest
|
||||
echo "Using PROWLER_API_VERSION=${PROWLER_API_VERSION}"
|
||||
docker compose up -d api worker worker-beat
|
||||
- name: Wait for API to be ready
|
||||
run: |
|
||||
echo "Waiting for prowler-api..."
|
||||
timeout=150 # 5 minutes max
|
||||
elapsed=0
|
||||
while [ $elapsed -lt $timeout ]; do
|
||||
if curl -s ${NEXT_PUBLIC_API_BASE_URL}/docs >/dev/null 2>&1; then
|
||||
echo "Prowler API is ready!"
|
||||
exit 0
|
||||
fi
|
||||
echo "Waiting for prowler-api... (${elapsed}s elapsed)"
|
||||
sleep 5
|
||||
elapsed=$((elapsed + 5))
|
||||
done
|
||||
echo "Timeout waiting for prowler-api to start"
|
||||
exit 1
|
||||
- name: Load database fixtures for E2E tests
|
||||
run: |
|
||||
docker compose exec -T api sh -c '
|
||||
echo "Loading all fixtures from api/fixtures/dev/..."
|
||||
for fixture in api/fixtures/dev/*.json; do
|
||||
if [ -f "$fixture" ]; then
|
||||
echo "Loading $fixture"
|
||||
poetry run python manage.py loaddata "$fixture" --database admin
|
||||
fi
|
||||
done
|
||||
echo "All database fixtures loaded successfully!"
|
||||
'
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version: "20.x"
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
node-version: '20.x'
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
version: 10
|
||||
run_install: false
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
@@ -197,10 +137,6 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build UI application
|
||||
working-directory: ./ui
|
||||
env:
|
||||
NEXT_PUBLIC_API_BASE_URL: ${{ steps.vars.outputs.api_url }}
|
||||
NEXT_PUBLIC_IS_CLOUD_ENV: "true"
|
||||
CLOUD_API_BASE_URL: ${{ steps.vars.outputs.api_url }}
|
||||
run: pnpm run build
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
@@ -216,50 +152,17 @@ jobs:
|
||||
run: pnpm run test:e2e:install
|
||||
- name: Run E2E tests
|
||||
working-directory: ./ui
|
||||
env:
|
||||
NEXT_PUBLIC_API_BASE_URL: ${{ steps.vars.outputs.api_url }}
|
||||
NEXT_PUBLIC_IS_CLOUD_ENV: "true"
|
||||
CLOUD_API_BASE_URL: ${{ steps.vars.outputs.api_url }}
|
||||
E2E_USER: ${{ secrets[steps.vars.outputs.e2e_user_secret] }}
|
||||
E2E_PASSWORD: ${{ secrets[steps.vars.outputs.e2e_password_secret] }}
|
||||
E2E_ADMIN_USER: ${{ secrets.E2E_ADMIN_USER }}
|
||||
E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD }}
|
||||
E2E_AWS_PROVIDER_ACCOUNT_ID: ${{ secrets.E2E_AWS_PROVIDER_ACCOUNT_ID }}
|
||||
E2E_AWS_PROVIDER_ACCESS_KEY: ${{ secrets.E2E_AWS_PROVIDER_ACCESS_KEY }}
|
||||
E2E_AWS_PROVIDER_SECRET_KEY: ${{ secrets.E2E_AWS_PROVIDER_SECRET_KEY }}
|
||||
E2E_AWS_PROVIDER_ROLE_ARN: ${{ secrets.E2E_AWS_PROVIDER_ROLE_ARN }}
|
||||
E2E_AZURE_SUBSCRIPTION_ID: ${{ secrets.E2E_AZURE_SUBSCRIPTION_ID }}
|
||||
E2E_AZURE_CLIENT_ID: ${{ secrets.E2E_AZURE_CLIENT_ID }}
|
||||
E2E_AZURE_SECRET_ID: ${{ secrets.E2E_AZURE_SECRET_ID }}
|
||||
E2E_AZURE_TENANT_ID: ${{ secrets.E2E_AZURE_TENANT_ID }}
|
||||
E2E_M365_DOMAIN_ID: ${{ secrets.E2E_M365_DOMAIN_ID }}
|
||||
E2E_M365_CLIENT_ID: ${{ secrets.E2E_M365_CLIENT_ID }}
|
||||
E2E_M365_SECRET_ID: ${{ secrets.E2E_M365_SECRET_ID }}
|
||||
E2E_M365_TENANT_ID: ${{ secrets.E2E_M365_TENANT_ID }}
|
||||
E2E_M365_CERTIFICATE_CONTENT: ${{ secrets.E2E_M365_CERTIFICATE_CONTENT }}
|
||||
E2E_KUBERNETES_CONTEXT: "kind-kind"
|
||||
E2E_KUBERNETES_KUBECONFIG_PATH: /home/runner/.kube/config
|
||||
E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY: ${{ secrets.E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY }}
|
||||
E2E_GCP_PROJECT_ID: ${{ secrets.E2E_GCP_PROJECT_ID }}
|
||||
E2E_GITHUB_APP_ID: ${{ secrets.E2E_GITHUB_APP_ID }}
|
||||
E2E_GITHUB_BASE64_APP_PRIVATE_KEY: ${{ secrets.E2E_GITHUB_BASE64_APP_PRIVATE_KEY }}
|
||||
E2E_GITHUB_USERNAME: ${{ secrets.E2E_GITHUB_USERNAME }}
|
||||
E2E_GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.E2E_GITHUB_PERSONAL_ACCESS_TOKEN }}
|
||||
E2E_GITHUB_ORGANIZATION: ${{ secrets.E2E_GITHUB_ORGANIZATION }}
|
||||
E2E_GITHUB_ORGANIZATION_ACCESS_TOKEN: ${{ secrets.E2E_GITHUB_ORGANIZATION_ACCESS_TOKEN }}
|
||||
E2E_ORGANIZATION_ID: ${{ secrets.E2E_ORGANIZATION_ID }}
|
||||
E2E_OCI_TENANCY_ID: ${{ secrets.E2E_OCI_TENANCY_ID }}
|
||||
E2E_OCI_USER_ID: ${{ secrets.E2E_OCI_USER_ID }}
|
||||
E2E_OCI_FINGERPRINT: ${{ secrets.E2E_OCI_FINGERPRINT }}
|
||||
E2E_OCI_KEY_CONTENT: ${{ secrets.E2E_OCI_KEY_CONTENT }}
|
||||
E2E_OCI_REGION: ${{ secrets.E2E_OCI_REGION }}
|
||||
E2E_NEW_USER_PASSWORD: ${{ secrets.E2E_NEW_USER_PASSWORD }}
|
||||
|
||||
run: pnpm run test:e2e-cloud
|
||||
run: pnpm run test:e2e
|
||||
- name: Upload test reports
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: always()
|
||||
if: failure()
|
||||
with:
|
||||
name: playwright-report-${{ steps.env.outputs.environment }}-${{ github.run_number }}
|
||||
name: playwright-report
|
||||
path: ui/playwright-report/
|
||||
retention-days: 30
|
||||
- name: Cleanup services
|
||||
if: always()
|
||||
run: |
|
||||
echo "Shutting down services..."
|
||||
docker compose down -v || true
|
||||
echo "Cleanup completed"
|
||||
|
||||
Generated
-5
@@ -6065,7 +6065,6 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"},
|
||||
@@ -6074,7 +6073,6 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"},
|
||||
@@ -6083,7 +6081,6 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"},
|
||||
@@ -6092,7 +6089,6 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"},
|
||||
@@ -6101,7 +6097,6 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"},
|
||||
{file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"},
|
||||
|
||||
@@ -58,7 +58,7 @@ Before you begin, ensure you have:
|
||||
|
||||
### Authentication
|
||||
|
||||
Prowler supports multiple authentication methods for OCI. For detailed authentication setup, see the [OCI Authentication Guide](./authentication).
|
||||
Prowler supports multiple authentication methods for OCI. For detailed authentication setup, see the [OCI Authentication Guide](./authentication.mdx).
|
||||
|
||||
**Note:** OCI Session Authentication and Config File Authentication both use the same `~/.oci/config` file. The difference is how the config file is generated - automatically via browser (session auth) or manually with API keys.
|
||||
|
||||
@@ -107,7 +107,7 @@ The easiest and most secure method is using OCI session authentication, which au
|
||||
|
||||
#### Alternative: Manual API Key Setup
|
||||
|
||||
If you prefer to manually generate API keys instead of using browser-based session authentication, see the detailed instructions in the [Authentication Guide](./authentication#config-file-authentication-manual-api-key-setup).
|
||||
If you prefer to manually generate API keys instead of using browser-based session authentication, see the detailed instructions in the [Authentication Guide](./authentication.mdx#config-file-authentication-manual-api-key-setup).
|
||||
|
||||
**Note:** Both methods use the same `~/.oci/config` file - the difference is that manual setup uses static API keys while session authentication uses temporary session tokens.
|
||||
|
||||
|
||||
@@ -19,17 +19,12 @@ class CheckRemediation(MinimalSerializerMixin, BaseModel):
|
||||
default=None,
|
||||
description="Terraform code snippet with best practices for remediation",
|
||||
)
|
||||
nativeiac: str | None = Field(
|
||||
default=None,
|
||||
description="Native Infrastructure as Code code snippet with best practices for remediation",
|
||||
recommendation_text: str | None = Field(
|
||||
default=None, description="Text description with best practices"
|
||||
)
|
||||
other: str | None = Field(
|
||||
recommendation_url: str | None = Field(
|
||||
default=None,
|
||||
description="Other remediation code snippet with best practices for remediation, usually used for web interfaces or other tools",
|
||||
)
|
||||
recommendation: str | None = Field(
|
||||
default=None,
|
||||
description="Text description with general best recommended practices to avoid the issue",
|
||||
description="URL to external remediation documentation",
|
||||
)
|
||||
|
||||
|
||||
@@ -38,6 +33,9 @@ class CheckMetadata(MinimalSerializerMixin, BaseModel):
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
check_id: str = Field(
|
||||
description="Unique provider identifier for the security check (e.g., 's3_bucket_public_access')",
|
||||
)
|
||||
title: str = Field(
|
||||
description="Human-readable title of the security check",
|
||||
)
|
||||
@@ -61,9 +59,9 @@ class CheckMetadata(MinimalSerializerMixin, BaseModel):
|
||||
default=None,
|
||||
description="Remediation guidance including CLI commands and recommendations",
|
||||
)
|
||||
additional_urls: list[str] = Field(
|
||||
default_factory=list,
|
||||
description="List of additional URLs related to the check",
|
||||
related_url: str | None = Field(
|
||||
default=None,
|
||||
description="URL to additional documentation or references",
|
||||
)
|
||||
categories: list[str] = Field(
|
||||
default_factory=list,
|
||||
@@ -81,23 +79,23 @@ class CheckMetadata(MinimalSerializerMixin, BaseModel):
|
||||
recommendation = remediation_data.get("recommendation", {})
|
||||
|
||||
remediation = CheckRemediation(
|
||||
cli=code["cli"],
|
||||
terraform=code["terraform"],
|
||||
nativeiac=code["nativeiac"],
|
||||
other=code["other"],
|
||||
recommendation=recommendation["text"],
|
||||
cli=code.get("cli"),
|
||||
terraform=code.get("terraform"),
|
||||
recommendation_text=recommendation.get("text"),
|
||||
recommendation_url=recommendation.get("url"),
|
||||
)
|
||||
|
||||
return cls(
|
||||
check_id=data["checkid"],
|
||||
title=data["checktitle"],
|
||||
description=data["description"],
|
||||
provider=data["provider"],
|
||||
risk=data["risk"],
|
||||
risk=data.get("risk"),
|
||||
service=data["servicename"],
|
||||
resource_type=data["resourcetype"],
|
||||
remediation=remediation,
|
||||
additional_urls=data["additionalurls"],
|
||||
categories=data["categories"],
|
||||
related_url=data.get("relatedurl"),
|
||||
categories=data.get("categories", []),
|
||||
)
|
||||
|
||||
|
||||
@@ -118,36 +116,35 @@ class SimplifiedFinding(MinimalSerializerMixin, BaseModel):
|
||||
severity: Literal["critical", "high", "medium", "low", "informational"] = Field(
|
||||
description="Severity level of the finding",
|
||||
)
|
||||
check_id: str = Field(
|
||||
description="ID of the security check that generated this finding",
|
||||
check_metadata: CheckMetadata = Field(
|
||||
description="Metadata about the security check that generated this finding",
|
||||
)
|
||||
status_extended: str = Field(
|
||||
description="Extended status information providing additional context",
|
||||
)
|
||||
delta: Literal["new", "changed"] | None = Field(
|
||||
default=None,
|
||||
delta: Literal["new", "changed"] = Field(
|
||||
description="Change status: 'new' (not seen before), 'changed' (modified since last scan), or None (unchanged)",
|
||||
)
|
||||
muted: bool | None = Field(
|
||||
default=None,
|
||||
muted: bool = Field(
|
||||
description="Whether this finding has been muted/suppressed by the user",
|
||||
)
|
||||
muted_reason: str | None = Field(
|
||||
muted_reason: str = Field(
|
||||
default=None,
|
||||
description="Reason provided when muting this finding",
|
||||
description="Reason provided when muting this finding (3-500 chars if muted)",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_api_response(cls, data: dict) -> "SimplifiedFinding":
|
||||
"""Transform JSON:API finding response to simplified format."""
|
||||
attributes = data["attributes"]
|
||||
check_metadata = attributes["check_metadata"]
|
||||
|
||||
return cls(
|
||||
id=data["id"],
|
||||
uid=attributes["uid"],
|
||||
status=attributes["status"],
|
||||
severity=attributes["severity"],
|
||||
check_id=attributes["check_metadata"]["checkid"],
|
||||
check_metadata=CheckMetadata.from_api_response(check_metadata),
|
||||
status_extended=attributes["status_extended"],
|
||||
delta=attributes["delta"],
|
||||
muted=attributes["muted"],
|
||||
@@ -182,9 +179,6 @@ class DetailedFinding(SimplifiedFinding):
|
||||
default_factory=list,
|
||||
description="List of UUIDs for cloud resources associated with this finding",
|
||||
)
|
||||
check_metadata: CheckMetadata = Field(
|
||||
description="Metadata about the security check that generated this finding",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_api_response(cls, data: dict) -> "DetailedFinding":
|
||||
@@ -210,7 +204,6 @@ class DetailedFinding(SimplifiedFinding):
|
||||
uid=attributes["uid"],
|
||||
status=attributes["status"],
|
||||
severity=attributes["severity"],
|
||||
check_id=check_metadata["checkid"],
|
||||
check_metadata=CheckMetadata.from_api_response(check_metadata),
|
||||
status_extended=attributes.get("status_extended"),
|
||||
delta=attributes.get("delta"),
|
||||
|
||||
@@ -19,9 +19,9 @@ class FindingsTools(BaseTool):
|
||||
"""Tools for security findings operations.
|
||||
|
||||
Provides tools for:
|
||||
- search_security_findings: Fast and lightweight searching across findings
|
||||
- get_finding_details: Get complete details for a specific finding
|
||||
- get_findings_overview: Get aggregate statistics and trends across all findings
|
||||
- Searching and filtering security findings
|
||||
- Getting detailed finding information
|
||||
- Viewing findings overview/statistics
|
||||
"""
|
||||
|
||||
async def search_security_findings(
|
||||
@@ -90,27 +90,27 @@ class FindingsTools(BaseTool):
|
||||
) -> dict[str, Any]:
|
||||
"""Search and filter security findings across all cloud providers with rich filtering capabilities.
|
||||
|
||||
IMPORTANT: This tool returns LIGHTWEIGHT findings. Use this for fast searching and filtering across many findings.
|
||||
For complete details use prowler_app_get_finding_details on specific findings.
|
||||
This is the primary tool for browsing and filtering security findings. Returns lightweight findings
|
||||
optimized for searching across large result sets. For detailed information about a specific finding,
|
||||
use get_finding_details.
|
||||
|
||||
Default behavior:
|
||||
- Returns latest findings from most recent scans (no date parameters needed)
|
||||
- Filters to FAIL status only (security issues found)
|
||||
- Returns 50 results per page
|
||||
- Returns 100 results per page
|
||||
|
||||
Date filtering:
|
||||
- Without dates: queries findings from the most recent completed scan across all providers (most efficient)
|
||||
- With dates: queries historical findings (2-day maximum range between date_from and date_to)
|
||||
- Without dates: queries findings from the most recent completed scan across all providers (most efficient). This returns the latest snapshot of findings, not a time-based query.
|
||||
- With dates: queries historical findings (2-day maximum range)
|
||||
|
||||
Each finding includes:
|
||||
- Core identification: id (UUID for get_finding_details), uid, check_id
|
||||
- Security context: status (FAIL/PASS/MANUAL), severity (critical/high/medium/low/informational)
|
||||
- State tracking: delta (new/changed/unchanged), muted (boolean), muted_reason
|
||||
- Extended details: status_extended with additional context
|
||||
- Core identification: id, uid, check_id
|
||||
- Security context: status, severity, check_metadata (title, description, remediation)
|
||||
- State tracking: delta (new/changed), muted status
|
||||
- Extended details: status_extended for additional context
|
||||
|
||||
Workflow:
|
||||
1. Use this tool to search and filter findings by severity, status, provider, service, region, etc.
|
||||
2. Use prowler_app_get_finding_details with the finding 'id' to get complete information about the finding
|
||||
Returns:
|
||||
Paginated list of simplified findings with total count and pagination metadata
|
||||
"""
|
||||
# Validate page_size parameter
|
||||
self.api_client.validate_page_size(page_size)
|
||||
@@ -185,39 +185,21 @@ class FindingsTools(BaseTool):
|
||||
) -> dict[str, Any]:
|
||||
"""Retrieve comprehensive details about a specific security finding by its ID.
|
||||
|
||||
IMPORTANT: This tool returns COMPLETE finding details.
|
||||
Use this after finding a specific finding via prowler_app_search_security_findings
|
||||
This tool provides MORE detailed information than search_security_findings. Use this when you need
|
||||
to deeply analyze a specific finding or understand its complete context and history.
|
||||
|
||||
This tool provides ALL information that prowler_app_search_security_findings returns PLUS:
|
||||
|
||||
1. Check Metadata (information about the check script that generated the finding):
|
||||
- title: Human-readable phrase used to summarize the check
|
||||
- description: Detailed explanation of what the check validates and why it is important
|
||||
- risk: What could happen if this check fails
|
||||
- remediation: Complete remediation guidance including step-by-step instructions and code snippets with best practices to fix the issue:
|
||||
* cli: Command-line commands to fix the issue
|
||||
* terraform: Terraform code snippets with best practices
|
||||
* nativeiac: Provider native Infrastructure as Code code snippets with best practices to fix the issue
|
||||
* other: Other remediation code snippets with best practices, usually used for web interfaces or other tools
|
||||
* recommendation: Text description with general best recommended practices to avoid the issue
|
||||
- provider: Cloud provider (aws/azure/gcp/etc)
|
||||
- service: Service name (s3/ec2/keyvault/etc)
|
||||
- resource_type: Resource type being evaluated
|
||||
- categories: Security categories this check belongs to
|
||||
- additional_urls: List of additional URLs related to the check
|
||||
|
||||
2. Temporal Metadata:
|
||||
- inserted_at: When this finding was first inserted into database
|
||||
- updated_at: When this finding was last updated
|
||||
- first_seen_at: When this finding was first detected across all scans
|
||||
|
||||
3. Relationships:
|
||||
- scan_id: UUID of the scan that generated this finding
|
||||
- resource_ids: List of UUIDs for cloud resources associated with this finding
|
||||
Additional information compared to search_security_findings:
|
||||
- Temporal metadata: when the finding was first seen, inserted, and last updated
|
||||
- Scan relationship: ID of the scan that generated this finding
|
||||
- Resource relationships: IDs of all cloud resources associated with this finding
|
||||
|
||||
Workflow:
|
||||
1. Use prowler_app_search_security_findings to browse and filter findings
|
||||
2. Use this tool with the finding 'id' to get remediation guidance and complete context
|
||||
1. Use search_security_findings to browse and filter across many findings
|
||||
2. Use get_finding_details to drill down into specific findings of interest
|
||||
|
||||
Returns:
|
||||
dict containing detailed finding with comprehensive security metadata, temporal information,
|
||||
and relationships to scans and resources
|
||||
"""
|
||||
params = {
|
||||
# Return comprehensive fields including temporal metadata
|
||||
@@ -243,31 +225,26 @@ class FindingsTools(BaseTool):
|
||||
description="Filter statistics by cloud provider. Multiple values allowed. If empty, all providers are returned. For valid values, please refer to Prowler Hub/Prowler Documentation that you can also find in form of tools in this MCP Server.",
|
||||
),
|
||||
) -> dict[str, Any]:
|
||||
"""Get aggregate statistics and trends about security findings as a markdown report.
|
||||
"""Get high-level statistics about security findings formatted as a human-readable markdown report.
|
||||
|
||||
This tool provides a HIGH-LEVEL OVERVIEW without retrieving individual findings. Use this when you
|
||||
need to understand the overall security posture, trends, or remediation progress across all findings.
|
||||
Use this tool to get a quick overview of your security posture without retrieving individual findings.
|
||||
Perfect for understanding trends, identifying areas of concern, and tracking improvements over time.
|
||||
|
||||
The markdown report includes:
|
||||
The report includes:
|
||||
- Summary statistics: total findings, fail/pass/muted counts with percentages
|
||||
- Delta analysis: breakdown of new vs changed findings
|
||||
- Trending information: how findings are evolving over time
|
||||
|
||||
1. Summary Statistics:
|
||||
- Total number of findings
|
||||
- Failed checks (security issues) with percentage
|
||||
- Passed checks (no issues) with percentage
|
||||
- Muted findings (user-suppressed) with percentage
|
||||
Output format: Markdown-formatted report ready to present to users or include in documentation.
|
||||
|
||||
2. Delta Analysis (Change Tracking):
|
||||
- New findings: never seen before in previous scans
|
||||
* Broken down by: new failures, new passes, new muted
|
||||
- Changed findings: status changed since last scan
|
||||
* Broken down by: changed to fail, changed to pass, changed to muted
|
||||
- Unchanged findings: same status as previous scan
|
||||
Use cases:
|
||||
- Quick security posture assessment
|
||||
- Tracking remediation progress over time
|
||||
- Identifying which providers have most issues
|
||||
- Understanding finding trends (improving or degrading)
|
||||
|
||||
This helps answer questions like:
|
||||
- "What's my overall security posture?"
|
||||
- "How many critical security issues do I have?"
|
||||
- "Are we improving or getting worse over time?"
|
||||
- "How many new security issues appeared since last scan?"
|
||||
Returns:
|
||||
Dictionary with 'report' key containing markdown-formatted summary statistics
|
||||
"""
|
||||
params = {
|
||||
# Return only LLM-relevant aggregate statistics
|
||||
|
||||
Generated
+1
-8
@@ -2923,8 +2923,6 @@ python-versions = "*"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"},
|
||||
{file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"},
|
||||
{file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -5515,7 +5513,6 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"},
|
||||
@@ -5524,7 +5521,6 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"},
|
||||
@@ -5533,7 +5529,6 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"},
|
||||
@@ -5542,7 +5537,6 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"},
|
||||
@@ -5551,7 +5545,6 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"},
|
||||
{file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"},
|
||||
@@ -6460,4 +6453,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">3.9.1,<3.13"
|
||||
content-hash = "1559a8799915bf0372eef07396e1dc40802911ef07ae92997cd260d9fe596ba3"
|
||||
content-hash = "433468987cb3c4499d094d90e9f8cc9062a25ce115fde991a4e1b39edbfb7815"
|
||||
|
||||
+1
-15
@@ -11,11 +11,6 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `compute_instance_preemptible_vm_disabled` check for GCP provider [(#9342)](https://github.com/prowler-cloud/prowler/pull/9342)
|
||||
- `compute_instance_automatic_restart_enabled` check for GCP provider [(#9271)](https://github.com/prowler-cloud/prowler/pull/9271)
|
||||
- `compute_instance_deletion_protection_enabled` check for GCP provider [(#9358)](https://github.com/prowler-cloud/prowler/pull/9358)
|
||||
- Update SOC2 - Azure with Processing Integrity requirements [(#9463)](https://github.com/prowler-cloud/prowler/pull/9463)
|
||||
- Update SOC2 - GCP with Processing Integrity requirements [(#9464)](https://github.com/prowler-cloud/prowler/pull/9464)
|
||||
- Update SOC2 - AWS with Processing Integrity requirements [(#9462)](https://github.com/prowler-cloud/prowler/pull/9462)
|
||||
- RBI Cyber Security Framework compliance for Azure provider [(#8822)](https://github.com/prowler-cloud/prowler/pull/8822)
|
||||
|
||||
|
||||
### Changed
|
||||
- Update AWS Macie service metadata to new format [(#9265)](https://github.com/prowler-cloud/prowler/pull/9265)
|
||||
@@ -28,19 +23,10 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
|
||||
---
|
||||
|
||||
## [v5.14.3] (Prowler UNRELEASED)
|
||||
|
||||
### Fixed
|
||||
- Fix duplicate requirement IDs in ISO 27001:2013 AWS compliance framework by adding unique letter suffixes
|
||||
- Removed incorrect threat-detection category from checks metadata [(#9489)](https://github.com/prowler-cloud/prowler/pull/9489)
|
||||
|
||||
---
|
||||
|
||||
## [v5.14.2] (Prowler 5.14.2)
|
||||
## [v5.14.2] (Prowler UNRELEASED)
|
||||
|
||||
### Fixed
|
||||
- Custom check folder metadata validation [(#9335)](https://github.com/prowler-cloud/prowler/pull/9335)
|
||||
- Pin `alibabacloud-gateway-oss-util` to version 0.0.3 to address missing dependency [(#9487)](https://github.com/prowler-cloud/prowler/pull/9487)
|
||||
|
||||
---
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -547,106 +547,6 @@
|
||||
"cloudwatch_log_group_retention_policy_specific_days_enabled",
|
||||
"kinesis_stream_data_retention_period"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_2",
|
||||
"Name": "PI1.2 System inputs are measured and recorded completely, accurately, and timely to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements policies and procedures over system inputs, including controls over completeness and accuracy, to result in products, services, and reporting to meet the entity's objectives. This includes defining accuracy targets, monitoring input quality, and creating detailed records of each input event.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_2",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "aws",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"apigateway_restapi_logging_enabled",
|
||||
"apigatewayv2_api_access_logging_enabled",
|
||||
"elbv2_logging_enabled",
|
||||
"elb_logging_enabled",
|
||||
"wafv2_webacl_logging_enabled",
|
||||
"waf_global_webacl_logging_enabled",
|
||||
"cloudtrail_s3_dataevents_write_enabled",
|
||||
"cloudfront_distributions_logging_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_3",
|
||||
"Name": "PI1.3 Data is processed completely, accurately, and timely as authorized to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements controls to ensure data is processed completely, accurately, and timely. This includes defining processing specifications, identifying processing activities, detecting and correcting errors throughout processing, recording processing activities with accurate logs, and ensuring completeness and timeliness of processing.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_3",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "aws",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"cloudtrail_multi_region_enabled",
|
||||
"cloudtrail_log_file_validation_enabled",
|
||||
"cloudtrail_cloudwatch_logging_enabled",
|
||||
"cloudwatch_log_metric_filter_unauthorized_api_calls",
|
||||
"cloudwatch_log_metric_filter_authentication_failures",
|
||||
"cloudwatch_log_metric_filter_policy_changes",
|
||||
"cloudwatch_log_metric_filter_root_usage",
|
||||
"config_recorder_all_regions_enabled",
|
||||
"rds_instance_integration_cloudwatch_logs",
|
||||
"rds_cluster_integration_cloudwatch_logs",
|
||||
"glue_etl_jobs_logging_enabled",
|
||||
"stepfunctions_statemachine_logging_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_4",
|
||||
"Name": "PI1.4 System outputs are complete, accurate, distributed only to intended parties, and retained to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements controls to ensure system outputs are delivered to authorized recipients in the correct format and protected against unauthorized access, modification, theft, destruction, or corruption. This includes output encryption, access controls, and audit trails for output delivery.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_4",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "aws",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"s3_bucket_default_encryption",
|
||||
"s3_bucket_kms_encryption",
|
||||
"cloudwatch_log_group_kms_encryption_enabled",
|
||||
"sns_topics_kms_encryption_at_rest_enabled",
|
||||
"kinesis_stream_encrypted_at_rest",
|
||||
"cloudfront_distributions_field_level_encryption_enabled",
|
||||
"cloudwatch_log_group_not_publicly_accessible",
|
||||
"cloudwatch_cross_account_sharing_disabled",
|
||||
"glue_etl_jobs_cloudwatch_logs_encryption_enabled",
|
||||
"glue_etl_jobs_amazon_s3_encryption_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_5",
|
||||
"Name": "PI1.5 Stored data is maintained complete, accurate, and protected from unauthorized modification to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements controls to protect stored inputs, items in processing, and outputs from theft, destruction, corruption, or deterioration. This includes data encryption at rest, key management, backup and recovery procedures, access controls, and data integrity validation.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_5",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "aws",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"s3_bucket_object_versioning",
|
||||
"s3_bucket_object_lock",
|
||||
"rds_instance_storage_encrypted",
|
||||
"rds_cluster_storage_encrypted",
|
||||
"dynamodb_tables_kms_cmk_encryption_enabled",
|
||||
"ec2_ebs_volume_encryption",
|
||||
"backup_plans_exist",
|
||||
"backup_recovery_point_encrypted",
|
||||
"backup_vaults_encrypted",
|
||||
"kms_cmk_rotation_enabled"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
{
|
||||
"Framework": "RBI-Cyber-Security-Framework",
|
||||
"Name": "Reserve Bank of India (RBI) Cyber Security Framework",
|
||||
"Version": "",
|
||||
"Provider": "Azure",
|
||||
"Description": "The Reserve Bank had prescribed a set of baseline cyber security controls for primary (Urban) cooperative banks (UCBs) in October 2018. On further examination, it has been decided to prescribe a comprehensive cyber security framework for the UCBs, as a graded approach, based on their digital depth and interconnectedness with the payment systems landscape, digital products offered by them and assessment of cyber security risk. The framework would mandate implementation of progressively stronger security measures based on the nature, variety and scale of digital product offerings of banks.",
|
||||
"Requirements": [
|
||||
{
|
||||
"Id": "annex_i_1_1",
|
||||
"Name": "Annex I (1.1)",
|
||||
"Description": "UCBs should maintain an up-to-date business IT Asset Inventory Register containing the following fields, as a minimum: a) Details of the IT Asset (viz., hardware/software/network devices, key personnel, services, etc.), b. Details of systems where customer data are stored, c. Associated business applications, if any, d. Criticality of the IT asset (For example, High/Medium/Low).",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "annex_i_1_1",
|
||||
"Service": "vm"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"vm_ensure_using_approved_images",
|
||||
"vm_ensure_using_managed_disks",
|
||||
"vm_trusted_launch_enabled",
|
||||
"aks_cluster_rbac_enabled",
|
||||
"aks_clusters_created_with_private_nodes",
|
||||
"appinsights_ensure_is_configured",
|
||||
"containerregistry_admin_user_disabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "annex_i_1_3",
|
||||
"Name": "Annex I (1.3)",
|
||||
"Description": "Appropriately manage and provide protection within and outside UCB/network, keeping in mind how the data/information is stored, transmitted, processed, accessed and put to use within/outside the UCB's network, and level of risk they are exposed to depending on the sensitivity of the data/information.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "annex_i_1_3",
|
||||
"Service": "azure"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"keyvault_key_rotation_enabled",
|
||||
"keyvault_access_only_through_private_endpoints",
|
||||
"keyvault_private_endpoints",
|
||||
"keyvault_rbac_enabled",
|
||||
"app_function_not_publicly_accessible",
|
||||
"app_ensure_http_is_redirected_to_https",
|
||||
"app_minimum_tls_version_12",
|
||||
"storage_blob_public_access_level_is_disabled",
|
||||
"storage_secure_transfer_required_is_enabled",
|
||||
"storage_ensure_encryption_with_customer_managed_keys",
|
||||
"storage_ensure_minimum_tls_version_12",
|
||||
"storage_default_network_access_rule_is_denied",
|
||||
"storage_ensure_private_endpoints_in_storage_accounts",
|
||||
"network_ssh_internet_access_restricted",
|
||||
"sqlserver_unrestricted_inbound_access",
|
||||
"sqlserver_tde_encryption_enabled",
|
||||
"sqlserver_tde_encrypted_with_cmk",
|
||||
"cosmosdb_account_use_private_endpoints",
|
||||
"cosmosdb_account_firewall_use_selected_networks",
|
||||
"mysql_flexible_server_ssl_connection_enabled",
|
||||
"mysql_flexible_server_minimum_tls_version_12",
|
||||
"postgresql_flexible_server_enforce_ssl_enabled",
|
||||
"aks_clusters_public_access_disabled",
|
||||
"containerregistry_not_publicly_accessible",
|
||||
"containerregistry_uses_private_link",
|
||||
"aisearch_service_not_publicly_accessible"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "annex_i_5_1",
|
||||
"Name": "Annex I (5.1)",
|
||||
"Description": "The firewall configurations should be set to the highest security level and evaluation of critical device (such as firewall, network switches, security devices, etc.) configurations should be done periodically.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "annex_i_5_1",
|
||||
"Service": "network"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"network_rdp_internet_access_restricted",
|
||||
"network_http_internet_access_restricted",
|
||||
"network_udp_internet_access_restricted",
|
||||
"network_ssh_internet_access_restricted",
|
||||
"network_flow_log_captured_sent",
|
||||
"network_flow_log_more_than_90_days",
|
||||
"network_watcher_enabled",
|
||||
"network_bastion_host_exists",
|
||||
"aks_network_policy_enabled",
|
||||
"storage_default_network_access_rule_is_denied"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "annex_i_6",
|
||||
"Name": "Annex I (6)",
|
||||
"Description": "Put in place systems and processes to identify, track, manage and monitor the status of patches to servers, operating system and application software running at the systems used by the UCB officials (end-users). Implement and update antivirus protection for all servers and applicable end points preferably through a centralised system.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "annex_i_6",
|
||||
"Service": "defender"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"defender_ensure_system_updates_are_applied",
|
||||
"defender_assessments_vm_endpoint_protection_installed",
|
||||
"defender_ensure_defender_for_server_is_on",
|
||||
"defender_ensure_defender_for_app_services_is_on",
|
||||
"defender_ensure_defender_for_sql_servers_is_on",
|
||||
"defender_ensure_defender_for_azure_sql_databases_is_on",
|
||||
"defender_ensure_defender_for_storage_is_on",
|
||||
"defender_ensure_defender_for_containers_is_on",
|
||||
"defender_ensure_defender_for_keyvault_is_on",
|
||||
"defender_ensure_defender_for_arm_is_on",
|
||||
"defender_ensure_defender_for_dns_is_on",
|
||||
"defender_ensure_defender_for_databases_is_on",
|
||||
"defender_ensure_defender_for_cosmosdb_is_on",
|
||||
"defender_container_images_scan_enabled",
|
||||
"defender_container_images_resolved_vulnerabilities",
|
||||
"defender_auto_provisioning_vulnerabilty_assessments_machines_on",
|
||||
"vm_backup_enabled",
|
||||
"app_ensure_java_version_is_latest",
|
||||
"app_ensure_php_version_is_latest",
|
||||
"app_ensure_python_version_is_latest"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "annex_i_7_1",
|
||||
"Name": "Annex I (7.1)",
|
||||
"Description": "Disallow administrative rights on end-user workstations/PCs/laptops and provide access rights on a 'need to know' and 'need to do' basis.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "annex_i_7_1",
|
||||
"Service": "iam"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"iam_role_user_access_admin_restricted",
|
||||
"iam_subscription_roles_owner_custom_not_created",
|
||||
"iam_custom_role_has_permissions_to_administer_resource_locks",
|
||||
"entra_global_admin_in_less_than_five_users",
|
||||
"entra_policy_ensure_default_user_cannot_create_apps",
|
||||
"entra_policy_ensure_default_user_cannot_create_tenants",
|
||||
"entra_policy_default_users_cannot_create_security_groups",
|
||||
"entra_policy_guest_invite_only_for_admin_roles",
|
||||
"entra_policy_guest_users_access_restrictions",
|
||||
"app_function_identity_without_admin_privileges"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "annex_i_7_2",
|
||||
"Name": "Annex I (7.2)",
|
||||
"Description": "Passwords should be set as complex and lengthy and users should not use same passwords for all the applications/systems/devices.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "annex_i_7_2",
|
||||
"Service": "entra"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"entra_non_privileged_user_has_mfa",
|
||||
"entra_privileged_user_has_mfa",
|
||||
"entra_policy_user_consent_for_verified_apps",
|
||||
"entra_policy_restricts_user_consent_for_apps",
|
||||
"entra_user_with_vm_access_has_mfa",
|
||||
"entra_security_defaults_enabled",
|
||||
"entra_conditional_access_policy_require_mfa_for_management_api",
|
||||
"entra_trusted_named_locations_exists",
|
||||
"sqlserver_azuread_administrator_enabled",
|
||||
"postgresql_flexible_server_entra_id_authentication_enabled",
|
||||
"cosmosdb_account_use_aad_and_rbac"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "annex_i_7_3",
|
||||
"Name": "Annex I (7.3)",
|
||||
"Description": "Remote Desktop Protocol (RDP) which allows others to access the computer remotely over a network or over the internet should be always disabled and should be enabled only with the approval of the authorised officer of the UCB. Logs for such remote access shall be enabled and monitored for suspicious activities.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "annex_i_7_3",
|
||||
"Service": "network"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"network_rdp_internet_access_restricted",
|
||||
"vm_jit_access_enabled",
|
||||
"network_bastion_host_exists",
|
||||
"vm_linux_enforce_ssh_authentication"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "annex_i_7_4",
|
||||
"Name": "Annex I (7.4)",
|
||||
"Description": "Implement appropriate (e.g. centralised) systems and controls to allow, manage, log and monitor privileged/super user/administrative access to critical systems (servers/databases, applications, network devices etc.)",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "annex_i_7_4",
|
||||
"Service": "monitor"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"monitor_alert_create_update_nsg",
|
||||
"monitor_alert_delete_nsg",
|
||||
"monitor_diagnostic_setting_with_appropriate_categories",
|
||||
"monitor_diagnostic_settings_exists",
|
||||
"monitor_alert_create_policy_assignment",
|
||||
"monitor_alert_delete_policy_assignment",
|
||||
"monitor_alert_create_update_security_solution",
|
||||
"monitor_alert_delete_security_solution",
|
||||
"monitor_alert_create_update_sqlserver_fr",
|
||||
"monitor_alert_delete_sqlserver_fr",
|
||||
"monitor_alert_create_update_public_ip_address_rule",
|
||||
"monitor_alert_delete_public_ip_address_rule",
|
||||
"monitor_alert_service_health_exists",
|
||||
"monitor_storage_account_with_activity_logs_cmk_encrypted",
|
||||
"monitor_storage_account_with_activity_logs_is_private",
|
||||
"keyvault_logging_enabled",
|
||||
"sqlserver_auditing_enabled",
|
||||
"sqlserver_auditing_retention_90_days",
|
||||
"app_http_logs_enabled",
|
||||
"app_function_application_insights_enabled",
|
||||
"defender_additional_email_configured_with_a_security_contact",
|
||||
"defender_ensure_notify_alerts_severity_is_high",
|
||||
"defender_ensure_notify_emails_to_owners",
|
||||
"defender_ensure_mcas_is_enabled",
|
||||
"defender_ensure_wdatp_is_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "annex_i_12",
|
||||
"Name": "Annex I (12)",
|
||||
"Description": "Take periodic back up of the important data and store this data 'off line' (i.e., transferring important files to a storage device that can be detached from a computer/system after copying all the files).",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "annex_i_12",
|
||||
"Service": "azure"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"vm_backup_enabled",
|
||||
"vm_sufficient_daily_backup_retention_period",
|
||||
"storage_ensure_file_shares_soft_delete_is_enabled",
|
||||
"storage_blob_versioning_is_enabled",
|
||||
"storage_ensure_soft_delete_is_enabled",
|
||||
"storage_geo_redundant_enabled",
|
||||
"keyvault_recoverable",
|
||||
"sqlserver_vulnerability_assessment_enabled",
|
||||
"sqlserver_va_periodic_recurring_scans_enabled"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -619,92 +619,6 @@
|
||||
"sqlserver_auditing_retention_90_days",
|
||||
"storage_ensure_soft_delete_is_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_2",
|
||||
"Name": "PI1.2 System inputs are measured and recorded completely, accurately, and timely to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements policies and procedures over system inputs, including controls over completeness and accuracy, to result in products, services, and reporting to meet the entity's objectives. This includes defining accuracy targets, monitoring input quality, and creating detailed records of each input event.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_2",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "azure",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"app_http_logs_enabled",
|
||||
"network_flow_log_captured_sent",
|
||||
"keyvault_logging_enabled",
|
||||
"monitor_diagnostic_settings_exists",
|
||||
"sqlserver_auditing_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_3",
|
||||
"Name": "PI1.3 Data is processed completely, accurately, and timely as authorized to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements controls to ensure data is processed completely, accurately, and timely. This includes defining processing specifications, identifying processing activities, detecting and correcting errors throughout processing, recording processing activities with accurate logs, and ensuring completeness and timeliness of processing.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_3",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "azure",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"monitor_diagnostic_setting_with_appropriate_categories",
|
||||
"monitor_diagnostic_settings_exists",
|
||||
"defender_auto_provisioning_log_analytics_agent_vms_on",
|
||||
"mysql_flexible_server_audit_log_enabled",
|
||||
"postgresql_flexible_server_log_checkpoints_on",
|
||||
"postgresql_flexible_server_log_connections_on",
|
||||
"postgresql_flexible_server_log_disconnections_on",
|
||||
"network_flow_log_more_than_90_days"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_4",
|
||||
"Name": "PI1.4 System outputs are complete, accurate, distributed only to intended parties, and retained to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements controls to ensure system outputs are delivered to authorized recipients in the correct format and protected against unauthorized access, modification, theft, destruction, or corruption. This includes output encryption, access controls, and audit trails for output delivery.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_4",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "azure",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"storage_ensure_encryption_with_customer_managed_keys",
|
||||
"storage_infrastructure_encryption_is_enabled",
|
||||
"monitor_storage_account_with_activity_logs_cmk_encrypted",
|
||||
"monitor_storage_account_with_activity_logs_is_private",
|
||||
"sqlserver_tde_encryption_enabled",
|
||||
"sqlserver_tde_encrypted_with_cmk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_5",
|
||||
"Name": "PI1.5 Stored data is maintained complete, accurate, and protected from unauthorized modification to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements controls to protect stored inputs, items in processing, and outputs from theft, destruction, corruption, or deterioration. This includes data encryption at rest, key management, backup and recovery procedures, access controls, and data integrity validation.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_5",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "azure",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"storage_ensure_encryption_with_customer_managed_keys",
|
||||
"storage_infrastructure_encryption_is_enabled",
|
||||
"storage_ensure_soft_delete_is_enabled",
|
||||
"vm_ensure_attached_disks_encrypted_with_cmk",
|
||||
"vm_ensure_unattached_disks_encrypted_with_cmk",
|
||||
"keyvault_key_rotation_enabled",
|
||||
"keyvault_recoverable"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,87 +492,6 @@
|
||||
"Checks": [
|
||||
"cloudstorage_bucket_log_retention_policy_lock"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_2",
|
||||
"Name": "PI1.2 System inputs are measured and recorded completely, accurately, and timely to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements policies and procedures over system inputs, including controls over completeness and accuracy, to result in products, services, and reporting to meet the entity's objectives. This includes defining accuracy targets, monitoring input quality, and creating detailed records of each input event.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_2",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "gcp",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"compute_loadbalancer_logging_enabled",
|
||||
"compute_subnet_flow_logs_enabled",
|
||||
"logging_sink_created",
|
||||
"iam_audit_logs_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_3",
|
||||
"Name": "PI1.3 Data is processed completely, accurately, and timely as authorized to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements controls to ensure data is processed completely, accurately, and timely. This includes defining processing specifications, identifying processing activities, detecting and correcting errors throughout processing, recording processing activities with accurate logs, and ensuring completeness and timeliness of processing.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_3",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "gcp",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled",
|
||||
"logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled",
|
||||
"logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled",
|
||||
"cloudsql_instance_postgres_log_connections_flag",
|
||||
"cloudsql_instance_postgres_log_disconnections_flag",
|
||||
"cloudsql_instance_postgres_log_statement_flag",
|
||||
"iam_audit_logs_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_4",
|
||||
"Name": "PI1.4 System outputs are complete, accurate, distributed only to intended parties, and retained to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements controls to ensure system outputs are delivered to authorized recipients in the correct format and protected against unauthorized access, modification, theft, destruction, or corruption. This includes output encryption, access controls, and audit trails for output delivery.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_4",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "gcp",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"cloudstorage_bucket_uniform_bucket_level_access",
|
||||
"bigquery_dataset_cmk_encryption",
|
||||
"bigquery_table_cmk_encryption",
|
||||
"compute_instance_confidential_computing_enabled",
|
||||
"pubsub_topic_encryption_with_cmk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "pi_1_5",
|
||||
"Name": "PI1.5 Stored data is maintained complete, accurate, and protected from unauthorized modification to meet the entity's processing integrity commitments and system requirements",
|
||||
"Description": "The entity implements controls to protect stored inputs, items in processing, and outputs from theft, destruction, corruption, or deterioration. This includes data encryption at rest, key management, backup and recovery procedures, access controls, and data integrity validation.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "pi_1_5",
|
||||
"Section": "PI1.0 - Processing Integrity",
|
||||
"Service": "gcp",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"cloudstorage_bucket_log_retention_policy_lock",
|
||||
"cloudsql_instance_automated_backups",
|
||||
"compute_instance_encryption_with_csek_enabled",
|
||||
"kms_key_rotation_enabled",
|
||||
"dataproc_encrypted_with_cmks_disabled"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -29,7 +29,9 @@
|
||||
"Url": "https://hub.prowler.com/check/apigateway_restapi_waf_acl_attached"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"Categories": [
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "",
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"forensics-ready"
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -32,7 +32,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -29,7 +29,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -31,7 +31,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -36,7 +36,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -32,7 +32,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -32,7 +32,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -38,7 +38,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+2
-1
@@ -37,7 +37,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"logging"
|
||||
"logging",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
+1
@@ -36,6 +36,7 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"threat-detection",
|
||||
"logging"
|
||||
],
|
||||
"DependsOn": [],
|
||||
|
||||
+2
-1
@@ -32,7 +32,8 @@
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"trust-boundaries"
|
||||
"trust-boundaries",
|
||||
"threat-detection"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
|
||||
@@ -78,7 +78,6 @@ dependencies = [
|
||||
"alibabacloud_ecs20140526==7.2.5",
|
||||
"alibabacloud_sas20181203==6.1.0",
|
||||
"alibabacloud_oss20190517==1.0.6",
|
||||
"alibabacloud-gateway-oss-util==0.0.3",
|
||||
"alibabacloud_actiontrail20200706==2.4.1",
|
||||
"alibabacloud_cs20151215==6.1.0",
|
||||
"alibabacloud-rds20140815==12.0.0",
|
||||
|
||||
@@ -62,7 +62,7 @@ You are a code reviewer for the Prowler UI project. Analyze the full file conten
|
||||
**RULES TO CHECK:**
|
||||
1. React Imports: NO `import * as React` or `import React, {` → Use `import { useState }`
|
||||
2. TypeScript: NO union types like `type X = "a" | "b"` → Use const-based: `const X = {...} as const`
|
||||
3. Tailwind: NO `var()` or hex colors in className → Use Tailwind utilities and semantic color classes. Exception: `var()` is allowed when passing colors to chart/graph components that require CSS color strings (not Tailwind classes) for their APIs.
|
||||
3. Tailwind: NO `var()` or hex colors in className → Use Tailwind utilities and semantic color classes. Exception: `var()` is allowed when passing colors to chart/graph components that require CSS color strings (not Tailwind classes) for their APIs
|
||||
4. cn(): Use for merging multiple classes or for conditionals (handles Tailwind conflicts with twMerge) → `cn(BUTTON_STYLES.base, BUTTON_STYLES.active, isLoading && "opacity-50")`
|
||||
5. React 19: NO `useMemo`/`useCallback` without reason
|
||||
6. Zod v4: Use `.min(1)` not `.nonempty()`, `z.email()` not `z.string().email()`. All inputs must be validated with Zod.
|
||||
|
||||
@@ -6,7 +6,6 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- Risk Plot component with interactive legend and severity navigation to Overview page [(#9469)](https://github.com/prowler-cloud/prowler/pull/9469)
|
||||
- Navigation progress bar for page transitions using Next.js `onRouterTransitionStart` [(#9465)](https://github.com/prowler-cloud/prowler/pull/9465)
|
||||
- Finding Severity Over Time chart component to Overview page [(#9405)](https://github.com/prowler-cloud/prowler/pull/9405)
|
||||
- Attack Surface component to Overview page [(#9412)](https://github.com/prowler-cloud/prowler/pull/9412)
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
"use server";
|
||||
|
||||
import {
|
||||
getDateFromForTimeRange,
|
||||
type TimeRange,
|
||||
} from "@/app/(prowler)/_new-overview/severity-over-time/_constants/time-range.constants";
|
||||
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
||||
import { handleApiResponse } from "@/lib/server-actions-helper";
|
||||
|
||||
@@ -13,6 +9,20 @@ import {
|
||||
FindingsSeverityOverTimeResponse,
|
||||
} from "./types";
|
||||
|
||||
const TIME_RANGE_VALUES = {
|
||||
FIVE_DAYS: "5D",
|
||||
ONE_WEEK: "1W",
|
||||
ONE_MONTH: "1M",
|
||||
} as const;
|
||||
|
||||
type TimeRange = (typeof TIME_RANGE_VALUES)[keyof typeof TIME_RANGE_VALUES];
|
||||
|
||||
const TIME_RANGE_DAYS: Record<TimeRange, number> = {
|
||||
"5D": 5,
|
||||
"1W": 7,
|
||||
"1M": 30,
|
||||
};
|
||||
|
||||
export type SeverityTrendsResult =
|
||||
| { status: "success"; data: AdaptedSeverityTrendsResponse }
|
||||
| { status: "empty" }
|
||||
@@ -66,9 +76,21 @@ export const getSeverityTrendsByTimeRange = async ({
|
||||
timeRange: TimeRange;
|
||||
filters?: Record<string, string | string[] | undefined>;
|
||||
}): Promise<SeverityTrendsResult> => {
|
||||
const days = TIME_RANGE_DAYS[timeRange];
|
||||
|
||||
if (!days) {
|
||||
console.error("Invalid time range provided");
|
||||
return { status: "error" };
|
||||
}
|
||||
|
||||
const endDate = new Date();
|
||||
const startDate = new Date(endDate.getTime() - days * 24 * 60 * 60 * 1000);
|
||||
|
||||
const dateFrom = startDate.toISOString().split("T")[0];
|
||||
|
||||
const dateFilters = {
|
||||
...filters,
|
||||
"filter[date_from]": getDateFromForTimeRange(timeRange),
|
||||
date_from: dateFrom,
|
||||
};
|
||||
|
||||
return getFindingsSeverityTrends({ filters: dateFilters });
|
||||
|
||||
@@ -267,10 +267,6 @@ export function RiskPlotClient({ data }: RiskPlotClientProps) {
|
||||
<h3 className="text-text-neutral-primary text-lg font-semibold">
|
||||
Risk Plot
|
||||
</h3>
|
||||
<p className="text-text-neutral-tertiary mt-1 text-xs">
|
||||
Threat Score is severity-weighted, not quantity-based. Higher
|
||||
severity findings have greater impact on the score.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative min-h-[400px] w-full flex-1">
|
||||
@@ -302,9 +298,9 @@ export function RiskPlotClient({ data }: RiskPlotClientProps) {
|
||||
<YAxis
|
||||
type="number"
|
||||
dataKey="y"
|
||||
name="Fail Findings"
|
||||
name="Failed Findings"
|
||||
label={{
|
||||
value: "Fail Findings",
|
||||
value: "Failed Findings",
|
||||
angle: -90,
|
||||
position: "left",
|
||||
offset: 10,
|
||||
@@ -342,7 +338,7 @@ export function RiskPlotClient({ data }: RiskPlotClientProps) {
|
||||
{/* Interactive Legend - below chart */}
|
||||
<div className="mt-4 flex flex-col items-start gap-2">
|
||||
<p className="text-text-neutral-tertiary pl-2 text-xs">
|
||||
Click to filter by provider
|
||||
Click to filter by provider.
|
||||
</p>
|
||||
<ChartLegend
|
||||
items={providers.map((p) => ({
|
||||
@@ -367,7 +363,7 @@ export function RiskPlotClient({ data }: RiskPlotClientProps) {
|
||||
{selectedPoint.name}
|
||||
</h4>
|
||||
<p className="text-text-neutral-tertiary text-xs">
|
||||
Threat Score: {selectedPoint.x}% | Fail Findings:{" "}
|
||||
Threat Score: {selectedPoint.x}% | Failed Findings:{" "}
|
||||
{selectedPoint.y}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
+11
-5
@@ -7,12 +7,12 @@ import { getSeverityTrendsByTimeRange } from "@/actions/overview/severity-trends
|
||||
import { LineChart } from "@/components/graphs/line-chart";
|
||||
import { LineConfig, LineDataPoint } from "@/components/graphs/types";
|
||||
import {
|
||||
MUTED_COLOR,
|
||||
SEVERITY_LEVELS,
|
||||
SEVERITY_LINE_CONFIGS,
|
||||
SeverityLevel,
|
||||
} from "@/types/severities";
|
||||
|
||||
import { DEFAULT_TIME_RANGE } from "../_constants/time-range.constants";
|
||||
import { type TimeRange, TimeRangeSelector } from "./time-range-selector";
|
||||
|
||||
interface FindingSeverityOverTimeProps {
|
||||
@@ -24,7 +24,7 @@ export const FindingSeverityOverTime = ({
|
||||
}: FindingSeverityOverTimeProps) => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [timeRange, setTimeRange] = useState<TimeRange>(DEFAULT_TIME_RANGE);
|
||||
const [timeRange, setTimeRange] = useState<TimeRange>("5D");
|
||||
const [data, setData] = useState<LineDataPoint[]>(initialData);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -39,9 +39,6 @@ export const FindingSeverityOverTime = ({
|
||||
const params = new URLSearchParams();
|
||||
params.set("filter[inserted_at]", point.date);
|
||||
|
||||
// Always filter by FAIL status since this chart shows failed findings
|
||||
params.set("filter[status__in]", "FAIL");
|
||||
|
||||
// Add scan_ids filter
|
||||
if (
|
||||
point.scan_ids &&
|
||||
@@ -99,6 +96,15 @@ export const FindingSeverityOverTime = ({
|
||||
// Build line configurations from shared severity configs
|
||||
const lines: LineConfig[] = [...SEVERITY_LINE_CONFIGS];
|
||||
|
||||
// Only add muted line if data contains it
|
||||
if (data.some((item) => item.muted !== undefined)) {
|
||||
lines.push({
|
||||
dataKey: "muted",
|
||||
color: MUTED_COLOR,
|
||||
label: "Muted",
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate x-axis interval based on data length to show all labels without overlap
|
||||
const getXAxisInterval = (): number => {
|
||||
const dataLength = data.length;
|
||||
|
||||
+7
-5
@@ -2,12 +2,14 @@
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import {
|
||||
TIME_RANGE_OPTIONS,
|
||||
type TimeRange,
|
||||
} from "../_constants/time-range.constants";
|
||||
const TIME_RANGE_OPTIONS = {
|
||||
FIVE_DAYS: "5D",
|
||||
ONE_WEEK: "1W",
|
||||
ONE_MONTH: "1M",
|
||||
} as const;
|
||||
|
||||
export type { TimeRange };
|
||||
export type TimeRange =
|
||||
(typeof TIME_RANGE_OPTIONS)[keyof typeof TIME_RANGE_OPTIONS];
|
||||
|
||||
interface TimeRangeSelectorProps {
|
||||
value: TimeRange;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./time-range.constants";
|
||||
@@ -1,23 +0,0 @@
|
||||
export const TIME_RANGE_OPTIONS = {
|
||||
FIVE_DAYS: "5D",
|
||||
ONE_WEEK: "1W",
|
||||
ONE_MONTH: "1M",
|
||||
} as const;
|
||||
|
||||
export type TimeRange =
|
||||
(typeof TIME_RANGE_OPTIONS)[keyof typeof TIME_RANGE_OPTIONS];
|
||||
|
||||
export const TIME_RANGE_DAYS: Record<TimeRange, number> = {
|
||||
"5D": 5,
|
||||
"1W": 7,
|
||||
"1M": 30,
|
||||
};
|
||||
|
||||
export const DEFAULT_TIME_RANGE: TimeRange = "5D";
|
||||
|
||||
export const getDateFromForTimeRange = (timeRange: TimeRange): string => {
|
||||
const days = TIME_RANGE_DAYS[timeRange];
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - days);
|
||||
return date.toISOString().split("T")[0];
|
||||
};
|
||||
+2
-7
@@ -1,11 +1,10 @@
|
||||
import { getSeverityTrendsByTimeRange } from "@/actions/overview/severity-trends";
|
||||
import { getFindingsSeverityTrends } from "@/actions/overview/severity-trends";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/shadcn";
|
||||
|
||||
import { pickFilterParams } from "../_lib/filter-params";
|
||||
import { SSRComponentProps } from "../_types";
|
||||
import { FindingSeverityOverTime } from "./_components/finding-severity-over-time";
|
||||
import { FindingSeverityOverTimeSkeleton } from "./_components/finding-severity-over-time.skeleton";
|
||||
import { DEFAULT_TIME_RANGE } from "./_constants/time-range.constants";
|
||||
|
||||
export { FindingSeverityOverTimeSkeleton };
|
||||
|
||||
@@ -26,11 +25,7 @@ export const FindingSeverityOverTimeSSR = async ({
|
||||
searchParams,
|
||||
}: SSRComponentProps) => {
|
||||
const filters = pickFilterParams(searchParams);
|
||||
|
||||
const result = await getSeverityTrendsByTimeRange({
|
||||
timeRange: DEFAULT_TIME_RANGE,
|
||||
filters,
|
||||
});
|
||||
const result = await getFindingsSeverityTrends({ filters });
|
||||
|
||||
if (result.status === "error") {
|
||||
return <EmptyState message="Failed to load severity trends data" />;
|
||||
|
||||
@@ -68,31 +68,10 @@ const CustomLineTooltip = ({
|
||||
const typedPayload = payload as unknown as TooltipPayloadItem[];
|
||||
|
||||
// Filter payload if a line is selected or hovered
|
||||
const filteredPayload = filterLine
|
||||
const displayPayload = filterLine
|
||||
? typedPayload.filter((item) => item.dataKey === filterLine)
|
||||
: typedPayload;
|
||||
|
||||
// Sort by severity order: critical, high, medium, low, informational
|
||||
const severityOrder = [
|
||||
"critical",
|
||||
"high",
|
||||
"medium",
|
||||
"low",
|
||||
"informational",
|
||||
] as const;
|
||||
const displayPayload = [...filteredPayload].sort((a, b) => {
|
||||
const aIndex = severityOrder.indexOf(
|
||||
a.dataKey as (typeof severityOrder)[number],
|
||||
);
|
||||
const bIndex = severityOrder.indexOf(
|
||||
b.dataKey as (typeof severityOrder)[number],
|
||||
);
|
||||
// Items not in severityOrder go to the end
|
||||
if (aIndex === -1) return 1;
|
||||
if (bIndex === -1) return -1;
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
|
||||
if (displayPayload.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -117,17 +96,12 @@ const CustomLineTooltip = ({
|
||||
|
||||
return (
|
||||
<div key={item.dataKey} className="space-y-1">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="h-2 w-2 rounded-full"
|
||||
style={{ backgroundColor: item.stroke }}
|
||||
/>
|
||||
<span className="text-text-neutral-secondary text-sm">
|
||||
{item.name}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-text-neutral-primary text-sm font-medium">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="h-2 w-2 rounded-full"
|
||||
style={{ backgroundColor: item.stroke }}
|
||||
/>
|
||||
<span className="text-text-neutral-primary text-sm">
|
||||
{item.value}
|
||||
</span>
|
||||
</div>
|
||||
@@ -286,7 +260,7 @@ export function LineChart({
|
||||
|
||||
<div className="mt-4 flex flex-col items-start gap-2">
|
||||
<p className="text-text-neutral-tertiary pl-2 text-xs">
|
||||
Click to filter by severity
|
||||
Click to filter by severity.
|
||||
</p>
|
||||
<ChartLegend
|
||||
items={legendItems}
|
||||
|
||||
Reference in New Issue
Block a user