Skip to content

Commit 5200315

Browse files
kpankonenKevin Pankonen
authored andcommitted
feat(terraform): add trivy
1 parent 7940a46 commit 5200315

7 files changed

Lines changed: 313 additions & 0 deletions

File tree

ale_linters/terraform/trivy.vim

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
" Description: trivy for Terraform files
2+
"
3+
" See: https://www.terraform.io/
4+
" https://github.com/aquasecurity/trivy
5+
6+
call ale#Set('terraform_trivy_options', '')
7+
call ale#Set('terraform_trivy_executable', 'trivy')
8+
9+
function! ale_linters#terraform#trivy#Handle(buffer, lines) abort
10+
let l:output = []
11+
let l:json = ale#util#FuzzyJSONDecode(a:lines, {})
12+
13+
if empty(get(l:json, 'Results'))
14+
return l:output
15+
endif
16+
17+
let l:fname = expand('#' . a:buffer . ':t')
18+
19+
for l:result in get(l:json, 'Results', [])
20+
for l:misconfig in get(l:result, 'Misconfigurations', [])
21+
let l:severity = get(l:misconfig, 'Severity', 'MEDIUM')
22+
23+
if l:severity is# 'LOW'
24+
let l:type = 'I'
25+
elseif l:severity is# 'CRITICAL' || l:severity is# 'HIGH'
26+
let l:type = 'E'
27+
else
28+
let l:type = 'W'
29+
endif
30+
31+
let l:cause = get(l:misconfig, 'CauseMetadata', {})
32+
let l:title = get(l:misconfig, 'Title', '')
33+
let l:id = get(l:misconfig, 'ID', '')
34+
let l:desc = get(l:misconfig, 'Description', '')
35+
36+
" Module findings store the location in the caller's file
37+
" in CauseMetadata.Occurrences. Use those when available.
38+
let l:occurrences = get(l:cause, 'Occurrences', [])
39+
40+
if !empty(l:occurrences)
41+
for l:occurrence in l:occurrences
42+
if get(l:occurrence, 'Filename', '') ==# l:fname
43+
let l:loc = get(l:occurrence, 'Location', {})
44+
45+
call add(l:output, {
46+
\ 'lnum': get(l:loc, 'StartLine', 1),
47+
\ 'end_lnum': get(l:loc, 'EndLine', get(l:loc, 'StartLine', 1)),
48+
\ 'text': l:title . ' [' . l:id . ']',
49+
\ 'detail': l:id . ': ' . l:title . "\n" . l:desc,
50+
\ 'code': l:id,
51+
\ 'type': l:type,
52+
\})
53+
endif
54+
endfor
55+
elseif get(l:result, 'Target', '') ==# l:fname
56+
call add(l:output, {
57+
\ 'lnum': get(l:cause, 'StartLine', 1),
58+
\ 'end_lnum': get(l:cause, 'EndLine', get(l:cause, 'StartLine', 1)),
59+
\ 'text': l:title . ' [' . l:id . ']',
60+
\ 'detail': l:id . ': ' . l:title . "\n" . l:desc,
61+
\ 'code': l:id,
62+
\ 'type': l:type,
63+
\})
64+
endif
65+
endfor
66+
endfor
67+
68+
return l:output
69+
endfunction
70+
71+
" Construct command arguments to trivy with `terraform_trivy_options`.
72+
function! ale_linters#terraform#trivy#GetCommand(buffer) abort
73+
let l:cmd = '%e config --format json'
74+
75+
let l:opts = ale#Var(a:buffer, 'terraform_trivy_options')
76+
77+
if !empty(l:opts)
78+
let l:cmd .= ' ' . l:opts
79+
endif
80+
81+
let l:cmd .= ' .'
82+
83+
return l:cmd
84+
endfunction
85+
86+
call ale#linter#Define('terraform', {
87+
\ 'name': 'trivy',
88+
\ 'executable': {b -> ale#Var(b, 'terraform_trivy_executable')},
89+
\ 'cwd': '%s:h',
90+
\ 'command': function('ale_linters#terraform#trivy#GetCommand'),
91+
\ 'callback': 'ale_linters#terraform#trivy#Handle',
92+
\})

doc/ale-supported-languages-and-tools.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,7 @@ Notes:
689689
* `terraform-lsp`
690690
* `tflint`
691691
* `tfsec`
692+
* `trivy`
692693
* Texinfo
693694
* `alex`
694695
* `cspell`

doc/ale-terraform.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,30 @@ g:ale_terraform_tflint_options
139139
to include '-f json' in your new value.
140140

141141

142+
===============================================================================
143+
trivy *ale-terraform-trivy*
144+
145+
*ale-options.terraform_trivy_executable*
146+
*g:ale_terraform_trivy_executable*
147+
*b:ale_terraform_trivy_executable*
148+
terraform_trivy_executable
149+
g:ale_terraform_trivy_executable
150+
Type: |String|
151+
Default: `'trivy'`
152+
153+
This variable can be changed to use a different executable for trivy.
154+
155+
*ale-options.terraform_trivy_options*
156+
*g:ale_terraform_trivy_options*
157+
*b:ale_terraform_trivy_options*
158+
terraform_trivy_options
159+
g:ale_terraform_trivy_options
160+
Type: |String|
161+
Default: `''`
162+
163+
This variable can be changed to pass custom CLI flags to trivy.
164+
165+
142166
===============================================================================
143167
tfsec *ale-terraform-tfsec*
144168

doc/ale.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3998,6 +3998,7 @@ documented in additional help files.
39983998
terraform-ls..........................|ale-terraform-terraform-ls|
39993999
terraform-lsp.........................|ale-terraform-terraform-lsp|
40004000
tflint................................|ale-terraform-tflint|
4001+
trivy.................................|ale-terraform-trivy|
40014002
tfsec.................................|ale-terraform-tfsec|
40024003
tex.....................................|ale-tex-options|
40034004
chktex................................|ale-tex-chktex|

