-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Expand file tree
/
Copy pathaction.yml
More file actions
307 lines (282 loc) · 13.9 KB
/
action.yml
File metadata and controls
307 lines (282 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
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),
"<x.y.z>" (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"