Skip to content

Commit 34f424e

Browse files
paulbPaulBarrie
authored andcommitted
feat: add security report action
1 parent c27b6e2 commit 34f424e

File tree

9 files changed

+389
-0
lines changed

9 files changed

+389
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Example of workflow for security report
2+
3+
on:
4+
pull_request:
5+
6+
jobs:
7+
build:
8+
runs-on: ubuntu-20.04
9+
steps:
10+
- uses: actions/checkout@master
11+
12+
- name: Pull the base image
13+
run: docker pull python:3.10-rc-slim
14+
15+
- name: Build the new image
16+
run: docker build -t python:security-test -f example/Dockerfile .
17+
18+
- name: "Security reports"
19+
uses: dreamquark-ai/github-action-security-report@main
20+
env:
21+
GITHUB_PAT: ${{secrets.SECURITY_REPORT_ACTION_EXAMPLE_PAT}}
22+
with:
23+
image: 'python'
24+
base-tag: 3.10-rc-slim
25+
new-tag: 'security-test'
26+
orga: 'dreamquark-ai'
27+
repo: 'github-action-security-report'
28+
pr-nb: ${{ github.event.number }}
29+
topic: 'example'

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Dreamquark security report action
2+
3+
This action is meant for generating differntial secirity reports based on [trivy](https://github.com/aquasecurity/trivy) to be published as a comment of a pull request.
4+
5+
From a base image used as reference, it underlies the new security failures and the one that have been removed after changes in your source code.
6+
7+
8+
## Example of usage
9+
10+
Before calling the action make sure to get the images (the base and the new one) in the pipeline either by pulling or building them.
11+
12+
```name: Example of workflow for security report
13+
14+
on:
15+
pull_request:
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-20.04
20+
steps:
21+
- uses: actions/checkout@master
22+
23+
- name: Pull the base image
24+
run: docker pull python:3.10-rc-slim
25+
26+
- name: Build the new image
27+
run: docker build -t python:security-test -f example/Dockerfile .
28+
29+
- name: "Security reports"
30+
uses: dreamquark-ai/github-action-security-report@main
31+
env:
32+
GITHUB_PAT: ${{secrets.SECURITY_REPORT_ACTION_EXAMPLE_PAT}}
33+
with:
34+
image: 'python'
35+
base-tag: 3.10-rc-slim
36+
new-tag: 'security-test'
37+
orga: 'dreamquark-ai'
38+
repo: 'github-action-security-report'
39+
pr-nb: ${{ github.event.number }}
40+
topic: 'example'
41+
```
42+
43+
## Inputs
44+
45+
| Name | Type | Default | Required | Description |
46+
|--- |:-: |:-: |:-: |:-: |
47+
image | `string` | | `true` | The image on which differential reports must be performed |a
48+
base-tag | `string` | `latest` | `true` | The tag of the base image used as reference |a
49+
new-tag | `string` | `security-test` | `true` | The tag of the new image used to seek out new and removed vulnerabilities |a
50+
repo | `string` | | `true` | Repository on which the action is triggered |a
51+
pr-nb | `string` | | `true` | PR number on which to comment with the security report |a
52+
topic | `string` | `image` | `true` | The title of the report: used to identify the security report |a
53+
54+
55+

action.yaml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: 'Security report'
2+
description: 'Compare security reports between PR and develop'
3+
inputs:
4+
image:
5+
description: 'Image to perform security scans on'
6+
required: true
7+
base-tag:
8+
description: 'Tag of the base image used as reference'
9+
required: true
10+
default: 'latest'
11+
new-tag:
12+
description: 'Tag of the new image on which changes must be detected'
13+
required: true
14+
default: 'security-test'
15+
orga:
16+
description: 'Organization name to which the repo belongs'
17+
required: true
18+
default: 'dreamquark-ai'
19+
repo:
20+
description: 'Repository on which the action is triggered'
21+
required: true
22+
pr-nb:
23+
description: 'PR number on which to comment with the security report'
24+
required: true
25+
topic:
26+
description: 'The title of the report: used to identify the security report'
27+
default: 'image'
28+
required: true
29+
runs:
30+
using: 'composite'
31+
steps:
32+
- name: "Install trivy and set up folders"
33+
shell: bash
34+
run: |
35+
mkdir -p ${{ github.action_path }}/reports
36+
sudo apt-get install wget apt-transport-https gnupg lsb-release
37+
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
38+
echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
39+
sudo apt-get update
40+
sudo apt-get install -y trivy
41+
42+
- name: "Security report on base image"
43+
shell: bash
44+
working-directory: ${{ github.action_path }}/reports
45+
run: trivy -q -f json -o report-base.json ${{inputs.image}}:${{inputs.base-tag}}
46+
47+
- name: "Security report on the new image"
48+
shell: bash
49+
working-directory: ${{ github.action_path }}/reports
50+
run: trivy -q -f json -o report-new.json ${{inputs.image}}:${{inputs.new-tag}}
51+
52+
- name: "Compare the reports"
53+
shell: bash
54+
working-directory: ${{ github.action_path }}
55+
run: >
56+
./main.sh --image ${{inputs.image}} --base-tag ${{inputs.base-tag}} --new-tag ${{inputs.new-tag}}
57+
--pull-request ${{inputs.pr-nb}} --topic ${{inputs.topic}} --repo ${{inputs.repo}} --orga ${{inputs.orga}}

comment-pr.sh

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/bin/bash
2+
FOLDER=$(readlink -f "${BASH_SOURCE[0]}" | xargs dirname)
3+
. "${FOLDER}/utils.sh"
4+
5+
6+
#https://docs.github.com/en/rest/reference/issues#list-issue-comments
7+
function comment_exists() {
8+
repo=$1
9+
pr=$2
10+
topic=$3
11+
orga=$4
12+
13+
resp=$(curl -s -H "Authorization: token $GITHUB_PAT" \
14+
https://api.github.com/repos/$orga/$repo/issues/$pr/comments)
15+
#Check resp is an empty array
16+
if [ ${#resp[@]} -eq 0 ]; then
17+
echo ""
18+
else
19+
echo $resp | jq '.[] | select(.body | test(".*- (.*?)'"$topic"'\\s+")) | .id'
20+
fi
21+
}
22+
23+
#https://docs.github.com/en/rest/reference/issues#create-an-issue-comment
24+
function add_comment() {
25+
repo=$1
26+
pr=$2
27+
body=$3
28+
orga=$4
29+
30+
resp=$(curl --silent -H "Authorization: token ${GITHUB_PAT}" \
31+
-X POST -d @body \
32+
https://api.github.com/repos/$orga/$repo/issues/$pr/comments)
33+
34+
}
35+
36+
#https://docs.github.com/en/rest/reference/issues#delete-an-issue-comment
37+
function delete_comment() {
38+
repo=$1
39+
comment_id=$2
40+
41+
curl \
42+
-X DELETE --silent\
43+
-H "Accept: application/vnd.github.v3+json" \
44+
-H "Authorization: token ${GITHUB_PAT}" \
45+
https://api.github.com/repos/$orga/$repo/issues/comments/$comment_id
46+
}
47+
48+
function comment_pr() {
49+
repo=$1
50+
pr=$2
51+
topic=$3
52+
orga=$4
53+
54+
temp_folder
55+
# Check if comment already exists
56+
id_found=$(comment_exists $repo $pr $topic $orga)
57+
echo $id_found
58+
if [[ ! -z "$id_found" ]]
59+
then
60+
echo "Security report already exists: remove the previous one."
61+
delete_comment $repo $id_found $orga
62+
fi
63+
64+
# Add new comment
65+
echo "{\"body\": \"$(cat ../reports/security.md | sed "s/\"/'/g" | sed 's/$/\\n/')\"}" > body
66+
add_comment $repo $pr $body $orga
67+
cleanup_folder
68+
}
69+

example/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM python:3.10-rc-slim
2+
3+
USER root
4+
# Remove package with vulnerabilities and add one with vulnerabilities
5+
RUN rm -rf /usr/share/doc/libc-bin /usr/share/libc-bin &&\
6+
apt-get update -y &&\
7+
apt-get install -y libcrypto1.1-udeb

main.sh

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/bin/bash
2+
3+
set -e
4+
FOLDER=$(readlink -f "${BASH_SOURCE[0]}" | xargs dirname)
5+
. "${FOLDER}/parse-json.sh"
6+
. "${FOLDER}/md-template.sh"
7+
. "${FOLDER}/comment-pr.sh"
8+
9+
10+
base_report="$FOLDER/reports/report-base.json"
11+
new_report="$FOLDER/reports/report-new.json"
12+
target="$FOLDER/reports/security.md"
13+
report_folder="$FOLDER/reports"
14+
15+
16+
POSITIONAL=()
17+
while [[ $# -gt 0 ]]
18+
do
19+
key="$1"
20+
21+
case $key in
22+
-i|--image)
23+
image="$2"
24+
shift
25+
shift
26+
;;
27+
-bt|--base-tag)
28+
base_tag="$2"
29+
shift
30+
shift
31+
;;
32+
-nt|--new-tag)
33+
new_tag="$2"
34+
shift
35+
shift
36+
;;
37+
-r|--repo)
38+
repo="$2"
39+
shift
40+
shift
41+
;;
42+
-pr|--pull-request)
43+
pr="$2"
44+
shift
45+
shift
46+
;;
47+
-t|--topic)
48+
topic="$2"
49+
shift
50+
shift
51+
;;
52+
-o|--orga)
53+
orga="$2"
54+
shift
55+
shift
56+
;;
57+
*)
58+
POSITIONAL+=("$1")
59+
shift
60+
;;
61+
esac
62+
done
63+
64+
65+
echo "Compare the differences between $base_report and $new_report."
66+
get_differences $base_report $new_report $report_folder
67+
echo "Generate the report for $image:$base_tag->$new_tag."
68+
generate_markdown $report_folder $report_folder $image $base_tag $new_tag $topic
69+
echo "Publish the report as a comment of the PR #$pr to $repo related to $topic"
70+
comment_pr $repo $pr $topic $orga

md-template.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/bin/bash
2+
3+
set -e
4+
FOLDER=$(readlink -f "${BASH_SOURCE[0]}" | xargs dirname)
5+
. "${FOLDER}/utils.sh"
6+
7+
8+
function generate_markdown() {
9+
report_folder=$1
10+
target=$2
11+
img=$3
12+
base_tag=$4
13+
new_tag=$5
14+
subject=$6
15+
16+
template="$FOLDER/template.md"
17+
temp_folder
18+
19+
old=$(cat $report_folder/old.json | jq length)
20+
new=$(cat $report_folder/new.json | jq length)
21+
22+
echo "Beginning markdown generation"
23+
img=$(echo $img | sed -s "s/\//-/g")
24+
25+
printf "# 🛡️ Security scan 🕵🚔🔫 - $subject \n\n" > security.md
26+
printf "**$new** security vulnerabilities have appeared and **$old** have been removed from the **$base_tag** version of the image **$img**. \n\n" >> security.md
27+
28+
# Generate table with the new vulnerabilities
29+
if [ $new -ne 0 ]; then
30+
printf "## New security failures \n\n" >> security.md
31+
printf "\n| Vulnerability | Package | Package Version | Severity |\n |--- |:-: |:-: |:-: |\n" >> security.md
32+
cat "$report_folder/new.json" | jq -r '.[] | [.VulnerabilityID, .PkgName, .InstalledVersion, .Severity] | join(" | ")' | sed -E "s/([^ ]*)(.*)/[\1](https:\/\/nvd.nist.gov\/vuln\/detail\/\1)\2/" | sed 's/$/ |a /' >> security.md
33+
fi
34+
35+
if [ $old -ne 0 ]; then
36+
printf "\n ## Removed security failures \n\n" >> security.md
37+
printf "\n| Vulnerability | Package | Package Version | Severity |\n |--- |:-: |:-: |:-: |\n" >> security.md
38+
cat "${report_folder}/old.json" | jq -r '.[] | [.VulnerabilityID, .PkgName, .InstalledVersion, .Severity] | join(" | ")' | sed -E "s/([^ ]*)(.*)/[\1](https:\/\/nvd.nist.gov\/vuln\/detail\/\1)\2/" | sed 's/$/ |a /' >> security.md
39+
fi
40+
41+
42+
mv security.md $target
43+
cleanup_folder
44+
}
45+
46+

