Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions ale_linters/terraform/trivy.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
" Description: trivy for Terraform files
"
" See: https://www.terraform.io/
" https://github.com/aquasecurity/trivy

call ale#Set('terraform_trivy_options', '')
call ale#Set('terraform_trivy_executable', 'trivy')

function! ale_linters#terraform#trivy#Handle(buffer, lines) abort
let l:output = []
let l:json = ale#util#FuzzyJSONDecode(a:lines, {})

if empty(get(l:json, 'Results'))
return l:output
endif

let l:fname = expand('#' . a:buffer . ':t')

for l:result in get(l:json, 'Results', [])
for l:misconfig in get(l:result, 'Misconfigurations', [])
let l:severity = get(l:misconfig, 'Severity', 'MEDIUM')

if l:severity is# 'LOW'
let l:type = 'I'
elseif l:severity is# 'CRITICAL' || l:severity is# 'HIGH'
let l:type = 'E'
else
let l:type = 'W'
endif

let l:cause = get(l:misconfig, 'CauseMetadata', {})
let l:title = get(l:misconfig, 'Title', '')
let l:id = get(l:misconfig, 'ID', '')
let l:desc = get(l:misconfig, 'Description', '')

" Module findings store the location in the caller's file
" in CauseMetadata.Occurrences. Use those when available.
let l:occurrences = get(l:cause, 'Occurrences', [])

if !empty(l:occurrences)
for l:occurrence in l:occurrences
if get(l:occurrence, 'Filename', '') ==# l:fname
let l:loc = get(l:occurrence, 'Location', {})

call add(l:output, {
\ 'lnum': get(l:loc, 'StartLine', 1),
\ 'end_lnum': get(l:loc, 'EndLine', get(l:loc, 'StartLine', 1)),
\ 'text': l:title . ' [' . l:id . ']',
\ 'detail': l:id . ': ' . l:title . "\n" . l:desc,
\ 'code': l:id,
\ 'type': l:type,
\})
endif
endfor
elseif get(l:result, 'Target', '') ==# l:fname
call add(l:output, {
\ 'lnum': get(l:cause, 'StartLine', 1),
\ 'end_lnum': get(l:cause, 'EndLine', get(l:cause, 'StartLine', 1)),
\ 'text': l:title . ' [' . l:id . ']',
\ 'detail': l:id . ': ' . l:title . "\n" . l:desc,
\ 'code': l:id,
\ 'type': l:type,
\})
endif
endfor
endfor

return l:output
endfunction

" Construct command arguments to trivy with `terraform_trivy_options`.
function! ale_linters#terraform#trivy#GetCommand(buffer) abort
let l:cmd = '%e config --format json'

let l:opts = ale#Var(a:buffer, 'terraform_trivy_options')

if !empty(l:opts)
let l:cmd .= ' ' . l:opts
endif

let l:cmd .= ' .'

return l:cmd
endfunction

call ale#linter#Define('terraform', {
\ 'name': 'trivy',
\ 'executable': {b -> ale#Var(b, 'terraform_trivy_executable')},
\ 'cwd': '%s:h',
\ 'command': function('ale_linters#terraform#trivy#GetCommand'),
\ 'callback': 'ale_linters#terraform#trivy#Handle',
\})
1 change: 1 addition & 0 deletions doc/ale-supported-languages-and-tools.txt
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ Notes:
* `terraform-lsp`
* `tflint`
* `tfsec`
* `trivy`
* Texinfo
* `alex`
* `cspell`
Expand Down
24 changes: 24 additions & 0 deletions doc/ale-terraform.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,30 @@ g:ale_terraform_tflint_options
to include '-f json' in your new value.


===============================================================================
trivy *ale-terraform-trivy*

*ale-options.terraform_trivy_executable*
*g:ale_terraform_trivy_executable*
*b:ale_terraform_trivy_executable*
terraform_trivy_executable
g:ale_terraform_trivy_executable
Type: |String|
Default: `'trivy'`

