In this article I show how to create a container for GitHub CodeSpaces for some Azure + Terraform work. Usually, azterraform from Microsoft is my go-to, but in this instance I needed a PowerShell base, and the addition of the GitHub CLI client.
Obviously, it has to be built in CI, and let’s add some security love with Trivy too!
You can re-use this same process to make your own CodeSpaces container (or any container, for that matter), with the tools that work for your use cases.
Updating the GitHub PR with Trivy results
Here’s the CI pipeline in a nutshell:
- Build the docker image & scan it for security smells.
- Publish the report summary table as a comment on the PR.
- Create a link to the full Trivy report on the Actions step summary page.
Here’s some pictures from the end state:
The Trivy report table posted as a PR comment - and a link to the full reportWe're going to use the Actions 'summary' page for the full reportA taster from the full outputBuild the workflow
The full workflow is available in /.github/workflows/docker-pr-build-and-scan.yml.
Starting at the top:
1
2
3
4
5
6
7
8
| name: Docker PR Build and Scan
on :
pull_request :
branches :
- main
permissions:
contents: read
pull-requests: write
|
With the fine-grained access model, we need to make sure the permissions block allows write access to the PR, so we can add comments
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| jobs :
build-and-scan:
runs-on: ubuntu-latest
steps :
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses : docker/setup-buildx-action@v2
- name: Build Docker image
run: |
docker build -t pr-az-pwsh-terraform: latest
- name: Scan Docker image with Trivy and generate summary
uses: aquasecurity/[email protected]
with:
image-ref : 'pr-az-pwsh-terraform: latest'
format: 'table'
output : 'trivy-results . txt '
severity: 'CRITICAL, HIGH, MEDIUM'
|
The build process is pretty standard - in the final step we’re using Aqua Security’s GitHub Action for Trivy to run the scan.
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
| - name: Post Trivy scan results to PR
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const fullResults = fs.readFileSync('trivy-results.txt', 'utf8');
let summarySection = '';
const summaryMatch = fullResults.match(/Report Summary\n\n(.*?)\nLegend:/s);
if (summaryMatch && summaryMatch[1]) {
summarySection = summaryMatch[1].trim();
} else {
summarySection = "No vulnerability summary found. Please check the full report in the Actions tab.";
}
const header = '## Trivy Security Scan Summary\n\n';
const footer = `\n\n---\n\n*For detailed results, please [view the actions output](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}).*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: header + '```\n' + summarySection + '\n```' + footer
});
|
This will take the Trivy report and extract just the report table at the top, and then add this to the PR as a comment
1
2
3
4
5
6
7
8
9
10
11
12
13
| - name: Publish Trivy to action log
run: |
if [[ -s trivy-results.txt ]]; then
{
echo "### Trivy Scan Results"
echo "<details><summary>Click to expand</summary>"
echo ""
echo '```terraform'
cat trivy-results.txt
echo '```'
echo "</details>"
} >> $GITHUB_STEP_SUMMARY
fi
|
Finally - we use the “well known” variable GITHUB_STEP_SUMMARY
to post the contents to the Actions log.
That’s the end of the PR flow.
Publishing to Docker Hub
A separate action publishes the container to Docker Hub once the PR is merged.
Here it is: kewalaka/az-pwsh-terraform - Docker Image | Docker Hub
Use in CodeSpaces
Here’s an example of how you can use this in your own repositories, including the config to switch the default shell to PowerShell (place in .devcontainer/devcontainer.json
)
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
| {
"image": "docker. io/kewalaka/az-pwsh-terraform:latest",
"runArgs": [
" -- cap-add=SYS_PTRACE",
" -- security-opt",
"seccomp=unconfined",
" -- init",
" __ network=host"
],
"mounts": [
"source=/var/run/docker.sock, target=/var/run/docker.sock, type=bind"
],
"customizations": {
"vscode": {
"extensions": [
"azapi-vscode.azapi",
"EditorConfig. EditorConfig",
"hashicorp. terraform",
"davidanson. vscode-markdownlint"
],
"settings": {
"terminal. integrated. defaultProfile. linux": "pwsh"
}
}
}
}
|
Here is an example: terraform-azure-starter-template/.devcontainer/devcontainer.json
Call to action
Perhaps try this container out in your own CodeSpaces?
It is very customised to my use case hence you may not agree with my choice of tooling - but you can always simply re-use the above with just a change to the Dockerfile, then have one that works just for you!
Where next?
In my first iteration, I built the container on the PR, and then I re-build it when pushing to Docker Hub, and when tagging.
This is sub-optimal, because what I end up with after the PR might not be the same as what gets built in the deploy or tagging stage.
In the next article, I’ll illustrate one way to fix this - by using GHCR as a staging ground before promoting it to Docker Hub.