feat(deployment): Serverless multi account Prowler with SecurityHub Integration (#1113)

This commit is contained in:
Milton Torasso
2022-05-03 08:41:56 -03:00
committed by GitHub
parent de77a33341
commit 13c96a80db
7 changed files with 532 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
export ROLE=ProwlerXA-Role
export PARALLEL_ACCOUNTS=1
export REGION=us-east-1

View File

@@ -0,0 +1,45 @@
# Build command
# docker build --platform=linux/amd64 --no-cache -t prowler:latest .
FROM public.ecr.aws/amazonlinux/amazonlinux:2022
ARG PROWLERVER=2.9.0
ARG USERNAME=prowler
ARG USERID=34000
# Install Dependencies
RUN \
dnf update -y && \
dnf install -y bash file findutils git jq python3 python3-pip \
python3-setuptools python3-wheel shadow-utils tar unzip which && \
dnf remove -y awscli && \
dnf clean all && \
useradd -l -s /bin/sh -U -u ${USERID} ${USERNAME} && \
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
unzip awscliv2.zip && \
./aws/install && \
pip3 install --no-cache-dir --upgrade pip && \
pip3 install --no-cache-dir "git+https://github.com/ibm/detect-secrets.git@master#egg=detect-secrets" && \
rm -rf aws awscliv2.zip /var/cache/dnf
# Place script and env vars
COPY .awsvariables run-prowler-securityhub.sh /
# Installs prowler and change permissions
RUN \
curl -L "https://github.com/prowler-cloud/prowler/archive/refs/tags/${PROWLERVER}.tar.gz" -o "prowler.tar.gz" && \
tar xvzf prowler.tar.gz && \
rm -f prowler.tar.gz && \
mv prowler-${PROWLERVER} prowler && \
chown ${USERNAME}:${USERNAME} /run-prowler-securityhub.sh && \
chmod 500 /run-prowler-securityhub.sh && \
chown ${USERNAME}:${USERNAME} /.awsvariables && \
chmod 400 /.awsvariables && \
chown ${USERNAME}:${USERNAME} -R /prowler && \
chmod +x /prowler/prowler
# Drop to user
USER ${USERNAME}
# Run script
ENTRYPOINT ["/run-prowler-securityhub.sh"]

View File

@@ -0,0 +1,94 @@
# Example Solution: Serverless Organizational Prowler Deployment with SecurityHub
Deploys [Prowler](https://github.com/prowler-cloud/prowler) with AWS Fargate to assess all Accounts in an AWS Organization on a schedule, and sends the results to Security Hub.
## Context
Originally based on [org-multi-account](https://github.com/prowler-cloud/prowler/tree/master/util/org-multi-account), but changed in the following ways:
- No HTML reports and no S3 buckets
- Findings sent directly to Security Hub using the native integration
- AWS Fargate Task with EventBridge Rule instead of EC2 instance with cronjob
- Based on amazonlinux:2022 to leverage "wait -n" for improved parallelization as new jobs are launched as one finishes
## Architecture Explanation
The solution is designed to be very simple. Prowler is run via an ECS Task definition that launches a single Fargate container. This Task Definition is executed on a schedule using an EventBridge Rule.
## CloudFormation Templates
### CF-Prowler-IAM.yml
Creates the following IAM Roles:
1. **ECSExecutionRole**: Required for the Task Definition to be able to fetch the container image from ECR and launch the container.
2. **ProwlerTaskRole**: Role that the container itself runs with. It allows it to assume the ProwlerCrossAccountRole.
3. **ECSEventRoleName**: Required for the EventBridge Rule to execute the Task Definition.
### CF-Prowler-ECS.yml
Creates the following resources:
1. **ProwlerECSCluster**: Cluster to be used to execute the Task Definition.
2. **ProwlerECSCloudWatchLogsGroup**: Log group for the Prowler container logs. This is required because it's the only log driver supported by Fargate. The Prowler executable logs are suppressed to prevent unnecessary logs, but error logs are kept for debugging.
3. **ProwlerECSTaskDefinition**: Task Definition for the Fargate container. CPU and memory can be increased as needed. In my experience, 1 CPU per parallel Prowler job is ideal, but further performance testing may be required to find the optimal configuration for a specific organization. Enabling container insights helps a lot with this.
4. **ProwlerSecurityGroup**: Security Group for the container. It only allows TCP 443 outbound, as it is the only port needed for awscli.
5. **ProwlerTaskScheduler**: EventBridge Rule that schedules the execution of the Task Definition. The cron expression is specified as a CloudFormation template parameter.
### CF-Prowler-CrossAccountRole.yml
Creates the cross account IAM Role required for Prowler to run. Deploy it as StackSet in every account in the AWS Organization.
## Docker Container
### Dockerfile
The Dockerfile does the following:
1. Uses amazonlinux:2022 as a base.
2. Downloads required dependencies.
3. Copies the .awsvariables and run-prowler-securityhub.sh files into the root.
4. Downloads the specified version of Prowler as recommended in the release notes.
5. Assigns permissions to a lower privileged user and then drops to it.
6. Runs the script.
### .awsvariables
The .awsvariables file is used to pass required configuration to the script:
1. **ROLE**: The cross account Role to be assumed for the Prowler assessments.
2. **PARALLEL_ACCOUNTS**: The number of accounts to be scanned in parallel.
3. **REGION**: Region where Prowler will run its assessments.
### run-prowler-securityhub.sh
The script gets the list of accounts in AWS Organizations, and then executes Prowler as a job for each account, up to PARALLEL_ACCOUNT accounts at the same time.
The logs that are generated and sent to Cloudwatch are error logs, and assessment start and finish logs.
## Instructions
1. Create a Private ECR Repository in the account that will host the Prowler container. The Audit account is recommended, but any account can be used.
2. Configure the .awsvariables file. Note the ROLE name chosen as it will be the CrossAccountRole.
3. Follow the steps from "View Push Commands" to build and upload the container image. You need to have Docker and AWS CLI installed, and use the cli to login to the account first. After upload note the Image URI, as it is required for the CF-Prowler-ECS template.
4. Make sure SecurityHub is enabled in every account in AWS Organizations, and that the SecurityHub integration is enabled as explained in [Prowler - Security Hub Integration](https://github.com/prowler-cloud/prowler#security-hub-integration)
5. Deploy **CF-Prowler-CrossAccountRole.yml** in the Master Account as a single stack. You will have to choose the CrossAccountRole name (ProwlerXA-Role by default) and the ProwlerTaskRoleName (ProwlerECSTask-Role by default)
6. Deploy **CF-Prowler-CrossAccountRole.yml** in every Member Account as a StackSet. Choose the same CrossAccountName and ProwlerTaskRoleName as the previous step.
7. Deploy **CF-Prowler-IAM.yml** in the account that will host the Prowler container (the same from step 1). The following template parameters must be provided:
- **ProwlerCrossAccountRoleName**: Name of the from CF-Prowler-CrossAccountRole (default ProwlerXA-Role).
- **ECSExecutionRoleName**: Name for the ECS Task Execution Role (default ECSTaskExecution-Role).
- **ProwlerTaskRoleName**: Name for the ECS Prowler Task Role (default ProwlerECSTask-Role).
- **ECSEventRoleName**: Name for the Eventbridge Task Role (default ProwlerEvents-Role).
8. Deploy **CF-Prowler-ECS.yml** in the account that will host the Prowler container (the same from step 1). The following template parameters must be provided:
- **ProwlerClusterName**: Name for the ECS Cluster (default ProwlerCluster)
- **ProwlerContainerName**: Name for the Prowler container (default prowler)
- **ProwlerContainerInfo**: ECR URI from step 1.
- **ProwlerECSLogGroupName**: CloudWatch Log Group name (default /aws/ecs/SecurityHub-Prowler)
- **SecurityGroupVPCId**: VPC ID for the VPC where the container will run.
- **ProwlerScheduledSubnet1 and 2**: Subnets IDs from the VPC specified. Choose private subnets if possible.
- **ECSExecutionRole**: ECS Execution Task Role ARN from CF-Prowler-IAM outputs.
- **ProwlerTaskRole**: Prowler ECS Task Role ARN from CF-Prowler-IAM outputs.
- **ECSEventRole**: Eventbridge Task Role ARN from CF-Prowler-IAM outputs.
- **CronExpression**: Valid Cron Expression for the scheduling of the Task Definition.
9. Verify that Prowler runs correctly by checking the CloudWatch logs after the scheduled task is executed.
---
## Troubleshooting
If you permission find errors in the CloudWatch logs, the culprit might be a [Service Control Policy (SCP)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html). You will need to exclude the Prowler Cross Account Role from those SCPs.
---
## Upgrading Prowler
Prowler version is controlled by the PROWLERVER argument in the Dockerfile, change it to the desired version and follow the ECR Push Commands to update the container image.
Old images can be deleted from the ECR Repository after the new image is confirmed to work. They will show as "untagged" as only one image can hold the "latest" tag.

View File

@@ -0,0 +1,86 @@
#!/bin/bash
# Run Prowler against All AWS Accounts in an AWS Organization
# Change Directory (rest of the script, assumes you're in the root directory)
cd / || exit
# Show Prowler Version
./prowler/prowler -V
# Source .awsvariables
# shellcheck disable=SC1091
source .awsvariables
# Get Values from Environment Variables
echo "ROLE: $ROLE"
echo "PARALLEL_ACCOUNTS: $PARALLEL_ACCOUNTS"
echo "REGION: $REGION"
# Function to unset AWS Profile Variables
unset_aws() {
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
}
unset_aws
# Find THIS Account AWS Number
CALLER_ARN=$(aws sts get-caller-identity --output text --query "Arn")
PARTITION=$(echo "$CALLER_ARN" | cut -d: -f2)
THISACCOUNT=$(echo "$CALLER_ARN" | cut -d: -f5)
echo "THISACCOUNT: $THISACCOUNT"
echo "PARTITION: $PARTITION"
# Function to Assume Role to THIS Account & Create Session
this_account_session() {
unset_aws
role_credentials=$(aws sts assume-role --role-arn arn:"$PARTITION":iam::"$THISACCOUNT":role/"$ROLE" --role-session-name ProwlerRun --output json)
AWS_ACCESS_KEY_ID=$(echo "$role_credentials" | jq -r .Credentials.AccessKeyId)
AWS_SECRET_ACCESS_KEY=$(echo "$role_credentials" | jq -r .Credentials.SecretAccessKey)
AWS_SESSION_TOKEN=$(echo "$role_credentials" | jq -r .Credentials.SessionToken)
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
}
# Find AWS Master Account
this_account_session
AWSMASTER=$(aws organizations describe-organization --query Organization.MasterAccountId --output text)
echo "AWSMASTER: $AWSMASTER"
# Function to Assume Role to Master Account & Create Session
master_account_session() {
unset_aws
role_credentials=$(aws sts assume-role --role-arn arn:"$PARTITION":iam::"$AWSMASTER":role/"$ROLE" --role-session-name ProwlerRun --output json)
AWS_ACCESS_KEY_ID=$(echo "$role_credentials" | jq -r .Credentials.AccessKeyId)
AWS_SECRET_ACCESS_KEY=$(echo "$role_credentials" | jq -r .Credentials.SecretAccessKey)
AWS_SESSION_TOKEN=$(echo "$role_credentials" | jq -r .Credentials.SessionToken)
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
}
# Lookup All Accounts in AWS Organization
master_account_session
ACCOUNTS_IN_ORGS=$(aws organizations list-accounts --query Accounts[*].Id --output text)
# Run Prowler against Accounts in AWS Organization
echo "AWS Accounts in Organization"
echo "$ACCOUNTS_IN_ORGS"
for accountId in $ACCOUNTS_IN_ORGS; do
# shellcheck disable=SC2015
test "$(jobs | wc -l)" -ge $PARALLEL_ACCOUNTS && wait -n || true
{
START_TIME=$SECONDS
# Unset AWS Profile Variables
unset_aws
# Run Prowler
echo -e "Assessing AWS Account: $accountId, using Role: $ROLE on $(date)"
# Pipe stdout to /dev/null to reduce unnecessary Cloudwatch logs
./prowler/prowler -R "$ROLE" -A "$accountId" -M json-asff -q -S -f "$REGION" > /dev/null
TOTAL_SEC=$((SECONDS - START_TIME))
printf "Completed AWS Account: $accountId in %02dh:%02dm:%02ds" $((TOTAL_SEC / 3600)) $((TOTAL_SEC % 3600 / 60)) $((TOTAL_SEC % 60))
echo ""
} &
done
# Wait for All Prowler Processes to finish
wait
echo "Prowler Assessments Completed against All Accounts in AWS Organization"
# Unset AWS Profile Variables
unset_aws

View File

@@ -0,0 +1,97 @@
AWSTemplateFormatVersion: 2010-09-09
Description: Create the Cross-Account IAM Prowler Role
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: ECS Settings
Parameters:
- ProwlerEcsAccount
- ProwlerTaskRoleName
- Label:
default: CrossAccount Role
Parameters:
- ProwlerCrossAccountRole
Parameters:
ProwlerEcsAccount:
Type: String
Description: Enter AWS Account Number where Prowler ECS Task will reside.
AllowedPattern: ^\d{12}$
ConstraintDescription: An AWS Account Number must be a 12 digit numeric string.
ProwlerTaskRoleName:
Type: String
Description: Enter Instance Role that will be given to the Prowler ECS Instance (needed to grant sts:AssumeRole rights).
AllowedPattern: ^[\w+=,.@-]{1,64}$
ConstraintDescription: Max 64 alphanumeric characters. Also special characters supported [+, =, ., @, -]
Default: ProwlerECSTask-Role
ProwlerCrossAccountRole:
Type: String
Description: Enter Name for CrossAccount Role to be created for Prowler to assess all Accounts in the AWS Organization.
AllowedPattern: ^[\w+=,.@-]{1,64}$
ConstraintDescription: Max 64 alphanumeric characters. Also special characters supported [+, =, ., @, -]
Default: ProwlerXA-Role
Resources:
ProwlerRole:
Type: AWS::IAM::Role
Properties:
Description: Provides Prowler ECS tasks permissions to assess security of Accounts in AWS Organization
RoleName: !Ref ProwlerCrossAccountRole
Tags:
- Key: App
Value: Prowler
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
AWS:
- !Sub arn:${AWS::Partition}:iam::${ProwlerEcsAccount}:role/${ProwlerTaskRoleName}
Action:
- sts:AssumeRole
ManagedPolicyArns:
- !Sub arn:${AWS::Partition}:iam::aws:policy/SecurityAudit
- !Sub arn:${AWS::Partition}:iam::aws:policy/job-function/ViewOnlyAccess
Policies:
- PolicyName: Prowler-Additions-Policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: AllowMoreReadForProwler
Effect: Allow
Resource: "*"
Action:
- ds:ListAuthorizedApplications
- ec2:GetEbsEncryptionByDefault
- ecr:Describe*
- elasticfilesystem:DescribeBackupPolicy
- glue:GetConnections
- glue:GetSecurityConfiguration
- glue:SearchTables
- lambda:GetFunction
- s3:GetAccountPublicAccessBlock
- shield:DescribeProtection
- shield:GetSubscriptionState
- ssm:GetDocument
- support:Describe*
- tag:GetTagKeys
- PolicyName: Prowler-Security-Hub
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: AllowProwlerSecurityHub
Effect: Allow
Resource: "*"
Action:
- securityhub:BatchImportFindings
- securityhub:GetFindings
Metadata:
cfn_nag:
rules_to_suppress:
- id: W11
reason: "Prowler requires these rights to perform its Security Assessment."
- id: W28
reason: "Using a defined Role Name."
Outputs:
ProwlerCrossAccountRole:
Description: CrossAccount Role to be used by Prowler to assess AWS Accounts in the AWS Organization.
Value: !Ref ProwlerCrossAccountRole

View File

@@ -0,0 +1,102 @@
AWSTemplateFormatVersion: 2010-09-09
Description: This Template will create the infrastructure for Prowler with ECS Fargate
Parameters:
ProwlerClusterName:
Type: String
Description: Name of the ECS Cluster that the Prowler Fargate Task will run in
Default: ProwlerCluster
ProwlerContainerName:
Type: String
Description: Name of the Prowler Container Definition within the ECS Task
Default: prowler
ProwlerContainerInfo:
Type: String
Description: ECR URI of the Prowler container
ProwlerECSLogGroupName:
Type: String
Description: Name for the log group to be created
Default: /aws/ecs/SecurityHub-Prowler
SecurityGroupVPCId:
Type: String
Description: VPC Id for the Security Group to be created
ProwlerScheduledSubnet1:
Type: String
Description: Subnet Id in which Prowler can be scheduled to Run
ProwlerScheduledSubnet2:
Type: String
Description: A secondary Subnet Id in which Prowler can be scheduled to Run
ECSExecutionRole:
Type: String
Description: ECS Execution Task Role ARN.
ProwlerTaskRole:
Type: String
Description: Prowler ECS Task Role ARN.
ECSEventRole:
Type: String
Description: Eventbridge Task Role ARN.
CronExpression:
Type: String
Description: Cron schedule for the event rule.
Default: cron(0 23 * * ? *)
Resources:
ProwlerECSCloudWatchLogsGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Ref ProwlerECSLogGroupName
RetentionInDays: 90
ProwlerECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Ref ProwlerClusterName
ProwlerECSTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
ContainerDefinitions:
- Image: !Ref ProwlerContainerInfo
Name: !Ref ProwlerContainerName
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref ProwlerECSCloudWatchLogsGroup
awslogs-region: !Ref 'AWS::Region'
awslogs-stream-prefix: ecs
Cpu: 1024
ExecutionRoleArn: !Ref ECSExecutionRole
Memory: 2048
NetworkMode: awsvpc
TaskRoleArn: !Ref ProwlerTaskRole
Family: SecurityHubProwlerTask
RequiresCompatibilities:
- FARGATE
ProwlerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTPS Out - Prowler
VpcId: !Ref SecurityGroupVPCId
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
ProwlerTaskScheduler:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: !Ref CronExpression
State: ENABLED
Targets:
- Arn: !GetAtt ProwlerECSCluster.Arn
RoleArn: !Ref ECSEventRole
Id: prowlerTaskScheduler
EcsParameters:
TaskDefinitionArn: !Ref ProwlerECSTaskDefinition
TaskCount: 1
LaunchType: FARGATE
PlatformVersion: 'LATEST'
NetworkConfiguration:
AwsVpcConfiguration:
AssignPublicIp: DISABLED
SecurityGroups:
- !Ref ProwlerSecurityGroup
Subnets:
- !Ref ProwlerScheduledSubnet1
- !Ref ProwlerScheduledSubnet2

View File

@@ -0,0 +1,105 @@
AWSTemplateFormatVersion: 2010-09-09
Description: This Template will create the IAM Roles needed for the Prowler infrastructure
Parameters:
ProwlerCrossAccountRoleName:
Type: String
Description: Name of the cross account Prowler IAM Role
AllowedPattern: ^[\w+=,.@-]{1,64}$
ConstraintDescription: Max 64 alphanumeric characters. Also special characters supported [+, =, ., @, -]
Default: ProwlerXA-Role
ECSExecutionRoleName:
Type: String
Description: Name for the ECS Task Execution Role
AllowedPattern: ^[\w+=,.@-]{1,64}$
ConstraintDescription: Max 64 alphanumeric characters. Also special characters supported [+, =, ., @, -]
Default: ECSTaskExecution-Role
ProwlerTaskRoleName:
Type: String
Description: Name for the ECS Prowler Task Role
AllowedPattern: ^[\w+=,.@-]{1,64}$
ConstraintDescription: Max 64 alphanumeric characters. Also special characters supported [+, =, ., @, -]
Default: ProwlerECSTask-Role
ECSEventRoleName:
Type: String
Description: Name for the Eventbridge Task Role
AllowedPattern: ^[\w+=,.@-]{1,64}$
ConstraintDescription: Max 64 alphanumeric characters. Also special characters supported [+, =, ., @, -]
Default: ProwlerEvents-Role
Resources:
ECSExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref ECSExecutionRoleName
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: ECSExecutionTrust
Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ProwlerTaskRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref ProwlerTaskRoleName
Policies:
- PolicyName: ProwlerAssumeRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowProwlerAssumeRole
Effect: Allow
Action: sts:AssumeRole
Resource:
- !Sub arn:aws:iam::*:role/${ProwlerCrossAccountRoleName}
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: ECSExecutionTrust
Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ECSEventRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref ECSEventRoleName
Policies:
- PolicyName: AllowProwlerEventsECS
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ecs:RunTask
Resource:
- "*"
Sid: EventRunECS
- Effect: Allow
Action: iam:PassRole
Resource:
- "*"
Sid: EventPassRole
Condition:
StringLike:
iam:PassedToService: ecs-tasks.amazonaws.com
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: EventsECSExecutionTrust
Effect: Allow
Principal:
Service: events.amazonaws.com
Action: sts:AssumeRole
Outputs:
ECSExecutionRoleARN:
Description: ARN of the ECS Task Execution Role
Value: !GetAtt ECSExecutionRole.Arn
ProwlerTaskRoleARN:
Description: ARN of the ECS Prowler Task Role
Value: !GetAtt ProwlerTaskRole.Arn
ECSEventRoleARN:
Description: ARN of the Eventbridge Task Role
Value: !GetAtt ECSEventRole.Arn