Security AWS

Prowler 5.48.0 on AWS User Guide

| Product: Prowler 5.48.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Prowler 5.48.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg OWASP ZAP and Vulnerability Scanners listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Prowler is the open source cloud security assessment tool. It runs hundreds of read-only checks against an AWS account (672 in this release), and also Azure, Google Cloud, Kubernetes, Microsoft 365 and GitHub. It reports exactly which controls pass and which fail, with a severity, the affected resource and a remediation, and maps every finding onto published compliance frameworks such as CIS, AWS Foundational Security Best Practices, PCI DSS, HIPAA, SOC 2, ISO 27001, NIST and FedRAMP (50 frameworks for AWS). Its iac provider also scans Terraform, CloudFormation, Kubernetes manifests and Dockerfiles for misconfigurations and leaked secrets before they are deployed. Results are written as CSV, JSON in the OCSF schema and a self-contained HTML report.

Upstream Prowler installs from PyPI with pip. The cloudimg image adds:

  • A fully hash-locked install. Prowler 5.48.0 and every one of its 243 dependencies are installed from a lock file that pins the sha256 of each file, the Prowler wheel included: nothing is resolved at build time and a byte that differs from PyPI aborts the build. The 11 pure-Python dependencies that publish no wheel were built from hash-checked source in a sandbox with no network, and each resulting wheel is pinned too. An integrity check (sudo prowler-verify) re-hashes all of the roughly 76,000 installed Python files against the build manifest.
  • Read-only access to your AWS account through the instance role. No access key is ever stored on the instance: you attach an IAM role with the AWS managed SecurityAudit and ViewOnlyAccess policies, and prowler aws picks it up through IMDSv2.
  • A scheduled scan that cannot phone home. A daily timer scans infrastructure-as-code in a directory you choose. The scan runs as its own unprivileged prowler user inside a systemd sandbox with no network access at all: every address is denied, including the EC2 instance metadata service, so code in a scanned repository cannot reach your instance role or anything else. The operating system is read-only to it and it holds no capabilities.
  • Reports only root can read. The scheduled scan writes its reports with mode 0600 into dated 0700 directories under /var/lib/prowler/reports, and the system journal gets only a one line count of findings.
  • A self-test on every new instance. On first boot the instance checks the installed scanner against its build manifest and scans two bundled fixtures: a deliberately insecure Terraform file that must be flagged and a clean target that must not.
  • Key-only SSH (no password or keyboard-interactive login, no root login), IMDSv2 only, and a fully patched Ubuntu 24.04 LTS with unattended security upgrades switched on, including for the bundled trivy engine that the iac provider uses.

This is a command-line product: there is no web UI, no listening service, no account and no password. SSH on port 22 is the only open port.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
  • A security group that allows TCP 22 (SSH) from your own networks only. No other inbound port is needed.
  • To assess an AWS account: outbound HTTPS to the AWS APIs (through an internet gateway, a NAT gateway or VPC endpoints) and permission to create an IAM role. Scanning infrastructure-as-code needs neither.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Prowler 5.48.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (a full AWS assessment runs many checks in parallel and uses about 2 GB of memory), your key pair and a security group as above. The root volume defaults to 12 GiB; add space if you will keep many reports.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <sg-allowing-22-from-your-network> --subnet-id <subnet-id> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=prowler}]'

Step 2: Connect to your instance

Operating system SSH user
Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<instance-public-ip>

There is no password to look up: /etc/prowler/credentials on the instance is an informational note that says so and lists the paths used below.

Step 3: Check the installation

prowler --version
systemctl is-enabled prowler-scan.timer prowler-firstboot.service
systemctl is-active prowler-scan.timer
Prowler 5.48.0 (You are running the latest version, yay!)
enabled
enabled
active

prowler-firstboot.service ran once on the first boot of your instance and started the self-test. prowler-scan.timer runs the scheduled scan once a day. Confirm the self-test passed:

sudo journalctl -u prowler-selftest.service -o cat --no-pager | grep SELFTEST
prowler-scan: SELFTEST insecure rc=3 total=81 fail=13 critical=0 high=9 medium=1 low=3; clean rc=0 total=0 fail=0 critical=0 high=0 medium=0 low=0
prowler-scan: SELFTEST OK

Network use. When you run prowler yourself it makes one HTTPS request to see whether a newer Prowler release exists (the "latest version" note above); it sends nothing about your accounts or findings. The scheduled scan cannot make it at all (Step 9).

