diff --git a/README.md b/README.md index dbf836eec0..e2961b20b4 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,36 @@ python prowler-cli.py -v > If your Poetry version is below v2.0.0, continue using `poetry shell` to activate your environment. > For further guidance, refer to the Poetry Environment Activation Guide https://python-poetry.org/docs/managing-environments/#activating-the-environment. +# ๐Ÿ›ก๏ธ GitHub Action + +The official **Prowler GitHub Action** runs Prowler scans in your GitHub workflows using the official [`prowlercloud/prowler`](https://hub.docker.com/r/prowlercloud/prowler) Docker image. Scans run on any [supported provider](https://docs.prowler.com/user-guide/providers/), with optional [`--push-to-cloud`](https://docs.prowler.com/user-guide/tutorials/prowler-app-import-findings) to send findings to Prowler Cloud and optional SARIF upload so findings show up in the repo's **Security โ†’ Code scanning** tab and as inline PR annotations. + +```yaml +name: Prowler IaC Scan +on: + pull_request: + +permissions: + contents: read + security-events: write + actions: read + +jobs: + prowler: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: prowler-cloud/prowler@5.25 + with: + provider: iac + output-formats: sarif json-ocsf + upload-sarif: true + flags: --severity critical high +``` + +Full configuration, per-provider authentication, and SARIF examples: [Prowler GitHub Action tutorial](docs/user-guide/tutorials/prowler-app-github-action.mdx). Marketplace listing: [Prowler Security Scan](https://github.com/marketplace/actions/prowler-security-scan). + # โœ๏ธ High level architecture ## Prowler App diff --git a/action.yml b/action.yml new file mode 100644 index 0000000000..3b5b8d8bdf --- /dev/null +++ b/action.yml @@ -0,0 +1,307 @@ +name: Prowler Security Scan +description: Run Prowler cloud security scanner using the official Docker image +branding: + icon: cloud + color: green + +inputs: + provider: + description: Cloud provider to scan (e.g. aws, azure, gcp, github, kubernetes, iac). See https://docs.prowler.com for supported providers. + required: true + image-tag: + description: > + Docker image tag for prowlercloud/prowler. + Default is "stable" (latest release). Available tags: + "stable" (latest release), "latest" (master branch, not stable), + "" (pinned release version). + See all tags at https://hub.docker.com/r/prowlercloud/prowler/tags + required: false + default: stable + output-formats: + description: Output format(s) for scan results (e.g. "json-ocsf", "sarif json-ocsf") + required: false + default: json-ocsf + push-to-cloud: + description: Push scan findings to Prowler Cloud. Requires the PROWLER_CLOUD_API_KEY environment variable. See https://docs.prowler.com/user-guide/tutorials/prowler-app-import-findings#using-the-cli + required: false + default: "false" + flags: + description: 'Additional CLI flags passed to the Prowler scan (e.g. "--severity critical high --compliance cis_aws"). Values containing spaces can be quoted, e.g. "--resource-tag ''Environment=My Server''".' + required: false + default: "" + extra-env: + description: > + Space-, newline-, or comma-separated list of host environment variable NAMES to forward to the Prowler container + (e.g. "AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN" for AWS, + "GITHUB_PERSONAL_ACCESS_TOKEN" for GitHub, "CLOUDFLARE_API_TOKEN" for Cloudflare). + List names only; set the values via `env:` at the workflow or job level (typically from `secrets.*`). + See the README for per-provider examples. + required: false + default: "" + upload-sarif: + description: 'Upload SARIF results to GitHub Code Scanning (requires "sarif" in output-formats and both `security-events: write` and `actions: read` permissions)' + required: false + default: "false" + sarif-file: + description: Path to the SARIF file to upload (auto-detected from output/ if not set) + required: false + default: "" + sarif-category: + description: Category for the SARIF upload (used to distinguish multiple analyses) + required: false + default: prowler + fail-on-findings: + description: Fail the workflow step when Prowler detects findings (exit code 3). By default the action tolerates findings and succeeds. + required: false + default: "false" + +runs: + using: composite + steps: + - name: Validate inputs + shell: bash + env: + INPUT_IMAGE_TAG: ${{ inputs.image-tag }} + INPUT_UPLOAD_SARIF: ${{ inputs.upload-sarif }} + INPUT_OUTPUT_FORMATS: ${{ inputs.output-formats }} + run: | + # Validate image tag format (alphanumeric, dots, hyphens, underscores only) + if [[ ! "$INPUT_IMAGE_TAG" =~ ^[a-zA-Z0-9._-]+$ ]]; then + echo "::error::Invalid image-tag '${INPUT_IMAGE_TAG}'. Must contain only alphanumeric characters, dots, hyphens, and underscores." + exit 1 + fi + + # Warn if upload-sarif is enabled but sarif not in output-formats + if [ "$INPUT_UPLOAD_SARIF" = "true" ]; then + if [[ ! "$INPUT_OUTPUT_FORMATS" =~ (^|[[:space:]])sarif($|[[:space:]]) ]]; then + echo "::warning::upload-sarif is enabled but 'sarif' is not included in output-formats ('${INPUT_OUTPUT_FORMATS}'). SARIF upload will fail unless you add 'sarif' to output-formats." + fi + fi + + - name: Run Prowler scan + shell: bash + env: + INPUT_PROVIDER: ${{ inputs.provider }} + INPUT_IMAGE_TAG: ${{ inputs.image-tag }} + INPUT_OUTPUT_FORMATS: ${{ inputs.output-formats }} + INPUT_PUSH_TO_CLOUD: ${{ inputs.push-to-cloud }} + INPUT_FLAGS: ${{ inputs.flags }} + INPUT_EXTRA_ENV: ${{ inputs.extra-env }} + INPUT_FAIL_ON_FINDINGS: ${{ inputs.fail-on-findings }} + run: | + set -e + + # Parse space-separated inputs with shlex so values with spaces can be quoted + # (e.g. `--resource-tag 'Environment=My Server'`). + mapfile -t OUTPUT_FORMATS < <(python3 -c 'import shlex, os; [print(t) for t in shlex.split(os.environ.get("INPUT_OUTPUT_FORMATS", ""))]') + mapfile -t EXTRA_FLAGS < <(python3 -c 'import shlex, os; [print(t) for t in shlex.split(os.environ.get("INPUT_FLAGS", ""))]') + mapfile -t EXTRA_ENV_NAMES < <(python3 -c 'import shlex, os; [print(t) for t in shlex.split(os.environ.get("INPUT_EXTRA_ENV", "").replace(",", " "))]') + + env_args=() + for var in "${EXTRA_ENV_NAMES[@]}"; do + [ -z "$var" ] && continue + if [[ ! "$var" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then + echo "::error::Invalid env var name '${var}' in extra-env. Names must match ^[A-Za-z_][A-Za-z0-9_]*$." + exit 1 + fi + env_args+=("-e" "$var") + done + + push_args=() + if [ "$INPUT_PUSH_TO_CLOUD" = "true" ]; then + push_args=("--push-to-cloud") + env_args+=("-e" "PROWLER_CLOUD_API_KEY") + fi + + mkdir -p "$GITHUB_WORKSPACE/output" + chmod 777 "$GITHUB_WORKSPACE/output" + + set +e + docker run --rm \ + "${env_args[@]}" \ + -v "$GITHUB_WORKSPACE:/home/prowler/workspace" \ + -v "$GITHUB_WORKSPACE/output:/home/prowler/workspace/output" \ + -w /home/prowler/workspace \ + "prowlercloud/prowler:${INPUT_IMAGE_TAG}" \ + "$INPUT_PROVIDER" \ + --output-formats "${OUTPUT_FORMATS[@]}" \ + "${push_args[@]}" \ + "${EXTRA_FLAGS[@]}" + exit_code=$? + set -e + + # Exit code 3 = findings detected + if [ "$exit_code" -eq 3 ] && [ "$INPUT_FAIL_ON_FINDINGS" != "true" ]; then + echo "::notice::Prowler detected findings (exit code 3). Set fail-on-findings to 'true' to fail the workflow on findings." + exit 0 + fi + exit $exit_code + + - name: Upload scan results + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: prowler-${{ inputs.provider }} + path: output/ + retention-days: 30 + if-no-files-found: warn + + - name: Find SARIF file + if: always() && inputs.upload-sarif == 'true' + id: find-sarif + shell: bash + env: + INPUT_SARIF_FILE: ${{ inputs.sarif-file }} + run: | + if [ -n "$INPUT_SARIF_FILE" ]; then + echo "sarif_path=$INPUT_SARIF_FILE" >> "$GITHUB_OUTPUT" + else + sarif_file=$(find output/ -name '*.sarif' -type f | head -1) + if [ -z "$sarif_file" ]; then + echo "::warning::No .sarif file found in output/. Ensure 'sarif' is included in output-formats." + echo "sarif_path=" >> "$GITHUB_OUTPUT" + else + echo "sarif_path=$sarif_file" >> "$GITHUB_OUTPUT" + fi + fi + + - name: Upload SARIF to GitHub Code Scanning + if: always() && inputs.upload-sarif == 'true' && steps.find-sarif.outputs.sarif_path != '' + uses: github/codeql-action/upload-sarif@d4b3ca9fa7f69d38bfcd667bdc45bc373d16277e # v4 + with: + sarif_file: ${{ steps.find-sarif.outputs.sarif_path }} + category: ${{ inputs.sarif-category }} + + - name: Write scan summary + if: always() + shell: bash + env: + INPUT_PROVIDER: ${{ inputs.provider }} + INPUT_UPLOAD_SARIF: ${{ inputs.upload-sarif }} + INPUT_PUSH_TO_CLOUD: ${{ inputs.push-to-cloud }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + BRANCH: ${{ github.head_ref || github.ref_name }} + GH_TOKEN: ${{ github.token }} + run: | + set +e + + # Build a link to the scan step in the workflow logs. Requires `actions: read` + # on the caller's GITHUB_TOKEN; silently skips the link if unavailable. + scan_step_url="" + if [ -n "${GH_TOKEN:-}" ] && command -v gh >/dev/null 2>&1; then + job_info=$(gh api \ + "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${GITHUB_RUN_ATTEMPT:-1}/jobs" \ + --jq ".jobs[] | select(.runner_name == \"${RUNNER_NAME:-}\")" 2>/dev/null) + if [ -n "$job_info" ]; then + job_id=$(jq -r '.id // empty' <<<"$job_info") + step_number=$(jq -r '[.steps[]? | select((.name // "") | test("Run Prowler scan"; "i")) | .number] | first // empty' <<<"$job_info") + if [ -z "$step_number" ]; then + step_number=$(jq -r '[.steps[]? | select(.status == "in_progress") | .number] | first // empty' <<<"$job_info") + fi + if [ -n "$job_id" ] && [ -n "$step_number" ]; then + scan_step_url="${REPO_URL}/actions/runs/${GITHUB_RUN_ID}/job/${job_id}#step:${step_number}:1" + elif [ -n "$job_id" ]; then + scan_step_url="${REPO_URL}/actions/runs/${GITHUB_RUN_ID}/job/${job_id}" + fi + fi + fi + + # Map provider code to a properly-cased display name. + case "$INPUT_PROVIDER" in + alibabacloud) provider_name="Alibaba Cloud" ;; + aws) provider_name="AWS" ;; + azure) provider_name="Azure" ;; + cloudflare) provider_name="Cloudflare" ;; + gcp) provider_name="GCP" ;; + github) provider_name="GitHub" ;; + googleworkspace) provider_name="Google Workspace" ;; + iac) provider_name="IaC" ;; + image) provider_name="Container Image" ;; + kubernetes) provider_name="Kubernetes" ;; + llm) provider_name="LLM" ;; + m365) provider_name="Microsoft 365" ;; + mongodbatlas) provider_name="MongoDB Atlas" ;; + nhn) provider_name="NHN" ;; + openstack) provider_name="OpenStack" ;; + oraclecloud) provider_name="Oracle Cloud" ;; + vercel) provider_name="Vercel" ;; + *) provider_name="${INPUT_PROVIDER^}" ;; + esac + + ocsf_file=$(find output/ -name '*.ocsf.json' -type f 2>/dev/null | head -1) + + { + echo "## Prowler ${provider_name} Scan Summary" + echo "" + + counts="" + if [ -n "$ocsf_file" ] && [ -s "$ocsf_file" ]; then + counts=$(jq -r '[ + length, + ([.[] | select(.status_code == "FAIL")] | length), + ([.[] | select(.status_code == "PASS")] | length), + ([.[] | select(.status_code == "MUTED")] | length), + ([.[] | select(.status_code == "FAIL" and .severity == "Critical")] | length), + ([.[] | select(.status_code == "FAIL" and .severity == "High")] | length), + ([.[] | select(.status_code == "FAIL" and .severity == "Medium")] | length), + ([.[] | select(.status_code == "FAIL" and .severity == "Low")] | length), + ([.[] | select(.status_code == "FAIL" and .severity == "Informational")] | length) + ] | @tsv' "$ocsf_file" 2>/dev/null) + fi + + if [ -n "$counts" ]; then + read -r total fail pass muted critical high medium low info <<<"$counts" + + line="**${fail:-0} failing** ยท ${pass:-0} passing" + [ "${muted:-0}" -gt 0 ] && line="${line} ยท ${muted} muted" + echo "${line} โ€” ${total:-0} checks total" + echo "" + echo "| Severity | Failing |" + echo "|----------|---------|" + echo "| โ€ผ๏ธ Critical | ${critical:-0} |" + echo "| ๐Ÿ”ด High | ${high:-0} |" + echo "| ๐ŸŸ  Medium | ${medium:-0} |" + echo "| ๐Ÿ”ต Low | ${low:-0} |" + echo "| โšช Informational | ${info:-0} |" + echo "" + else + echo "_No findings report was produced. Check the scan logs above._" + echo "" + fi + + if [ -n "$scan_step_url" ]; then + echo "**Scan logs:** [view in workflow run](${scan_step_url})" + echo "" + fi + + echo "**Get the full report:** [\`prowler-${INPUT_PROVIDER}\` artifact](${RUN_URL}#artifacts)" + + if [ "$INPUT_UPLOAD_SARIF" = "true" ] && [ -n "$BRANCH" ]; then + encoded_branch=$(jq -nr --arg b "$BRANCH" '$b|@uri') + echo "" + echo "**See results in GitHub Code Security:** [open alerts on \`${BRANCH}\`](${REPO_URL}/security/code-scanning?query=is%3Aopen+branch%3A${encoded_branch})" + fi + + if [ "$INPUT_PUSH_TO_CLOUD" != "true" ]; then + echo "" + echo "---" + echo "" + echo "### Scale ${provider_name} security with Prowler Cloud โ˜๏ธ" + echo "" + echo "Send this scan's findings to **[Prowler Cloud](https://cloud.prowler.com)** and get:" + echo "" + echo "- **Unified findings** across every cloud, SaaS provider (M365, Google Workspace, GitHub, MongoDB Atlas), IaC repo, Kubernetes cluster, and container image" + echo "- **Posture over time** with alerts, and notifications" + echo "- **Prowler Lighthouse AI**: agentic assistant that triages findings, explains root cause and helps with remediation" + echo "- **50+ Compliance frameworks** mapped automatically" + echo "- **Enterprise-ready platform**: SOC 2 Type 2, SSO/SAML, AWS Security Hub, S3 and Jira integrations" + echo "" + echo "**Get started in 3 steps:**" + echo "1. Create an account at [cloud.prowler.com](https://cloud.prowler.com)" + echo "2. Generate a Prowler Cloud API key ([docs](https://docs.prowler.com/user-guide/tutorials/prowler-app-import-findings#using-the-cli))" + echo "3. Add \`PROWLER_CLOUD_API_KEY\` to your GitHub secrets and set \`push-to-cloud: true\` on this action" + echo "" + echo "See [prowler.com/pricing](https://prowler.com/pricing) for plan details." + fi + } >> "$GITHUB_STEP_SUMMARY" diff --git a/docs/docs.json b/docs/docs.json index 82c23ba42f..aee4e3259a 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -164,6 +164,13 @@ } ] }, + { + "group": "CI/CD", + "pages": [ + "user-guide/tutorials/prowler-app-github-action", + "user-guide/cookbooks/cicd-pipeline" + ] + }, { "group": "CLI", "pages": [ diff --git a/docs/images/github-action/scan-summary.png b/docs/images/github-action/scan-summary.png new file mode 100644 index 0000000000..6a231ec06f Binary files /dev/null and b/docs/images/github-action/scan-summary.png differ diff --git a/docs/user-guide/cookbooks/cicd-pipeline.mdx b/docs/user-guide/cookbooks/cicd-pipeline.mdx index 9dffd5049c..ee7d49eb91 100644 --- a/docs/user-guide/cookbooks/cicd-pipeline.mdx +++ b/docs/user-guide/cookbooks/cicd-pipeline.mdx @@ -2,6 +2,10 @@ title: 'Run Prowler in CI/CD and Send Findings to Prowler Cloud' --- + +For new projects, use the official [Prowler GitHub Action](/user-guide/tutorials/prowler-app-github-action) โ€” a Docker-based reusable action that runs scans, optionally pushes findings to Prowler Cloud, and uploads SARIF results to GitHub Code Scanning. The GitHub Actions examples below document the legacy pip-based flow. + + This cookbook demonstrates how to integrate Prowler into CI/CD pipelines so that security scans run automatically and findings are sent to Prowler Cloud via [Import Findings](/user-guide/tutorials/prowler-app-import-findings). Examples cover GitHub Actions and GitLab CI. ## Prerequisites diff --git a/docs/user-guide/tutorials/prowler-app-github-action.mdx b/docs/user-guide/tutorials/prowler-app-github-action.mdx new file mode 100644 index 0000000000..5b80511b89 --- /dev/null +++ b/docs/user-guide/tutorials/prowler-app-github-action.mdx @@ -0,0 +1,265 @@ +--- +title: 'GitHub Action' +description: 'Run Prowler scans in GitHub Actions using the official Docker-based action' +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +The official **Prowler GitHub Action** runs Prowler scans inside your GitHub workflows using the official [`prowlercloud/prowler`](https://hub.docker.com/r/prowlercloud/prowler) Docker image. It supports every [Prowler provider](/user-guide/providers/) (AWS, Azure, GCP, Kubernetes, GitHub, Cloudflare, IaC, and more), optionally pushes findings to Prowler Cloud, and uploads SARIF results to GitHub Code Scanning so findings appear in the **Security** tab and as inline PR annotations. + +Source: [`prowler-cloud/prowler`](https://github.com/prowler-cloud/prowler) ยท Marketplace listing: [Prowler Security Scan](https://github.com/marketplace/actions/prowler-security-scan). + +## Inputs + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `provider` | yes | โ€” | Cloud provider to scan (`aws`, `azure`, `gcp`, `github`, `kubernetes`, `iac`, `cloudflare`, etc.) | +| `image-tag` | no | `stable` | Docker image tag โ€” `stable` (latest release), `latest` (master, not stable), or `` (pinned). See [available tags](https://hub.docker.com/r/prowlercloud/prowler/tags). | +| `output-formats` | no | `json-ocsf` | Output format(s) for scan results. Space-separated (e.g. `sarif json-ocsf`) | +| `push-to-cloud` | no | `false` | Push findings to [Prowler Cloud](/user-guide/tutorials/prowler-app-import-findings). When `true`, `PROWLER_CLOUD_API_KEY` is auto-forwarded | +| `flags` | no | `""` | Additional CLI flags (e.g. `--severity critical high`). Values with spaces can be quoted: `--resource-tag 'Environment=My Server'` | +| `extra-env` | no | `""` | Space-, newline-, or comma-separated list of env var **names** to forward to the container (see [Authentication](#authentication)) | +| `upload-sarif` | no | `false` | Upload SARIF results to GitHub Code Scanning | +| `sarif-file` | no | `""` | Path to SARIF file (auto-detected from `output/` if not set) | +| `sarif-category` | no | `prowler` | Category for the SARIF upload (distinguishes multiple analyses) | +| `fail-on-findings` | no | `false` | Fail the workflow step when findings are detected (exit code 3) | + +## Usage + +### AWS scan + +```yaml +- uses: prowler-cloud/prowler@5.25 + with: + provider: aws + extra-env: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }} +``` + +### Push findings to Prowler Cloud + +Send scan results directly to [Prowler Cloud](/user-guide/tutorials/prowler-app-import-findings) for centralized visibility, compliance tracking, and team collaboration. + +```yaml +- uses: prowler-cloud/prowler@5.25 + with: + provider: aws + push-to-cloud: true + extra-env: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }} + PROWLER_CLOUD_API_KEY: ${{ secrets.PROWLER_CLOUD_API_KEY }} +``` + + +When `push-to-cloud: true`, `PROWLER_CLOUD_API_KEY` is forwarded automatically โ€” set it in `env:` but don't list it in `extra-env`. Requires a Prowler Cloud subscription and an API key with the **Manage Ingestions** permission. See [API Keys](/user-guide/tutorials/prowler-app-api-keys). + + +### Upload SARIF to GitHub Code Scanning + +Findings appear in the **Security** tab and as **inline PR annotations** when SARIF upload is enabled. + +```yaml +name: Prowler IaC Scan +on: + pull_request: + +permissions: + contents: read + security-events: write + actions: read + +jobs: + prowler: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: prowler-cloud/prowler@5.25 + with: + provider: iac + output-formats: sarif json-ocsf + upload-sarif: true + flags: --severity critical high +``` + + +**Requirements:** +- Include `sarif` in `output-formats` (the action warns if this is missing). +- The workflow needs `security-events: write` and `actions: read` permissions. +- GitHub Code Scanning is free for public repositories. Private repositories require a [GitHub Code Security](https://docs.github.com/en/get-started/learning-about-github/about-github-advanced-security) license. + + +### Combine push-to-cloud with SARIF upload + +```yaml +- uses: prowler-cloud/prowler@5.25 + with: + provider: aws + output-formats: sarif json-ocsf + push-to-cloud: true + upload-sarif: true + extra-env: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }} + PROWLER_CLOUD_API_KEY: ${{ secrets.PROWLER_CLOUD_API_KEY }} +``` + +### Scan the current repository with the GitHub provider + +```yaml +name: Prowler GitHub Scan +on: + schedule: + - cron: '0 0 * * 0' + workflow_dispatch: + +jobs: + prowler: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: prowler-cloud/prowler@5.25 + with: + provider: github + flags: --repository ${{ github.repository }} + extra-env: GITHUB_PERSONAL_ACCESS_TOKEN + env: + GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.PROWLER_GITHUB_PAT }} +``` + + +`--repository` scans a single repo. Use `--organization ` instead to include org-level checks (MFA, security policies, etc.). See the [GitHub provider authentication](/user-guide/providers/github/authentication) for required token permissions. + + +### Fail the PR on findings + +By default the action tolerates findings (exit code 3) and succeeds. Set `fail-on-findings: true` to fail the workflow step when Prowler detects findings. Combine with `--severity` to control which severity levels trigger the failure: + +```yaml +- uses: prowler-cloud/prowler@5.25 + with: + provider: iac + output-formats: sarif + upload-sarif: true + fail-on-findings: true + flags: --severity critical high +``` + +The scan step fails if critical/high findings are detected, blocking the PR via required checks. SARIF is still uploaded (the upload step runs with `if: always()`) so findings appear in the Security tab regardless. + +## Authentication + +Each provider requires its own credentials passed as environment variables. Credentials are **not forwarded automatically** โ€” list every env var name you need in the `extra-env` input, and set its value via `env:` at the step, job, or workflow level (typically from `secrets.*`). + +Refer to the [Prowler provider docs](/user-guide/providers/) for the full list of variables each provider supports. Common ones: + +| Provider | Typical `extra-env` | +|----------|---------------------| +| AWS | `AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_DEFAULT_REGION` (OIDC exports these automatically) | +| Azure | `AZURE_CLIENT_ID AZURE_CLIENT_SECRET AZURE_TENANT_ID` | +| GCP | `GOOGLE_APPLICATION_CREDENTIALS CLOUDSDK_AUTH_ACCESS_TOKEN GOOGLE_CLOUD_PROJECT` | +| GitHub | `GITHUB_PERSONAL_ACCESS_TOKEN` *(or `GITHUB_OAUTH_APP_TOKEN`, or `GITHUB_APP_ID GITHUB_APP_KEY`)* | +| Kubernetes | `KUBECONFIG` | +| Cloudflare | `CLOUDFLARE_API_TOKEN` *(or `CLOUDFLARE_API_KEY CLOUDFLARE_API_EMAIL`)* | + + +`PROWLER_CLOUD_API_KEY` is auto-forwarded when `push-to-cloud: true` โ€” no need to add it to `extra-env`. + + +### AWS + +Use [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) with OIDC (recommended) or pass static credentials. OIDC sets `AWS_*` env vars on the runner, so you only forward them: + +```yaml +permissions: + id-token: write + contents: read + +steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::123456789012:role/ProwlerRole + aws-region: eu-west-1 + + - uses: prowler-cloud/prowler@5.25 + with: + provider: aws + extra-env: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_DEFAULT_REGION +``` + +### Azure + +Use [azure/login](https://github.com/Azure/login) with a service principal or pass credentials directly: + +```yaml +steps: + - uses: azure/login@v2 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - uses: prowler-cloud/prowler@5.25 + with: + provider: azure + extra-env: AZURE_CLIENT_ID AZURE_CLIENT_SECRET AZURE_TENANT_ID + env: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} +``` + +### GCP + +Use [google-github-actions/auth](https://github.com/google-github-actions/auth) with Workload Identity Federation (recommended): + +```yaml +permissions: + id-token: write + contents: read + +steps: + - uses: google-github-actions/auth@v2 + with: + workload_identity_provider: projects/123456/locations/global/workloadIdentityPools/my-pool/providers/my-provider + service_account: prowler@my-project.iam.gserviceaccount.com + + - uses: prowler-cloud/prowler@5.25 + with: + provider: gcp + extra-env: GOOGLE_APPLICATION_CREDENTIALS CLOUDSDK_AUTH_ACCESS_TOKEN GOOGLE_CLOUD_PROJECT +``` + +### Cloudflare + +Create a Cloudflare API Token with `Zone:Read`, `Zone Settings:Read`, and `DNS:Read` permissions ([provider auth docs](/user-guide/providers/cloudflare/authentication)). Then: + +```yaml +- uses: prowler-cloud/prowler@5.25 + with: + provider: cloudflare + extra-env: CLOUDFLARE_API_TOKEN + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} +``` + +## Outputs + +Scan results are written to `output/` in the workspace and uploaded as artifacts named `prowler-` with 30-day retention. + +When `upload-sarif` is enabled, SARIF results are also uploaded to GitHub Code Scanning and appear on the repository's **Security โ†’ Code scanning** tab, filtered by the branch that ran the scan. + +### Step summary + +The action writes a summary to the run page with a per-severity breakdown of failing checks, artifact and Code Scanning links, and (when `push-to-cloud: false`) a pointer to [Prowler Cloud](https://cloud.prowler.com) for continuous monitoring. + +GitHub Actions run page showing the Prowler IaC Scan Summary with failing and passing counts, severity breakdown, scan log link, artifact link, and GitHub Code Security link diff --git a/docs/user-guide/tutorials/prowler-app-import-findings.mdx b/docs/user-guide/tutorials/prowler-app-import-findings.mdx index 48e6049943..9f1002c2a9 100644 --- a/docs/user-guide/tutorials/prowler-app-import-findings.mdx +++ b/docs/user-guide/tutorials/prowler-app-import-findings.mdx @@ -365,6 +365,10 @@ Prowler must be installed in the CI/CD environment before running scans. Refer t ### GitHub Actions + +For new projects, use the official [Prowler GitHub Action](/user-guide/tutorials/prowler-app-github-action) โ€” a Docker-based reusable action that runs scans, optionally pushes findings to Prowler Cloud, and uploads SARIF results to GitHub Code Scanning. The example below documents the legacy pip-based flow. + + ```yaml - name: Install Prowler run: pip install prowler diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index e165641f70..494fdd9105 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - SARIF output format for the IaC provider, enabling GitHub Code Scanning integration via `--output-formats sarif` [(#10626)](https://github.com/prowler-cloud/prowler/pull/10626) - `repository_default_branch_dismisses_stale_reviews` check for GitHub provider to ensure stale pull request approvals are dismissed when new commits are pushed [(#10569)](https://github.com/prowler-cloud/prowler/pull/10569) +- Official Prowler GitHub Action (`prowler-cloud/prowler@5.25`) for running scans in GitHub workflows with optional `--push-to-cloud` and SARIF upload to GitHub Code Scanning [(#10872)](https://github.com/prowler-cloud/prowler/pull/10872) ---