Skip to content

Commit bad59e6

Browse files
authored
Add example to convert new engine JSON to SARIF (#55)
1 parent 6f6356f commit bad59e6

File tree

2 files changed

+240
-0
lines changed

2 files changed

+240
-0
lines changed

github/new-scan-engine/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,69 @@ IMAGE_NAME: "mytestimage"
3131
IMAGE_TAG: "my-tag"
3232
DOCKERFILE_CONTEXT: "github/new-scan-engine/"
3333
```
34+
35+
# Convert to SARIF output
36+
37+
You can use the script sysdig-to-sarif to convert the JSON output of the CLI scanner to SARIF and upload it to Github Security:
38+
39+
```yaml
40+
jobs:
41+
build:
42+
name: Build
43+
runs-on: ubuntu-latest
44+
steps:
45+
- name: Checkout code
46+
uses: actions/checkout@v3
47+
48+
...
49+
50+
- name: Setup cache
51+
uses: actions/cache@v3
52+
with:
53+
path: cache
54+
key: ${{ runner.os }}-cache-${{ hashFiles('**/sysdig-cli-scanner', '**/latest_version.txt', '**/db/main.db.meta.json', '**/scanner-cache/inlineScannerCache.db') }}
55+
restore-keys: ${{ runner.os }}-cache-
56+
57+
- name: Download sysdig-cli-scanner if needed
58+
run: |
59+
curl -sLO https://download.sysdig.com/scanning/sysdig-cli-scanner/latest_version.txt
60+
mkdir -p ${GITHUB_WORKSPACE}/cache/db/
61+
if [ ! -f ${GITHUB_WORKSPACE}/cache/latest_version.txt ] || [ $(cat ./latest_version.txt) != $(cat ${GITHUB_WORKSPACE}/cache/latest_version.txt) ]; then
62+
cp ./latest_version.txt ${GITHUB_WORKSPACE}/cache/latest_version.txt
63+
curl -sL -o ${GITHUB_WORKSPACE}/cache/sysdig-cli-scanner "https://download.sysdig.com/scanning/bin/sysdig-cli-scanner/$(cat ${GITHUB_WORKSPACE}/cache/latest_version.txt)/linux/amd64/sysdig-cli-scanner"
64+
chmod +x ${GITHUB_WORKSPACE}/cache/sysdig-cli-scanner
65+
else
66+
echo "sysdig-cli-scanner latest version already downloaded"
67+
fi
68+
69+
- name: Scan the image using sysdig-cli-scanner
70+
env:
71+
SECURE_API_TOKEN: ${{ secrets.SECURE_API_TOKEN }}
72+
run: |
73+
${GITHUB_WORKSPACE}/cache/sysdig-cli-scanner \
74+
--apiurl ${SYSDIG_SECURE_ENDPOINT} \
75+
--console-log \
76+
--json-scan-result report.json \
77+
<put your image name here>
78+
79+
- name: Scan the image using sysdig-cli-scanner
80+
env:
81+
SECURE_API_TOKEN: ${{ secrets.SECURE_API_TOKEN }}
82+
run: |
83+
${GITHUB_WORKSPACE}/cache/sysdig-cli-scanner \
84+
--apiurl ${SYSDIG_SECURE_ENDPOINT} \
85+
docker://${REGISTRY_HOST}/${{github.repository_owner}}/${IMAGE_NAME}:${IMAGE_TAG} \
86+
--console-log \
87+
--dbpath=${GITHUB_WORKSPACE}/cache/db/ \
88+
--cachepath=${GITHUB_WORKSPACE}/cache/scanner-cache/
89+
90+
- name: Generate SARIF report
91+
run: |
92+
python3 /path/to/sysdig-to-sarif.py report.json > results.sarif
93+
94+
- name: Upload scan results to GitHub Security tab
95+
uses: github/codeql-action/upload-sarif@v2
96+
if: always()
97+
with:
98+
sarif_file: results.sarif
99+
```
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import json
5+
import logging
6+
7+
# Setup logger
8+
LOG = logging.getLogger(__name__)
9+
10+
# define a Handler which writes INFO messages or higher to the sys.stderr
11+
console = logging.StreamHandler()
12+
# set a format which is simpler for console use
13+
console.setLevel(logging.INFO)
14+
# tell the handler to use this format
15+
console.setFormatter(logging.Formatter('%(message)s'))
16+
17+
# add the handler to the main logger
18+
LOG.addHandler(console)
19+
20+
21+
LEVELS = {
22+
"error": ["High","Critical"],
23+
"warning": ["Medium"],
24+
"note": ["Negligible","Low"]
25+
}
26+
27+
def generate_report(data):
28+
results = []
29+
rules = []
30+
ruleIds = []
31+
resultUrl = data['info']['resultUrl']
32+
baseUrl = resultUrl[:resultUrl.rfind('/')]
33+
for package in data['result']['packages']:
34+
# Continue if package has no vulnerabilities
35+
if 'vulns' not in package.keys():
36+
LOG.info(f"Package: {package['name']} has no vulnerabilities...skipping...")
37+
continue
38+
39+
for vuln in package['vulns']:
40+
if vuln['name'] not in ruleIds:
41+
rule = {
42+
"id": f"{vuln['name']}",
43+
"name": f"{package['type']}",
44+
"shortDescription": {
45+
"text": f"{vuln['name']} - {package['name']}@{package['version']}"
46+
},
47+
"fullDescription": {
48+
"text": f"{vuln['name']} - {package['name']}@{package['version']}"
49+
},
50+
"defaultConfiguration": {
51+
"level": f"{check_level(vuln['severity']['value'])}"
52+
},
53+
"helpUri": f"https://nvd.nist.gov/vuln/detail/{vuln['name']}",
54+
"help": {
55+
"text": f"Vulnerability {vuln['name']}\nPackage: {package['name']}\nSeverity: {vuln['severity']['value']}\nCVSS Score: {vuln['cvssScore']['value']['score']}\nCVSS Version: {vuln['cvssScore']['value']['version']}\nCVSS Vector: {vuln['cvssScore']['value']['vector']}\nFixed Version: {(vuln['fixedInVersion'] if 'fixedInVersion' in vuln else '')}\nExploitable: {vuln['exploitable']}\nLink: [{vuln['name']}](https://nvd.nist.gov/vuln/detail/{vuln['name']})",
56+
"markdown": f"**Vulnerability {vuln['name']}**\n| Package | Severity| CVSS Score | CVSS Version | CVSS Vector | Fixed Version | Exploitable | Link |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n|{package['name']}|{vuln['severity']['value']}|{vuln['cvssScore']['value']['score']}|{vuln['cvssScore']['value']['version']}|{vuln['cvssScore']['value']['vector']}|{(vuln['fixedInVersion'] if 'fixedInVersion' in vuln else '')}|{vuln['exploitable']}|[{vuln['name']}](https://nvd.nist.gov/vuln/detail/{vuln['name']})|"
57+
},
58+
"properties": {
59+
"precision": "very-high",
60+
"security-severity": f"{vuln['cvssScore']['value']['score']}",
61+
"tags": [
62+
"vulnerability",
63+
"security",
64+
f"{vuln['severity']['value']}"
65+
]
66+
}
67+
}
68+
rules.append(rule)
69+
70+
result = {
71+
"ruleId": f"{vuln['name']}",
72+
"level": f"{check_level(vuln['severity']['value'])}",
73+
"message": {
74+
"text": f"Full image scan results in Sysdig UI: [{data['result']['metadata']['pullString']} scan result]({data['info']['resultUrl']})\nPackage: [{package['name']}]({baseUrl}/content?filter=freeText+in+(\"{package['name']}\"))\nPackage type: {package['type']}\nInstalled Version: {package['version']}\nPackage path: {package['path']}\nVulnerability: [{vuln['name']}]({baseUrl}/vulnerabilities?filter=freeText+in+(\"{vuln['name']}\"))\nSeverity: {vuln['severity']['value']}\nCVSS Score: {vuln['cvssScore']['value']['score']}\nCVSS Version: {vuln['cvssScore']['value']['version']}\nCVSS Vector: {vuln['cvssScore']['value']['vector']}\nFixed Version: {(vuln['fixedInVersion'] if 'fixedInVersion' in vuln else '')}\nExploitable: {vuln['exploitable']}\nLink to NVD: [{vuln['name']}](https://nvd.nist.gov/vuln/detail/{vuln['name']})"
75+
},
76+
"locations": [
77+
{
78+
"physicalLocation": {
79+
"artifactLocation": {
80+
"uri": f"{data['result']['metadata']['pullString']}",
81+
"uriBaseId": "ROOTPATH"
82+
}
83+
},
84+
"message": {
85+
"text": f"{data['result']['metadata']['pullString']} - {package['name']}@{package['version']}"
86+
}
87+
}
88+
]
89+
}
90+
results.append(result)
91+
92+
run = {
93+
"tool": {
94+
"driver": {
95+
"fullName": "Sysdig Vulnerability CLI Scanner",
96+
"informationUri": "https://docs.sysdig.com/en/docs/installation/sysdig-secure/install-vulnerability-cli-scanner",
97+
"name": "sysdig-cli-scanner",
98+
"version": f"{data['scanner']['version']}",
99+
"rules": rules
100+
}
101+
},
102+
"results": results,
103+
"columnKind": "utf16CodeUnits",
104+
"properties": {
105+
"pullString": f"{data['result']['metadata']['pullString']}",
106+
"digest": f"{data['result']['metadata']['digest']}",
107+
"imageId": f"{data['result']['metadata']['imageId']}",
108+
"architecture": f"{data['result']['metadata']['architecture']}",
109+
"baseOs": f"{data['result']['metadata']['baseOs']}",
110+
"os": f"{data['result']['metadata']['os']}",
111+
"size": f"{data['result']['metadata']['size']}",
112+
"layersCount": f"{data['result']['metadata']['layersCount']}",
113+
"resultUrl": f"{data['info']['resultUrl']}",
114+
"resultId": f"{data['info']['resultId']}",
115+
}
116+
}
117+
118+
report = {
119+
"version": "2.1.0",
120+
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
121+
"runs": [run]
122+
}
123+
124+
return report
125+
126+
def check_level(severity):
127+
for key in LEVELS:
128+
if severity in LEVELS[key]:
129+
return key
130+
131+
def main():
132+
parser = argparse.ArgumentParser(description="Convert Sysdig report to SARIF format.")
133+
parser.add_argument("filename", type=str, help="Sysdig report file in json format.")
134+
parser.add_argument("--log-level", dest="logLevel", type=str, choices=['INFO', 'DEBUG'], help="Set log level. If DEBUG level set logs are stored in report.log file in the same folder where script is executed.", default="INFO", required=False)
135+
args = parser.parse_args()
136+
filename = args.filename
137+
logLevel = args.logLevel
138+
139+
if logLevel == "DEBUG":
140+
logging.basicConfig(
141+
level=logging.DEBUG,
142+
format="%(asctime)s.%(msecs)03d %(levelname)s - %(funcName)s: %(message)s",
143+
datefmt="%Y-%m-%d %H:%M:%S",
144+
filename="report.log",
145+
filemode = 'w',
146+
)
147+
else:
148+
LOG.setLevel(logging.INFO)
149+
150+
try:
151+
LOG.info(f"Loading {filename} JSON file.")
152+
with open(filename) as json_file:
153+
data = json.load(json_file)
154+
155+
except:
156+
LOG.info(f"Error: {filename}: Invalid JSON file!")
157+
exit(parser.print_help())
158+
159+
# Simple validation of JSON file format
160+
try:
161+
'metadata' in data
162+
'vulnerabilties' in data
163+
'packages' in data
164+
'policies' in data
165+
'info' in data
166+
except:
167+
LOG.info(f"Error: {filename}: JSON file is not from sysdig-cli-scanner!")
168+
exit(parser.print_help())
169+
170+
report = generate_report(data=data)
171+
print(json.dumps(report))
172+
173+
if __name__ == '__main__':
174+
main()

0 commit comments

Comments
 (0)