Step 4: Scan the insecure fixture

/usr/share/prowler/fixtures/insecure/main.tf is a small Terraform file with an SSH security group open to 0.0.0.0/0, a public-read S3 bucket and an EC2 instance with an unencrypted volume that allows IMDSv1. Never apply it. Scan it with the iac provider, which needs no credentials, and write all three report formats to ~/prowler-out. Prowler adds to an existing report of the same name instead of replacing it, so each command below first removes the previous run's files (or give every run its own -F name):

rm -f ~/prowler-out/insecure.*
prowler iac --scan-path /usr/share/prowler/fixtures/insecure --no-banner --no-color \
  -M csv json-ocsf html -F insecure -o ~/prowler-out > ~/prowler-insecure.log 2>&1; echo "exit code: $?"
grep -A 12 'Overview Results' ~/prowler-insecure.log
exit code: 3
Overview Results:
╭────────────────────┬────────────────────┬────────────────╮
│ 16.05% (13) Failed │ 83.95% (68) Passed │ 0.0% (0) Muted │
╰────────────────────┴────────────────────┴────────────────╯

Directory /usr/share/prowler/fixtures/insecure Scan Results (severity columns are for fails only):
╭────────────┬───────────┬───────────┬────────────┬────────┬──────────┬───────┬─────────╮
│ Provider   │ Service   │ Status    │   Critical │   High │   Medium │   Low │   Muted │
├────────────┼───────────┼───────────┼────────────┼────────┼──────────┼───────┼─────────┤
│ iac        │ terraform │ FAIL (13) │          0 │      9 │        1 │     3 │       0 │
╰────────────┴───────────┴───────────┴────────────┴────────┴──────────┴───────┴─────────╯

The OCSF JSON report holds every check with its status, severity, resource and remediation. List the failed ones:

jq -r '.[] | select(.status_code=="FAIL") | "\(.severity)  \(.metadata.event_code)  \(.finding_info.title)"' \
  ~/prowler-out/insecure.ocsf.json | sort -u
High  AWS-0028  aws_instance should activate session tokens for Instance Metadata Service.
High  AWS-0086  S3 Access block should block public ACL
High  AWS-0087  S3 Access block should block public policy
High  AWS-0091  S3 Access Block should Ignore Public ACL
High  AWS-0092  S3 Buckets not publicly accessible through ACL.
High  AWS-0093  S3 Access block should restrict public bucket to limit access
High  AWS-0107  Security groups should not allow unrestricted ingress to SSH or RDP from any IP address.
High  AWS-0131  Instance with unencrypted block device.
High  AWS-0132  S3 encryption should use Customer Managed Keys
Low  AWS-0089  S3 Bucket Logging
Low  AWS-0094  S3 buckets should each define an aws_s3_bucket_public_access_block
Low  AWS-0124  Missing description for security group rule.
Medium  AWS-0090  S3 Data should be versioned

Prowler flagging the open security group, the public S3 bucket and the IMDSv1 instance in the insecure fixture

insecure.html is a self-contained report you can open in a browser; copy it to your workstation with scp ubuntu@<instance-public-ip>:prowler-out/insecure.html ..

Step 5: Confirm a clean scan reports nothing

rm -f ~/prowler-out/clean.*
prowler iac --scan-path /usr/share/prowler/fixtures/clean --no-banner --no-color \
  -M json-ocsf -F clean -o ~/prowler-out > ~/prowler-clean.log 2>&1; echo "clean exit code: $?"
grep 'no findings' ~/prowler-clean.log
clean exit code: 0
 There are no findings in Directory /usr/share/prowler/fixtures/clean

Step 6: Scan your own infrastructure code

Point --scan-path at a directory. As an example, a Terraform RDS instance that is public and unencrypted:

mkdir -p ~/iac-demo
cat > ~/iac-demo/main.tf <<'EOF'
resource "aws_db_instance" "orders" {
  identifier          = "orders"
  engine              = "postgres"
  instance_class      = "db.t3.micro"
  allocated_storage   = 20
  publicly_accessible = true
  storage_encrypted   = false
  skip_final_snapshot = true
}
EOF
rm -f ~/prowler-out/demo.*
prowler iac --scan-path ~/iac-demo --no-banner --no-color -M json-ocsf -F demo -o ~/prowler-out > ~/prowler-demo.log 2>&1; echo "exit code: $?"
jq -r '.[] | select(.status_code=="FAIL") | "\(.severity)  \(.metadata.event_code)  \(.finding_info.title)"' ~/prowler-out/demo.ocsf.json | sort -u
exit code: 3
High  AWS-0080  RDS encryption has not been enabled at a DB Instance level.
High  AWS-0180  RDS Publicly Accessible
Low  AWS-0133  Enable Performance Insights to detect potential problems
Medium  AWS-0077  RDS Cluster and RDS instance should have backup retention longer than default 1 day
Medium  AWS-0176  RDS IAM Database Authentication Disabled
Medium  AWS-0177  RDS Deletion Protection Disabled

