Setting the Workflow
The first step is to decide what events will trigger this workflow. In this case, we want to trigger this workflow when a push is committed to the main branch (whether direct push or as a result of a merge). In addition we need to give this Workflow the proper permissions:
name: lacework-iac-scan-push-main
on:
push:
branches:
- 'main'
workflow_dispatch:
permissions:
id-token: write
contents: read
security-events: write
actions: read
Job 1: Testing the tools
This serves as a straightforward test to verify the presence of necessary tools (like AWS, Terraform, etc.) within the container image we've selected, such as 'ubuntu-latest'
Initialize:
runs-on: ubuntu-latest
steps:
- name: Test Tools
run: |
aws --version
terraform --version
Job 2: Running the scanner
This is the Job that we are most interested in. We will use ubuntu-latest and we will Checkout the source code. Which is in this case Terraform HCL files.
Lacework-Scanner:
needs: Initialize
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
The next step is to run the actual scanner. We will need to configure our environment with Lacework keys and we also need to set those as repository secrets in Github repo. Please refer to this link for details on how to generate those keys and set them as secret values: https://docs.lacework.net/iac/restricted/github-actions
Once you have the keys, we will need to set then as ENV variables inside the Workflow
- name: Lacework IaC Scan
id: laceworkscan
env:
LW_ACCOUNT: ${{ secrets.LW_ACCOUNT }}
LW_API_KEY: ${{ secrets.API_KEY }}
LW_API_SECRET: ${{ secrets.API_SECRET_TOKEN }}
Next, we will need to use those secrets as well as additional configuration to execute the scanner. I have added notes that explain what each line does.
run: | # Setup IAC_REPORTS_DIR="/tmp/iac_reports" env | grep "GITHUB_\|LW_\|CI_" > env.list # Set Lacework and other secrets echo "SCAN_COMMAND=tf-scan" >> env.list # Configure the scanner to run Terraform Scan echo "WORKSPACE=src" >> env.list echo "EXIT_FLAG=critical" >> env.list # Cause the pipline to fail if any Critical violations are discovered echo "JUNIT_OUTPUT_FILE=/tmp/lacework-report.junit.xml" >> env.list echo "JSON_OUTPUT_FILE=/tmp/lacework-report.json" >> env.list # Run the Scan docker run --env-file env.list -v "$(pwd):/app/src" lacework/codesec-iac:latest # Copy the report outside lacework/codesec-iac:latest container CONTAINER_ID=$(docker ps -a |grep lacework |head -n1 |cut -d ' ' -f1) # Extracts the latest container ID (Inside a Workflow there should be only one anyways) mkdir $IAC_REPORTS_DIR docker container cp "$CONTAINER_ID:/tmp/lacework-report.json" $IAC_REPORTS_DIR docker container cp "$CONTAINER_ID:/tmp/lacework-report.junit.xml" $IAC_REPORTS_DIR # Set GITHUB_ENV RESULTS_JSON="$IAC_REPORTS_DIR/lacework-report.json" echo "RESULTS_JSON=$RESULTS_JSON" >> "$GITHUB_ENV"
Once the scan is done, Lacework Scanner will upload the results to your Lacework console. But the above configuration will also produce two types of report files, JUNIT and JSON. This is a common way for engineers to find these results without having to go to the Lacework Console. These reports can also be published inside the Workflow or using third party tools
To Upload/Store the results:
- name: 'Upload Artifacts'
uses: actions/upload-artifact@v3
with:
name: Lacework Junit Report
path: /tmp/iac_reports/*
There are many Github Actions that can help us to publish report results. However, in this example, we have created our own from scratch:
- name: Publish IaC Scan Results
run: |
# Create results summary
TESTS=$(cat $RESULTS_JSON |jq -r '.[].findings | .[].pass'| wc -l)
echo "TESTS: $TESTS"
TESTS_PASSED=$(cat $RESULTS_JSON |jq -r '.[].findings | .[].pass' | grep true | wc -l)
echo "TESTS_PASSED: $TESTS_PASSED"
TESTS_FAILED=$(cat $RESULTS_JSON |jq -r '.[].findings | .[].pass' | grep false| wc -l)
echo "TESTS_FAILED: $TESTS_FAILED"
# Set GITHUB_ENV
echo "TESTS=$TESTS" >> "$GITHUB_ENV"
echo "TESTS_PASSED=$TESTS_PASSED" >> "$GITHUB_ENV"
echo "TESTS_FAILED=$TESTS_FAILED" >> "$GITHUB_ENV"
# Adding a Step Summary to the workflow using $GITHUB_STEP_SUMMARY
echo "<table><tr><th><th>Tests</th><th>Passed ✅</th><th>Failed ❌</th></tr><tr><td>Lacework TF-SCAN JUnit Results</td><td>${TESTS} Ran</td><td>${TESTS_PASSED} Passed</td><td>${TESTS_FAILED} Failed</td></tr></table>" >> $GITHUB_STEP_SUMMARY
In the example above, we are creating a summary of the report based on the JSON output,TEST, TEST_PASSED, and TESTS_FAILED are the three values that we need to extract and present as a summary.
The actual summary is designed and built by writing to the $GITHUB_STEPS_SUMMARY variable.
The step summary would look like this:
Job 3: Terraform Deployment
In the final job, we will run Terraform Plan and Apply to deploy our infrastructure. Notice that we are using OpenID Connect in Amazon Web Services to have the Workflow to assume a role into our target AWS account. More on this in the references below.
Terraform-Deploy:
runs-on: ubuntu-latest
needs: Lacework-Scanner
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{secrets.AWS_ACCOUNT}}:role/github-actions
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
- name: Sts GetCallerIdentity
run: |
aws sts get-caller-identity
- name: Terraform Plan
run: |
terraform init
terraform plan -out tf.plan
- name: Terraform Apply
run: |
terraform apply --auto-approve tf.plan
Finally, putting the entire Workflow together, it should look something like this:
name: lacework-iac-scan-push-main
on:
push:
branches:
- 'main'
workflow_dispatch:
permissions:
id-token: write
contents: read
security-events: write
actions: read
jobs:
Initialize:
runs-on: ubuntu-latest
steps:
- name: Test Tools
run: |
aws --version
terraform --version
Lacework-Scanner:
needs: Initialize
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Lacework IaC Scan
id: laceworkscan
env:
LW_ACCOUNT: ${{ secrets.LW_ACCOUNT }}
LW_API_KEY: ${{ secrets.API_KEY }}
LW_API_SECRET: ${{ secrets.API_SECRET_TOKEN }}
run: |
# Setup
IAC_REPORTS_DIR="/tmp/iac_reports"
env | grep "GITHUB_\|LW_\|CI_" > env.list # Set Lacework and other secrets
echo "SCAN_COMMAND=tf-scan" >> env.list # Configure the scanner to run Terraform Scan
echo "WORKSPACE=src" >> env.list
echo "EXIT_FLAG=critical" >> env.list # Cause the pipline to fail if any Critical violations are discovered
echo "JUNIT_OUTPUT_FILE=/tmp/lacework-report.junit.xml" >> env.list
echo "JSON_OUTPUT_FILE=/tmp/lacework-report.json" >> env.list
# Run the Scan
docker run --env-file env.list -v "$(pwd):/app/src" lacework/codesec-iac:latest
# Copy the report outside lacework/codesec-iac:latest container
CONTAINER_ID=$(docker ps -a |grep lacework |head -n1 |cut -d ' ' -f1) # Extracts the latest container ID (Inside a Workflow there should be only one anyways)
mkdir $IAC_REPORTS_DIR
docker container cp "$CONTAINER_ID:/tmp/lacework-report.json" $IAC_REPORTS_DIR
docker container cp "$CONTAINER_ID:/tmp/lacework-report.junit.xml" $IAC_REPORTS_DIR
# Set GITHUB_ENV
RESULTS_JSON="$IAC_REPORTS_DIR/lacework-report.json"
echo "RESULTS_JSON=$RESULTS_JSON" >> "$GITHUB_ENV"
- name: 'Upload Artifacts'
uses: actions/upload-artifact@v3
with:
name: Lacework Junit Report
path: /tmp/iac_reports/*
- name: Publish IaC Scan Results
run: |
# Create results summary
TESTS=$(cat $RESULTS_JSON |jq -r '.[].findings | .[].pass'| wc -l)
echo "TESTS: $TESTS"
TESTS_PASSED=$(cat $RESULTS_JSON |jq -r '.[].findings | .[].pass' | grep true | wc -l)
echo "TESTS_PASSED: $TESTS_PASSED"
TESTS_FAILED=$(cat $RESULTS_JSON |jq -r '.[].findings | .[].pass' | grep false| wc -l)
echo "TESTS_FAILED: $TESTS_FAILED"
# Set GITHUB_ENV
echo "TESTS=$TESTS" >> "$GITHUB_ENV"
echo "TESTS_PASSED=$TESTS_PASSED" >> "$GITHUB_ENV"
echo "TESTS_FAILED=$TESTS_FAILED" >> "$GITHUB_ENV"
# Adding a Step Summary to the workflow using $GITHUB_STEP_SUMMARY
echo "<table><tr><th><th>Tests</th><th>Passed ✅</th><th>Failed ❌</th></tr><tr><td>Lacework TF-SCAN JUnit Results</td><td>${TESTS} Ran</td><td>${TESTS_PASSED} Passed</td><td>${TESTS_FAILED} Failed</td></tr></table>" >> $GITHUB_STEP_SUMMARY
Terraform-Deploy:
runs-on: ubuntu-latest
needs: Lacework-Scanner
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{secrets.AWS_ACCOUNT}}:role/github-actions
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
- name: Sts GetCallerIdentity
run: |
aws sts get-caller-identity
- name: Terraform Plan
run: |
terraform init
terraform plan -out tf.plan
- name: Terraform Apply
run: |
terraform apply --auto-approve tf.plan
Once the Workflow completes successfully, it should look something like this:
References:
|