This variable can be changed to use a different executable for trivy.

*ale-options.terraform_trivy_options*
*g:ale_terraform_trivy_options*
*b:ale_terraform_trivy_options*
terraform_trivy_options
g:ale_terraform_trivy_options
Type: |String|
Default: `''`

This variable can be changed to pass custom CLI flags to trivy.


===============================================================================
tfsec *ale-terraform-tfsec*

Expand Down
1 change: 1 addition & 0 deletions doc/ale.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4002,6 +4002,7 @@ documented in additional help files.
terraform-ls..........................|ale-terraform-terraform-ls|
terraform-lsp.........................|ale-terraform-terraform-lsp|
tflint................................|ale-terraform-tflint|
trivy.................................|ale-terraform-trivy|
tfsec.................................|ale-terraform-tfsec|
tex.....................................|ale-tex-options|
chktex................................|ale-tex-chktex|
Expand Down
1 change: 1 addition & 0 deletions supported-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,7 @@ formatting.
* [terraform-lsp](https://github.com/juliosueiras/terraform-lsp) :speech_balloon:
* [tflint](https://github.com/wata727/tflint)
* [tfsec](https://github.com/aquasecurity/tfsec)
* [trivy](https://github.com/aquasecurity/trivy)
* Texinfo
* [alex](https://github.com/get-alex/alex)
* [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell)
Expand Down
174 changes: 174 additions & 0 deletions test/handler/test_trivy_handler.vader
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
Before:
runtime ale_linters/terraform/trivy.vim

After:
call ale#linter#Reset()

Execute(The trivy handler should handle empty output):
AssertEqual
\ [],
\ ale_linters#terraform#trivy#Handle(bufnr(''), ['{}'])

Execute(The trivy handler should handle null Results):
AssertEqual
\ [],
\ ale_linters#terraform#trivy#Handle(bufnr(''), ['{"Results": null}'])

Execute(The trivy handler should parse direct findings correctly):
AssertEqual
\ [
\ {
\ 'lnum': 3,
\ 'end_lnum': 6,
\ 'text': 'Bucket does not have encryption enabled [AVD-AWS-0088]',
\ 'detail': "AVD-AWS-0088: Bucket does not have encryption enabled\n"
\ . 'S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.',
\ 'code': 'AVD-AWS-0088',
\ 'type': 'E',
\ },
\ ],
\ ale_linters#terraform#trivy#Handle(bufnr(''), [json_encode(
\ {
\ 'Results': [
\ {
\ 'Target': expand('#' . bufnr('') . ':t'),
\ 'Misconfigurations': [
\ {
\ 'ID': 'AVD-AWS-0088',
\ 'Title': 'Bucket does not have encryption enabled',
\ 'Description': 'S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.',
\ 'Severity': 'HIGH',
\ 'CauseMetadata': {
\ 'StartLine': 3,
\ 'EndLine': 6,
\ },
\ },
\ ],
\ },
\ ],
\ }
\ )])

Execute(The trivy handler should use Occurrences for module findings):
AssertEqual
\ [
\ {
\ 'lnum': 12,
\ 'end_lnum': 17,
\ 'text': 'S3 Bucket Logging [AWS-0089]',
\ 'detail': "AWS-0089: S3 Bucket Logging\n"
\ . 'Ensures S3 bucket logging is enabled for S3 buckets',
\ 'code': 'AWS-0089',
\ 'type': 'I',
\ },
\ ],
\ ale_linters#terraform#trivy#Handle(bufnr(''), [json_encode(
\ {
\ 'Results': [
\ {
\ 'Target': 'git::ssh:/git@github.com/example/terraform-module?ref=v1.0.0/main.tf',
\ 'Misconfigurations': [
\ {
\ 'ID': 'AWS-0089',
\ 'Title': 'S3 Bucket Logging',
\ 'Description': 'Ensures S3 bucket logging is enabled for S3 buckets',
\ 'Severity': 'LOW',
\ 'CauseMetadata': {
\ 'StartLine': 24,
\ 'EndLine': 33,
\ 'Occurrences': [
\ {
\ 'Filename': expand('#' . bufnr('') . ':t'),
\ 'Location': {
\ 'StartLine': 12,
\ 'EndLine': 17,
\ },
\ },
\ ],
\ },
\ },
\ ],
\ },
\ ],
\ }
\ )])

Execute(The trivy handler should skip module findings with no matching Occurrences):
AssertEqual
\ [],
\ ale_linters#terraform#trivy#Handle(bufnr(''), [json_encode(
\ {
\ 'Results': [
\ {
\ 'Target': 'git::ssh:/git@github.com/example/terraform-module?ref=v1.0.0/main.tf',
\ 'Misconfigurations': [
\ {
\ 'ID': 'AWS-0089',
\ 'Title': 'S3 Bucket Logging',
\ 'Description': 'Ensures S3 bucket logging is enabled',
\ 'Severity': 'LOW',
\ 'CauseMetadata': {
\ 'StartLine': 24,
\ 'EndLine': 33,
\ 'Occurrences': [
\ {
\ 'Filename': 'other.tf',
\ 'Location': {
\ 'StartLine': 5,
\ 'EndLine': 10,
\ },
\ },
\ ],
\ },
\ },
\ ],
\ },
\ ],
\ }
\ )])

Execute(The trivy handler should map severity levels correctly):
let g:result = ale_linters#terraform#trivy#Handle(bufnr(''), [json_encode(
\ {
\ 'Results': [
\ {
\ 'Target': expand('#' . bufnr('') . ':t'),
\ 'Misconfigurations': [
\ {
\ 'ID': 'AVD-001',
\ 'Title': 'Low issue',
\ 'Description': 'Low',
\ 'Severity': 'LOW',
\ 'CauseMetadata': {'StartLine': 1, 'EndLine': 1},
\ },
\ {
\ 'ID': 'AVD-002',
\ 'Title': 'Medium issue',
\ 'Description': 'Medium',
\ 'Severity': 'MEDIUM',
\ 'CauseMetadata': {'StartLine': 2, 'EndLine': 2},
\ },
\ {
\ 'ID': 'AVD-003',
\ 'Title': 'High issue',
\ 'Description': 'High',
\ 'Severity': 'HIGH',
\ 'CauseMetadata': {'StartLine': 3, 'EndLine': 3},
\ },
\ {
\ 'ID': 'AVD-004',
\ 'Title': 'Critical issue',
\ 'Description': 'Critical',
\ 'Severity': 'CRITICAL',
\ 'CauseMetadata': {'StartLine': 4, 'EndLine': 4},
\ },
\ ],
\ },
\ ],
\ }
\ )])

AssertEqual 'I', g:result[0].type
AssertEqual 'W', g:result[1].type
AssertEqual 'E', g:result[2].type
AssertEqual 'E', g:result[3].type
20 changes: 20 additions & 0 deletions test/linter/test_terraform_trivy.vader
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Before:
call ale#assert#SetUpLinterTest('terraform', 'trivy')

After:
call ale#assert#TearDownLinterTest()

Execute(The default command should be correct):
AssertLinter 'trivy', ale#Escape('trivy') . ' config --format json .'

Execute(The default executable should be configurable):
let b:ale_terraform_trivy_executable = '/usr/bin/trivy'

AssertLinter '/usr/bin/trivy', ale#Escape('/usr/bin/trivy') . ' config --format json .'

Execute(Overriding options should work):
let g:ale_terraform_trivy_executable = '/usr/local/bin/trivy'
let g:ale_terraform_trivy_options = '--severity HIGH,CRITICAL'

AssertLinter '/usr/local/bin/trivy',
\ ale#Escape('/usr/local/bin/trivy') . ' config --format json --severity HIGH,CRITICAL .'