Skip to content

Commit 75786cb

Browse files
SUP-3846: Add additional File Output Formats (#28)
* feat: produce additional output file for non-human output formats * feat: update parameters and format * docs: update README to reflect parameter changes * feat: reflect paramter changes in hook * test: reflect parameter changes in tests * fix: add set -e to ensure hook doesn't silently fail * chore: remove version, pin images and remove Read Only * feat/fix: add shared lib for handling reading env vars correctly for strings or arrays parameters * Revert "fix: add set -e to ensure hook doesn't silently fail" This reverts commit f0bc975. * fix/test: correct Env Var used to read and add test * fix: make shared.bash executable for shellcheck * fix: update pipeline for shellcheck shared.bash * chore: re-add read-only for volume mounts as read-write not required * feat: add check for duplicate file formats * set a default output that will be used for the Build Annotations so that can be refactored separately * added notes for future changes required for loop to support multiple output files with the same file format per Wiz CLI docs Signed-off-by: Tom Watt <tom@buildkite.com> * tests: add tests for duplicate file output formats Signed-off-by: Tom Watt <tom@buildkite.com> * docs/chore: file-output-format is optional with no default Signed-off-by: Tom Watt <tom@buildkite.com> --------- Signed-off-by: Tom Watt <tom@buildkite.com> Co-authored-by: Shimon Ulewicz <sulewicz@groq.com>
1 parent 83653a3 commit 75786cb

File tree

7 files changed

+226
-23
lines changed

7 files changed

+226
-23
lines changed

.buildkite/pipeline.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ steps:
99
- shellcheck#v1.3.0:
1010
files:
1111
- hooks/**
12+
- lib/**
1213

1314
- label: ":shell: Tests"
1415
plugins:

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,15 @@ Used when `scan-type` is `iac`.
172172

173173
The path to image file, if the `scan-type` is `docker`.
174174

175-
### `output-format` (Optional, string): `human | json | sarif`
175+
### `scan-format` (Optional, string): `human | json | sarif`
176176

177177
Scans output format.
178178
Defaults to: `human`
179179

180+
### `file-output-format` (Optional, string or array): `human | json | sarif | csv-zip`
181+
182+
Generates an additional output file with the specified format.
183+
180184
### `parameter-files` (Optional, string)
181185

182186
Comma separated list of globs of external parameter files to include while scanning e.g., `variables.tf`
@@ -197,7 +201,7 @@ Defaults to: `false`
197201
To run the tests:
198202

199203
```shell
200-
docker-compose run --rm tests
204+
docker compose run --rm tests
201205
```
202206

203207
## Contributing

docker-compose.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
version: '2'
21
services:
32
tests:
4-
image: buildkite/plugin-tester
3+
image: buildkite/plugin-tester:v4.2.0
54
volumes:
65
- ".:/plugin:ro"
76
lint:
8-
image: buildkite/plugin-linter
7+
image: buildkite/plugin-linter:v2.1.0
98
command: ['--id', 'wiz']
109
volumes:
1110
- ".:/plugin:ro"

hooks/post-command

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22

33
set -uo pipefail
44

5+
DIR="$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)"
6+
7+
# shellcheck source=lib/shared.bash
8+
. "$DIR/../lib/shared.bash"
9+
510
WIZ_DIR="$HOME/.wiz"
611
SCAN_TYPE="${BUILDKITE_PLUGIN_WIZ_SCAN_TYPE:-}"
712
FILE_PATH="${BUILDKITE_PLUGIN_WIZ_PATH:-}"
813
PARAMETER_FILES="${BUILDKITE_PLUGIN_WIZ_PARAMETER_FILES:-}"
914
IAC_TYPE="${BUILDKITE_PLUGIN_WIZ_IAC_TYPE:-}"
10-
OUTPUT_FORMAT="${BUILDKITE_PLUGIN_WIZ_OUTPUT_FORMAT:=human}"
15+
SCAN_FORMAT="${BUILDKITE_PLUGIN_WIZ_SCAN_FORMAT:=human}"
1116
SHOW_SECRET_SNIPPETS="${BUILDKITE_PLUGIN_WIZ_SHOW_SECRET_SNIPPETS:=false}"
1217

1318
if [[ -z "${SCAN_TYPE}" ]]; then
@@ -39,15 +44,51 @@ if [[ "${SHOW_SECRET_SNIPPETS}" == "true" ]]; then
3944
args+=("--show-secret-snippets")
4045
fi
4146

42-
output_formats=("human" "json" "sarif")
43-
if [[ ${output_formats[*]} =~ ${OUTPUT_FORMAT} ]]; then
44-
args+=("--format=${OUTPUT_FORMAT}")
47+
scan_formats=("human" "json" "sarif")
48+
if [[ ${scan_formats[*]} =~ ${SCAN_FORMAT} ]]; then
49+
args+=("--format=${SCAN_FORMAT}")
4550
else
46-
echo "+++ 🚨 Invalid Output Format: ${OUTPUT_FORMAT}"
47-
echo "Valid Formats: ${output_formats[*]}"
51+
echo "+++ 🚨 Invalid Scan Format: ${SCAN_FORMAT}"
52+
echo "Valid Formats: ${scan_formats[*]}"
4853
exit 1
4954
fi
5055

56+
# Define valid formats
57+
valid_file_formats=("human" "json" "sarif" "csv-zip")
58+
59+
# Default file output which is used for build annotation
60+
args+=("--output=/scan/result/output,human")
61+
62+
# Declare result array
63+
declare -a result
64+
65+
# Read file output formats into result array
66+
if plugin_read_list_into_result "BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT"; then
67+
declare -A seen_formats
68+
for format in "${result[@]}"; do
69+
# Multiple output files with the same format are supported
70+
# but would need to rework this loop to handle and validate i.e., specifying file names, etc.,
71+
# -o, --output file-outputs Output to file, can be passed multiple times to output to multiple files with possibly different formats.
72+
# Must be specified in the following format: file-path[,file-format[,policy-hits-only[,group-by[,include-audit-policy-hits]]]]
73+
# Options for file-format: [csv-zip, human, json, sarif], policy-hits-only: [true, false], group-by: [default, layer, resource], include-audit-policy-hits: [true, false]
74+
# Check for duplicates
75+
if [[ -n "${seen_formats[$format]:-}" ]]; then
76+
echo "+++ ⚠️ Duplicate file output format ignored: ${format}"
77+
continue
78+
fi
79+
seen_formats["$format"]=1
80+
81+
# Check for invalid formats
82+
if in_array "$format" "${valid_file_formats[@]}"; then
83+
args+=("--output=/scan/result/output-${format},${format}")
84+
else
85+
echo "+++ 🚨 Invalid File Output Format: ${format}"
86+
echo "Valid Formats: ${valid_file_formats[*]}"
87+
exit 1
88+
fi
89+
done
90+
fi
91+
5192
## IAC Scanning Parameters
5293

5394
if [[ "${SCAN_TYPE}" == "iac" ]]; then
@@ -133,7 +174,7 @@ dockerImageScan() {
133174
"${wiz_cli_container}" \
134175
docker scan --image "$IMAGE" \
135176
--policy-hits-only \
136-
-o /scan/result/output,human,true ${args:+"${args[@]}"}
177+
${args:+"${args[@]}"}
137178

138179
exit_code="$?"
139180
image_name=$(echo "$IMAGE" | cut -d "/" -f 2)
@@ -158,7 +199,6 @@ iacScan() {
158199
--mount type=bind,src="$PWD",dst=/scan \
159200
"${wiz_cli_container}" \
160201
iac scan \
161-
-o /scan/result/output,human \
162202
--name "$BUILDKITE_JOB_ID" \
163203
--path "/scan/$FILE_PATH" ${args:+"${args[@]}"}
164204

@@ -186,7 +226,6 @@ dirScan() {
186226
--mount type=bind,src="$PWD",dst=/scan \
187227
"${wiz_cli_container}" \
188228
dir scan \
189-
-o /scan/result/output,human \
190229
--name "$BUILDKITE_JOB_ID" \
191230
--path "/scan/$FILE_PATH" ${args:+"${args[@]}"}
192231

lib/shared.bash

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/bin/bash
2+
3+
# Show a prompt for a command
4+
function plugin_prompt() {
5+
if [[ -z "${HIDE_PROMPT:-}" ]] ; then
6+
echo -ne '\033[90m$\033[0m' >&2
7+
for arg in "${@}" ; do
8+
if [[ $arg =~ [[:space:]] ]] ; then
9+
echo -n " '$arg'" >&2
10+
else
11+
echo -n " $arg" >&2
12+
fi
13+
done
14+
echo >&2
15+
fi
16+
}
17+
18+
# Shorthand for reading env config
19+
function plugin_read_config() {
20+
local var="BUILDKITE_PLUGIN_WIZ_${1}"
21+
local default="${2:-}"
22+
echo "${!var:-$default}"
23+
}
24+
25+
# Reads either a value or a list from plugin config
26+
function plugin_read_list() {
27+
prefix_read_list "BUILDKITE_PLUGIN_WIZ_$1"
28+
}
29+
30+
# Reads either a value or a list from the given env prefix
31+
function prefix_read_list() {
32+
local prefix="$1"
33+
local parameter="${prefix}_0"
34+
35+
if [[ -n "${!parameter:-}" ]]; then
36+
local i=0
37+
local parameter="${prefix}_${i}"
38+
while [[ -n "${!parameter:-}" ]]; do
39+
echo "${!parameter}"
40+
i=$((i+1))
41+
parameter="${prefix}_${i}"
42+
done
43+
elif [[ -n "${!prefix:-}" ]]; then
44+
echo "${!prefix}"
45+
fi
46+
}
47+
48+
# Reads either a value or a list from plugin config into a global result array
49+
# Returns success if values were read
50+
function plugin_read_list_into_result() {
51+
local prefix="$1"
52+
local parameter="${prefix}_0"
53+
result=()
54+
55+
if [[ -n "${!parameter:-}" ]]; then
56+
local i=0
57+
local parameter="${prefix}_${i}"
58+
while [[ -n "${!parameter:-}" ]]; do
59+
result+=("${!parameter}")
60+
i=$((i+1))
61+
parameter="${prefix}_${i}"
62+
done
63+
elif [[ -n "${!prefix:-}" ]]; then
64+
result+=("${!prefix}")
65+
fi
66+
67+
[[ ${#result[@]} -gt 0 ]] || return 1
68+
}
69+
70+
function plugin_config_exists() {
71+
local var="BUILDKITE_PLUGIN_WIZ_${1}"
72+
73+
# Check if the variable is set
74+
[ "${!var+is_set}" != "" ]
75+
}
76+
77+
function in_array() {
78+
local e
79+
for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
80+
return 1
81+
}
82+
83+
# retry <number-of-retries> <command>
84+
function retry {
85+
local retries=$1; shift
86+
local attempts=1
87+
local status=0
88+
89+
until "$@"; do
90+
status=$?
91+
echo "Exited with $status"
92+
if (( retries == "0" )); then
93+
return $status
94+
elif (( attempts == retries )); then
95+
echo "Failed $attempts retries"
96+
return $status
97+
else
98+
echo "Retrying $((retries - attempts)) more times..."
99+
attempts=$((attempts + 1))
100+
sleep $(((attempts - 2) * 2))
101+
fi
102+
done
103+
}

plugin.yml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,20 @@ configuration:
1010
type: string
1111
image-address:
1212
type: string
13-
output-format:
13+
scan-format:
1414
type: string
1515
enum:
1616
- human
1717
- json
1818
- sarif
1919
default: human
20+
file-output-format:
21+
type: [string, array]
22+
enum:
23+
- human
24+
- json
25+
- sarif
26+
- csv-zip
2027
parameter-files:
2128
type: string
2229
path:
@@ -26,18 +33,18 @@ configuration:
2633
enum:
2734
- dir
2835
- docker
29-
- iac
36+
- iac
3037
show-secret-snippets:
3138
type: boolean
3239
default: false
3340
iac-type:
3441
type: string
3542
enum:
3643
- Ansible
37-
- AzureResourceManager
38-
- Cloudformation
39-
- Dockerfile
40-
- GoogleCloudDeploymentManager
44+
- AzureResourceManager
45+
- Cloudformation
46+
- Dockerfile
47+
- GoogleCloudDeploymentManager
4148
- Kubernetes
4249
- Terraform
4350
required:

tests/post-command.bats

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,62 @@ setup() {
104104
assert_failure
105105
}
106106

107-
@test "Invalid Output Format" {
107+
@test "Invalid Scan Format" {
108108
export WIZ_API_SECRET="secret"
109-
export BUILDKITE_PLUGIN_WIZ_OUTPUT_FORMAT="wrong-format"
109+
export BUILDKITE_PLUGIN_WIZ_SCAN_FORMAT="wrong-format"
110110

111111
run "$PWD/hooks/post-command"
112-
assert_output --partial "+++ 🚨 Invalid Output Format: $BUILDKITE_PLUGIN_WIZ_OUTPUT_FORMAT"
112+
assert_output --partial "+++ 🚨 Invalid Scan Format: $BUILDKITE_PLUGIN_WIZ_SCAN_FORMAT"
113113

114+
assert_failure
115+
}
116+
117+
@test "Invalid File Output Format" {
118+
export WIZ_API_SECRET="secret"
119+
export BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT="wrong-format"
120+
121+
run "$PWD/hooks/post-command"
122+
assert_output --partial "+++ 🚨 Invalid File Output Format: $BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT"
123+
124+
assert_failure
125+
}
126+
127+
@test "Invalid File Output Format (multiple)" {
128+
export WIZ_API_SECRET="secret"
129+
export BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT_0="human"
130+
export BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT_1="wrong-format"
131+
132+
run "$PWD/hooks/post-command"
133+
assert_output --partial "+++ 🚨 Invalid File Output Format: $BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT_1"
134+
135+
assert_failure
136+
}
137+
138+
@test "Duplicate File Output Formats" {
139+
export WIZ_API_SECRET="secret"
140+
export WIZ_API_ID="test"
141+
export BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT_0="human"
142+
export BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT_1="human"
143+
144+
stub docker : 'exit 0'
145+
mkdir -p "$WIZ_DIR"
146+
touch "$WIZ_DIR/key"
147+
148+
run "$PWD/hooks/post-command"
149+
assert_output --partial "+++ ⚠️ Duplicate file output format ignored: $BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT_1"
150+
151+
assert_success
152+
}
153+
154+
@test "Invalid File Output Format (multiple with duplicates)" {
155+
export WIZ_API_SECRET="secret"
156+
export BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT_0="human"
157+
export BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT_1="human"
158+
export BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT_2="wrong-format"
159+
160+
run "$PWD/hooks/post-command"
161+
assert_output --partial "+++ ⚠️ Duplicate file output format ignored: $BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT_1"
162+
assert_output --partial "+++ 🚨 Invalid File Output Format: $BUILDKITE_PLUGIN_WIZ_FILE_OUTPUT_FORMAT_2"
163+
114164
assert_failure
115165
}

0 commit comments

Comments
 (0)