To scan one of your repositories, clone it and point --scan-path at it, or let Prowler clone it with --scan-repository-url https://github.com/<your-org>/<your-infra-repo>. --exclude-path skips vendored directories, and --scanners misconfig turns off the secret scanner if you only want misconfigurations.

Step 7: Assess your AWS account with a read-only instance role

prowler aws needs AWS credentials. On this image you give it a read-only IAM role attached to the instance, so no access key is ever written to disk. Interactive users on the instance (ubuntu, and root through sudo) can reach the instance metadata service over IMDSv2 and receive the role's temporary credentials; the scheduled scan and the self-test cannot (Step 9).

1. Create the role (from your workstation or CloudShell, as an administrator of the account to assess):

cat > ec2-trust.json <<'EOF'
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}
EOF
aws iam create-role --role-name prowler-readonly --assume-role-policy-document file://ec2-trust.json
aws iam attach-role-policy --role-name prowler-readonly --policy-arn arn:aws:iam::aws:policy/SecurityAudit
aws iam attach-role-policy --role-name prowler-readonly --policy-arn arn:aws:iam::aws:policy/job-function/ViewOnlyAccess
aws iam create-instance-profile --instance-profile-name prowler-readonly
aws iam add-role-to-instance-profile --instance-profile-name prowler-readonly --role-name prowler-readonly

SecurityAudit and ViewOnlyAccess are the AWS managed read-only policies Prowler documents. Neither allows any change to your account. Grant nothing broader.

2. Attach it to the instance:

aws ec2 associate-iam-instance-profile --region <region> --instance-id <instance-id> \
  --iam-instance-profile Name=prowler-readonly

3. Run an assessment on the instance. Start narrow, then widen:

prowler aws --service s3 iam                      # two services
prowler aws --severity critical high              # only critical and high findings
prowler aws --compliance cis_4.0_aws              # one compliance framework
prowler aws -o ~/prowler-out/aws-full             # everything (allow 15 to 60 minutes)

Each run writes CSV, OCSF JSON and HTML reports (named after the account ID and the time) plus one CSV per compliance framework under compliance/. To assess other accounts of an AWS Organization, add a role in each that trusts this role and use prowler aws --role arn:aws:iam::<account-id>:role/<role-name>.

You can see what Prowler will check without any credentials:

for p in aws azure gcp kubernetes; do printf "%s: " $p; prowler $p --list-checks --no-banner --no-color 2>/dev/null | grep -E "There (are|is)" | tail -1; done
prowler aws --list-compliance --no-banner --no-color 2>/dev/null | grep -cE '^ ?- '
aws: There are 672 available checks.
azure: There are 203 available checks.
gcp: There are 116 available checks.
kubernetes: There are 92 available checks.
50

Exit codes. Prowler exits 3 when at least one check failed, 0 when none did and 1 when the run itself failed, which makes prowler ... ; [ $? -eq 3 ] a usable pipeline gate. --ignore-exit-code-3 makes a run with findings exit 0.

Step 8: The scheduled scan and its reports

The scheduled scan reads /etc/prowler/scan.conf, scans SCAN_TARGET (default /srv/iac) with the iac provider and writes a dated report directory plus a latest link. Copy the example code into it, run one now and read its log:

sudo cp -r ~/iac-demo /srv/iac/ && sudo chmod -R a+rX /srv/iac
sudo systemctl start prowler-scan.service
sudo journalctl -u prowler-scan.service -n 4 --no-pager -o cat
prowler-scan: FINDINGS (iac, rc=3) total=60 fail=6 critical=0 high=2 medium=3 low=1; report /var/lib/prowler/reports/20261108T164003Z
prowler-scan.service: Deactivated successfully.
Finished prowler-scan.service - Scheduled Prowler scan (cloudimg; config /etc/prowler/scan.conf).
prowler-scan.service: Consumed 19.497s CPU time.