parse-json.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/bash
2+
3+
set -e
4+
FOLDER=$(readlink -f "${BASH_SOURCE[0]}" | xargs dirname)
5+
. "${FOLDER}/utils.sh"
6+
7+
8+
function get_differences() {
9+
base_report=$1
10+
new_report=$2
11+
target_folder=$3
12+
13+
mkdir -p $target_folder
14+
temp_folder
15+
16+
cat $base_report | jq -r '.[].Vulnerabilities[]? | [.VulnerabilityID?, .PkgName?, .InstalledVersion?] | join(",")' | sort > tmp-base
17+
cat $new_report | jq -r '.[].Vulnerabilities[]? | [.VulnerabilityID?, .PkgName?, .InstalledVersion?] | join(",")' | sort > tmp-new
18+
19+
# Get added and removed the vulnerabilities
20+
echo "Get the added and removed vulnerabilities"
21+
comm -13 tmp-base tmp-new > tmp-added
22+
comm -23 tmp-base tmp-new > tmp-rm
23+
24+
# Get a proper json with all the vulnerabilities
25+
echo "Get proper json with all the vulnerabilities"
26+
# Get the new vulnerabilities
27+
echo "[" > new.json
28+
cat tmp-added | sed -s -e 's/^\([^,]*\),\([^,]*\),\(.*\)$/ cat '$(echo $new_report | sed -s "s|/|\\\/|g" )' | jq \x27 .[].Vulnerabilities[]? | select(.VulnerabilityID =="\1" and .PkgName =="\2" and .InstalledVersion=="\3") \x27/' | sh | head -c-1 | sed -z 's/}\n{/},\n{/g' >> new.json
29+
echo "]" >> new.json
30+
# Get the removed vulnerabilities
31+
echo "[" > old.json
32+
cat tmp-rm | sed -s -e 's/^\([^,]*\),\([^,]*\),\(.*\)$/ cat '$(echo $base_report | sed -s "s|/|\\\/|g" )' | jq \x27 .[].Vulnerabilities[]? | select(.VulnerabilityID =="\1" and .PkgName =="\2" and .InstalledVersion=="\3") \x27/' | sh | head -c-1 | sed -z 's/}\n{/},\n{/g' >> old.json
33+
echo "]" >> old.json
34+
35+
mv new.json $target_folder
36+
mv old.json $target_folder
37+
38+
cleanup_folder
39+
}
40+

utils.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
3+
function temp_folder(){
4+
# Use date instead of /dev/urandom since it's based on driver noise
5+
# and thus does not work in CI
6+
uuid="$(date | sed 's/[^0-9]//g')"
7+
temp_folder="temp_${uuid}"
8+
mkdir ${temp_folder}
9+
cd ${temp_folder}
10+
}
11+
12+
function cleanup_folder(){
13+
folder="$(pwd)"
14+
cd ..
15+
rm -rf ${folder}
16+
}

0 commit comments

Comments
 (0)