supported-tools.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,7 @@ formatting.
699699
* [terraform-lsp](https://github.com/juliosueiras/terraform-lsp) :speech_balloon:
700700
* [tflint](https://github.com/wata727/tflint)
701701
* [tfsec](https://github.com/aquasecurity/tfsec)
702+
* [trivy](https://github.com/aquasecurity/trivy)
702703
* Texinfo
703704
* [alex](https://github.com/get-alex/alex)
704705
* [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell)
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
Before:
2+
runtime ale_linters/terraform/trivy.vim
3+
4+
After:
5+
call ale#linter#Reset()
6+
7+
Execute(The trivy handler should handle empty output):
8+
AssertEqual
9+
\ [],
10+
\ ale_linters#terraform#trivy#Handle(bufnr(''), ['{}'])
11+
12+
Execute(The trivy handler should handle null Results):
13+
AssertEqual
14+
\ [],
15+
\ ale_linters#terraform#trivy#Handle(bufnr(''), ['{"Results": null}'])
16+
17+
Execute(The trivy handler should parse direct findings correctly):
18+
AssertEqual
19+
\ [
20+
\ {
21+
\ 'lnum': 3,
22+
\ 'end_lnum': 6,
23+
\ 'text': 'Bucket does not have encryption enabled [AVD-AWS-0088]',
24+
\ 'detail': "AVD-AWS-0088: Bucket does not have encryption enabled\n"
25+
\ . 'S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.',
26+
\ 'code': 'AVD-AWS-0088',
27+
\ 'type': 'E',
28+
\ },
29+
\ ],
30+
\ ale_linters#terraform#trivy#Handle(bufnr(''), [json_encode(
31+
\ {
32+
\ 'Results': [
33+
\ {
34+
\ 'Target': expand('#' . bufnr('') . ':t'),
35+
\ 'Misconfigurations': [
36+
\ {
37+
\ 'ID': 'AVD-AWS-0088',
38+
\ 'Title': 'Bucket does not have encryption enabled',
39+
\ 'Description': 'S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.',
40+
\ 'Severity': 'HIGH',
41+
\ 'CauseMetadata': {
42+
\ 'StartLine': 3,
43+
\ 'EndLine': 6,
44+
\ },
45+
\ },
46+
\ ],
47+
\ },
48+
\ ],
49+
\ }
50+
\ )])
51+
52+
Execute(The trivy handler should use Occurrences for module findings):
53+
AssertEqual
54+
\ [
55+
\ {
56+
\ 'lnum': 12,
57+
\ 'end_lnum': 17,
58+
\ 'text': 'S3 Bucket Logging [AWS-0089]',
59+
\ 'detail': "AWS-0089: S3 Bucket Logging\n"
60+
\ . 'Ensures S3 bucket logging is enabled for S3 buckets',
61+
\ 'code': 'AWS-0089',
62+
\ 'type': 'I',
63+
\ },
64+
\ ],
65+
\ ale_linters#terraform#trivy#Handle(bufnr(''), [json_encode(
66+
\ {
67+
\ 'Results': [
68+
\ {
69+
\ 'Target': 'git::ssh:/git@github.com/example/terraform-module?ref=v1.0.0/main.tf',
70+
\ 'Misconfigurations': [
71+
\ {
72+
\ 'ID': 'AWS-0089',
73+
\ 'Title': 'S3 Bucket Logging',
74+
\ 'Description': 'Ensures S3 bucket logging is enabled for S3 buckets',
75+
\ 'Severity': 'LOW',
76+
\ 'CauseMetadata': {
77+
\ 'StartLine': 24,
78+
\ 'EndLine': 33,
79+
\ 'Occurrences': [
80+
\ {
81+
\ 'Filename': expand('#' . bufnr('') . ':t'),
82+
\ 'Location': {
83+
\ 'StartLine': 12,
84+
\ 'EndLine': 17,
85+
\ },
86+
\ },
87+
\ ],
88+
\ },
89+
\ },
90+
\ ],
91+
\ },
92+
\ ],
93+
\ }
94+
\ )])
95+
96+
Execute(The trivy handler should skip module findings with no matching Occurrences):
97+
AssertEqual
98+
\ [],
99+
\ ale_linters#terraform#trivy#Handle(bufnr(''), [json_encode(
100+
\ {
101+
\ 'Results': [
102+
\ {
103+
\ 'Target': 'git::ssh:/git@github.com/example/terraform-module?ref=v1.0.0/main.tf',
104+
\ 'Misconfigurations': [
105+
\ {
106+
\ 'ID': 'AWS-0089',
107+
\ 'Title': 'S3 Bucket Logging',
108+
\ 'Description': 'Ensures S3 bucket logging is enabled',
109+
\ 'Severity': 'LOW',
110+
\ 'CauseMetadata': {
111+
\ 'StartLine': 24,
112+
\ 'EndLine': 33,
113+
\ 'Occurrences': [
114+
\ {
115+
\ 'Filename': 'other.tf',
116+
\ 'Location': {
117+
\ 'StartLine': 5,
118+
\ 'EndLine': 10,
119+
\ },
120+
\ },
121+
\ ],
122+
\ },
123+
\ },
124+
\ ],
125+
\ },
126+
\ ],
127+
\ }
128+
\ )])
129+
130+
Execute(The trivy handler should map severity levels correctly):
131+
let g:result = ale_linters#terraform#trivy#Handle(bufnr(''), [json_encode(
132+
\ {
133+
\ 'Results': [
134+
\ {
135+
\ 'Target': expand('#' . bufnr('') . ':t'),
136+
\ 'Misconfigurations': [
137+
\ {
138+
\ 'ID': 'AVD-001',
139+
\ 'Title': 'Low issue',
140+
\ 'Description': 'Low',
141+
\ 'Severity': 'LOW',
142+
\ 'CauseMetadata': {'StartLine': 1, 'EndLine': 1},
143+
\ },
144+
\ {
145+
\ 'ID': 'AVD-002',
146+
\ 'Title': 'Medium issue',
147+
\ 'Description': 'Medium',
148+
\ 'Severity': 'MEDIUM',
149+
\ 'CauseMetadata': {'StartLine': 2, 'EndLine': 2},
150+
\ },
151+
\ {
152+
\ 'ID': 'AVD-003',
153+
\ 'Title': 'High issue',
154+
\ 'Description': 'High',
155+
\ 'Severity': 'HIGH',
156+
\ 'CauseMetadata': {'StartLine': 3, 'EndLine': 3},
157+
\ },
158+
\ {
159+
\ 'ID': 'AVD-004',
160+
\ 'Title': 'Critical issue',
161+
\ 'Description': 'Critical',
162+
\ 'Severity': 'CRITICAL',
163+
\ 'CauseMetadata': {'StartLine': 4, 'EndLine': 4},
164+
\ },
165+
\ ],
166+
\ },
167+
\ ],
168+
\ }
169+
\ )])
170+
171+
AssertEqual 'I', g:result[0].type
172+
AssertEqual 'W', g:result[1].type
173+
AssertEqual 'E', g:result[2].type
174+
AssertEqual 'E', g:result[3].type
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Before:
2+
call ale#assert#SetUpLinterTest('terraform', 'trivy')
3+
4+
After:
5+
call ale#assert#TearDownLinterTest()
6+
7+
Execute(The default command should be correct):
8+
AssertLinter 'trivy', ale#Escape('trivy') . ' config --format json .'
9+
10+
Execute(The default executable should be configurable):
11+
let b:ale_terraform_trivy_executable = '/usr/bin/trivy'
12+
13+
AssertLinter '/usr/bin/trivy', ale#Escape('/usr/bin/trivy') . ' config --format json .'
14+
15+
Execute(Overriding options should work):
16+
let g:ale_terraform_trivy_executable = '/usr/local/bin/trivy'
17+
let g:ale_terraform_trivy_options = '--severity HIGH,CRITICAL'
18+
19+
AssertLinter '/usr/local/bin/trivy',
20+
\ ale#Escape('/usr/local/bin/trivy') . ' config --format json --severity HIGH,CRITICAL .'

0 commit comments

Comments
 (0)