systemctl start waits for the scan to finish. The reports belong to the prowler user and only root can read them:

sudo ls -l /var/lib/prowler/reports/latest/
ls /var/lib/prowler/reports 2>&1 || true
total 400
drwx------ 2 prowler prowler   4096 Nov  8 16:40 compliance
-rw------- 1 prowler prowler   1924 Nov  8 16:40 prowler.log
-rw------- 1 prowler prowler  39712 Nov  8 16:40 results.csv
-rw------- 1 prowler prowler  74157 Nov  8 16:40 results.html
-rw------- 1 prowler prowler 282429 Nov  8 16:40 results.ocsf.json
ls: cannot access '/var/lib/prowler/reports': Permission denied

The scheduled scan running as the prowler user, its counts-only journal line and its 0600 reports

A scan with findings exits 3, which systemd treats as a normal result; the journal line tells you which report to open with sudo. Everything under SCAN_TARGET must be readable by the prowler user (files 0644, directories 0755). Read the HTML report on your workstation by copying it off as root first, for example sudo install -m 0644 /var/lib/prowler/reports/latest/results.html ~/ and then scp.

Step 9: What the scheduled scan can reach

systemctl show -p User -p IPAddressDeny -p ProtectSystem -p ProtectHome -p ReadWritePaths -p NoNewPrivileges -p CapabilityBoundingSet -p PrivateTmp prowler-scan.service
IPAddressDeny=::/0 169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 0.0.0.0/0
CapabilityBoundingSet=
User=prowler
ReadWritePaths=/var/lib/prowler
PrivateTmp=yes
ProtectHome=read-only
ProtectSystem=strict
NoNewPrivileges=yes

0.0.0.0/0 and ::/0 mean every address. The unit also names the instance metadata service (169.254.169.254, fd00:ec2::254) explicitly; once systemd has reloaded its configuration it may show only ::/0 0.0.0.0/0, because those two already include every other entry: the scheduled scan, the self-test and the first boot unit cannot open any network connection, so they can never receive the instance role's credentials and a malicious file in a scanned repository cannot use them to reach anything. The iac provider needs no network; its rules ship with the bundled engine. The only path the scan may write to is /var/lib/prowler.

Scheduled AWS assessments (opt in). The scheduled scan defaults to iac for exactly this reason. If you want a daily prowler aws run under the instance role instead, you must deliberately give the unit network access. Set PROWLER_PROVIDER="aws" in /etc/prowler/scan.conf, then add a drop-in that clears the deny list:

sudo systemctl edit prowler-scan.service
# add these two lines in the editor, then save:
[Service]
IPAddressDeny=

Only do this with a read-only role as in Step 7, and remember that the prowler user then can reach the metadata service like any interactive user. sudo systemctl revert prowler-scan.service restores the sandbox.

Step 10: Configuration

grep -v '^#' /etc/prowler/scan.conf | grep .
SCAN_ENABLED="yes"
PROWLER_PROVIDER="iac"
SCAN_TARGET="/srv/iac"
PROWLER_ARGS=""
KEEP_REPORTS="14"
  • SCAN_ENABLED: no makes the daily scan exit without scanning.
  • PROWLER_PROVIDER and SCAN_TARGET: what the daily scan assesses (iac scans SCAN_TARGET; see Step 9 before choosing aws).
  • PROWLER_ARGS: extra Prowler flags, for example --exclude-path /srv/iac/vendor or --severity critical high.
  • KEEP_REPORTS: how many dated report directories are kept.

The file is read at the start of every scan. Edit it with sudo nano /etc/prowler/scan.conf. To change the schedule, add a drop-in with sudo systemctl edit prowler-scan.timer (for example OnCalendar=hourly). The default is OnCalendar=daily with up to 30 minutes of random delay.

Step 11: How the scanner on this image was built

sudo prowler-verify
prowler-verify: OK prowler 5.48.0, tree 76275 c6dbcc8b84b9fac41eff4ad7ec16b598737476a84f8413cc48a06e41446d07de

prowler-verify re-hashes every file of the virtual environment's site-packages (Prowler and all its dependencies) and compares the result with the manifest recorded at build time; first boot and every scheduled scan run it first and refuse to scan if anything changed. The full record:

cat /usr/share/prowler/build-provenance/BUILD-PROVENANCE.txt
Prowler 5.48.0 on Ubuntu 24.04 (AWS), built by cloudimg
release tag 5.48.0 = commit 8a5aebb8a8c34dcecd17eba79466b94226440804 (github.com/prowler-cloud/prowler, Apache-2.0)
wheel prowler-5.48.0-py3-none-any.whl sha256 63ded14566f2314150db354282821530965d0c2f31e5b431c8452fd1d46ad2e8 (PyPI)
dependencies: hash lock for python 3.12 x86_64 linux (requirements.lock sha256 6d0ff5765f08f4a00fd1b6936d200561028b6b52e434c761776c3f389ad071f8): 233 wheels installed
  --require-hashes --only-binary=:all: --no-deps. 11 pure-Python packages with no wheel for the needed version
  (sdists.lock sha256 c9f528dfe2aaa37daa9709ecf3f9f5e198c3104d627f520fc802fe3579c75178): each sdist fetched by fixed URL (sdist-urls.txt) and sha256-checked before
  use, built by a throwaway unprivileged user in a network-less, read-only systemd sandbox with the hash-locked
  setuptools (--no-index --no-build-isolation, SOURCE_DATE_EPOCH=315532800); every built wheel matched its pinned
  sha256 (sdist-built-wheels.sha256); build user and work directory removed. pymongo 4.18.3 per upstream's own
  [tool.uv] override (the in-range 4.15.1 carries GHSA-vp6j-j7w5-5xjj and four more); pip-audit clean
pip 26.3 (sha256 f3b60b20ffaadd4415ed7957212a38ac63fa4c5fa375bd0f4a797777940fad13) used to build, then removed from the venv; trivy 0.76.2 from aquasecurity.github.io/trivy-repo (key 825AD9036F7C850E6A6FED4935B8ACA44FD9CA9F)
tree manifest: 76275 c6dbcc8b84b9fac41eff4ad7ec16b598737476a84f8413cc48a06e41446d07de

The same directory holds the lock files (requirements.lock with the sha256 of all 233 wheels, sdists.lock with the sha256 of the 11 source packages), the source URLs, the pinned sha256 of every wheel built from source, and the installed package list. The 11 source-only packages are ten small alibabacloud-* helpers used by Prowler's Alibaba Cloud provider and shodan, used by the optional Shodan check; none of them publishes a wheel for the version used here (for alibabacloud-tea, the last wheel is an older release that lacks an interface Prowler uses). pip itself was only a build tool and is not on the image.

Step 12: Access to the instance

sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin|pubkeyauthentication) '
permitrootlogin no
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no

SSH accepts only the key pair you chose at launch (/etc/ssh/sshd_config.d/10-cloudimg-key-only.conf), and no account on the image has a password. Nothing else listens on a public address.

Prowler's integrity check, the scheduled scan sandbox and the key-only SSH settings

Step 13: Components

Component Path
Prowler /opt/prowler/venv (command linked from /usr/local/bin/prowler)
IaC engine /usr/bin/trivy (Aqua Security apt repository, signing key pinned)
Scheduled scan runner /usr/local/bin/prowler-scan-run
Scan, self-test and first boot units /etc/systemd/system/prowler-scan.service, prowler-scan.timer, prowler-selftest.service, prowler-firstboot.service
Configuration /etc/prowler/scan.conf
Reports /var/lib/prowler/reports (prowler, mode 0700)
Default scan target /srv/iac
Self-test fixtures /usr/share/prowler/fixtures
Integrity check /usr/local/sbin/prowler-verify
Build provenance and licence /usr/share/prowler/build-provenance/, /usr/share/prowler/LICENSE.upstream
Key-only SSH drop-in /etc/ssh/sshd_config.d/10-cloudimg-key-only.conf

Step 14: Security recommendations

  • Allow port 22 in the security group from your own networks only.
  • Give the instance role SecurityAudit and ViewOnlyAccess and nothing more. Prowler never needs write access.
  • Treat every report as sensitive: it is a map of where your account is weak. Keep reports under /var/lib/prowler or in your home directory, and copy them only to systems you trust.
  • Keep the operating system patched; unattended security upgrades are on (including trivy from its vendor repository), and sudo apt-get update && sudo apt-get upgrade applies everything else. New Prowler releases, with their new checks, arrive as new versions of this listing.

Support and licensing

Prowler is open source under the Apache License 2.0 (a copy is at /usr/share/prowler/LICENSE.upstream). This image is not affiliated with or endorsed by the Prowler project. There is no per user or per account licence fee. The AMI price covers cloudimg's build, hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA