Recently, I’ve been playing with GitLab, mostly because I’ve never used it so much, and at the same time, I stumbled upon tfsec, which is a really cool Terraform scanner! tfsec uses static analysis to catch any potential misconfigurations. So I thought to myself that it would be nice to combine those two together and include tfsec into my Merge Request pipeline.
About tfsec
tfsec can analyze your terraform configurations and generate reports. It has a lot of cool features:
- it can scan local and remote modules
- evaluates HCL expressions, literal values and Terraform functions!
- support for multiple formats when it comes to reports (lovely, JSON, SARIF, CSV, CheckStyle, JUnit, text, Gif.)
Gitlab CI setup
We want to run our tfsec
scans only when a Merge Request is opened, scan terraform files located on the feature branch, and put a comment with our scanning results.
Before we start writing our configuration, we need to generate our Access Token
and add it as an environment variable
in our project configuration on GitLab.
To do it, we need to go into our project and then Settings—>Access Tokens
We click Add new token
, we select api
scope for our token, role for it (I think reporter
makes the most sense in our case) and we are ready to go.
Save your token afterwards as you won’t be able to access it again!
Now when we have our token, we need to go to our CI/CD configuration. Similar story as with Access Token
, we go into our project and then Settings—>CI/CD.
There will be multiple settings for us to choose from, but we are interested in Variables
. Expand it, and click Add variable
.
We paste our token there and we set up key for it. Remember to turn on the flag Mask variable
so our token value is not visible in the pipeline.
When we are done with that, we are ready to write our tfsec commenter pipeline
! :D
Gitlab CI config
In the root directory of our terraform repository we need a .gitlab-ci.yml
file which is used for our pipeline configuration.
As we want to run our pipeline only when Merge Request is opened, we begin our configuration with:
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
We are going to have 2 stages in our pipeline: scan
and comment
. So…
stages:
- scan
- comment
Our first stage is a scan
tfsec-scan:
stage: scan
image:
name: aquasec/tfsec-ci:latest
script:
- tfsec . -f markdown --out results.md || true
artifacts:
paths:
- "results.md"
We are using tfsec-ci image and we output scan results to results.md
.
And our commenting stage
comment_on_merge_request:
stage: comment
image: alpine:latest
script: |
apk add --update curl jq
results=$(cat results.md)
json_payload=$(jq -n --arg body "$results" '{body: $body}')
curl --location --request POST "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" --header "PRIVATE-TOKEN: $TFSEC" --header "Content-Type: application/json" --data "$json_payload"
needs:
- job: tfsec-scan
artifacts: true
- we save our results to a variable
- we generate a payload using jq, we use content of the
results
variable as the value for the body. there is a chance you don’t have to do it this way if you use different format of the reports but I haven’t tested it - we make an API request to Gitlab API using our token. variables
CI_PROJECT_ID
andCI_MERGE_REQUEST_IID
are populated by Gitlab itself, we don’t need to set them up. - we make sure we have access to artifacts from previous stage
Let’s see how it works
I’ve made a Merge Request with some dubious configuration of AWS RDS and… It works! Our pipeline added a scan result to our Merge Request :D