Skip to content

Commit 1e1cf90

Browse files
vladklokunDavid Wertenteil
andauthored
feat: support autofixing (#28)
* feat: support autofixing files This change adds support for autofixing files. This is achieved by expanding the script that runs as the Docker entrypoint to run the `kubescape fix` command if a user requests it. It also validates that the prerequisites to fix the files are met: a scan must include a JSON output. * fix: use proper input variable * chore!: remove artifacts to test * fix: specify JSON format for fixing files * refactor: use proper string non-zero checks * refactor: implement most ShellCheck suggestions * style: lowercase all script-local variables * style: use curly brackets for variable expansion * refactor: use native mechanism to skip confirmation * feat: automatically add JSON to formats when fixes are requested * feat: do not enforce severity threshold if fixes requested * docs: document how to use autofixing * docs: update `outputFile` usage for multiple outputs This change specifies that `outputFile` should omit the extension. * docs: mention artifacts TODO in entrypoint * fix: do not suggest autofixes on push in README Co-authored-by: David Wertenteil <dwertent@armosec.io> * fix: do not suggest fixes on push in example workflow * docs: mention the autofixing workflow limitations Co-authored-by: David Wertenteil <dwertent@armosec.io>
1 parent a978d98 commit 1e1cf90

4 files changed

Lines changed: 186 additions & 37 deletions

File tree

.github/workflows/example.yaml

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,40 @@ jobs:
1717
- name: Upload Kubescape scan results to Github Code Scanning
1818
uses: github/codeql-action/upload-sarif@v2
1919
with:
20-
sarif_file: results.sarif
20+
sarif_file: results.sarif
21+
22+
---
23+
24+
name: Suggest autofixes with Kubescape
25+
on: [pull_request]
26+
jobs:
27+
kubescape:
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v3
31+
with:
32+
fetch-depth: 0
33+
- name: Get changed files
34+
id: changed-files
35+
uses: tj-actions/changed-files@v14.6
36+
- uses: kubescape/github-action@main
37+
with:
38+
account: ${{secrets.KUBESCAPE_ACCOUNT}}
39+
files: ${{ steps.changed-files.outputs.all_changed_files }}
40+
fixFiles: true
41+
format: "sarif"
42+
- uses: peter-evans/create-pull-request@v4
43+
with:
44+
add-paths: |
45+
*.yaml
46+
commit-message: "chore: fix K8s misconfigurations"
47+
title: "[Kubescape] chore: fix K8s misconfigurations"
48+
body: |
49+
# What this PR changes
50+
51+
[Kubescape](https://github.com/kubescape/kubescape) has found misconfigurations in the targeted branch. This PR fixes the misconfigurations that have automatic fixes available.
52+
53+
You may still need to fix misconfigurations that do not have automatic fixes.
54+
base: ${{ github.head_ref }}
55+
branch: kubescape-auto-fix-${{ github.head_ref || github.ref_name }}
56+
delete-branch: true

README.md

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ You need to make sure that workflows have [Read and write permissions](https://d
77

88
## Usage
99

10+
### Scanning with Kubescape
11+
1012
To scan your repository with [Kubescape in your Github workflow](https://www.armosec.io/blog/kubescape-now-integrates-with-github-actions/?utm_source=github&utm_medium=repository), add the following steps to your workflow configuration:
1113

1214
```yaml
@@ -21,7 +23,7 @@ jobs:
2123
continue-on-error: true
2224
with:
2325
format: sarif
24-
outputFile: results.sarif
26+
outputFile: results
2527
# # Optional: Specify the Kubescape cloud account ID
2628
# account: ${{secrets.KUBESCAPE_ACCOUNT}}
2729
# # Optional: Scan a specific path. Default will scan the whole repository
@@ -35,12 +37,56 @@ jobs:
3537
This workflow definition scans your repository with Kubescape and publishes the results to Github.
3638
You can then see the results in the Pull Request that triggered the scan and the _Security → Code scanning_ tab.
3739
40+
### Automatically Suggest Fixes
41+
42+
To make Kubescape automatically suggest fixes to your pushes and pull requests, use the following workflow:
43+
44+
```yaml
45+
name: Suggest autofixes with Kubescape
46+
on: [pull_request]
47+
jobs:
48+
kubescape:
49+
runs-on: ubuntu-latest
50+
steps:
51+
- uses: actions/checkout@v3
52+
with:
53+
fetch-depth: 0
54+
- name: Get changed files
55+
id: changed-files
56+
uses: tj-actions/changed-files@v14.6
57+
- uses: kubescape/github-action@main
58+
with:
59+
account: ${{secrets.KUBESCAPE_ACCOUNT}}
60+
files: ${{ steps.changed-files.outputs.all_changed_files }}
61+
fixFiles: true
62+
format: "sarif"
63+
- uses: peter-evans/create-pull-request@v4
64+
with:
65+
add-paths: |
66+
*.yaml
67+
commit-message: "chore: fix K8s misconfigurations"
68+
title: "[Kubescape] chore: fix K8s misconfigurations"
69+
body: |
70+
# What this PR changes
71+
72+
[Kubescape](https://github.com/kubescape/kubescape) has found misconfigurations in the targeted branch. This PR fixes the misconfigurations that have automatic fixes available.
73+
74+
You may still need to fix misconfigurations that do not have automatic fixes.
75+
base: ${{ github.head_ref }}
76+
branch: kubescape-auto-fix-${{ github.head_ref || github.ref_name }}
77+
delete-branch: true
78+
```
79+
80+
Please note that since Kubescape provides automatic fixes only to the rendered YAML manifests, the workflow above will not produce correct fixes for Helm charts.
81+
82+
The next important thing to note is that Kubescape only fixes the files. It does not open pull requests on its own. In the example above, a separate step that runs a different action opens the appropriate pull request. Due to how Github works, there are [limitations](https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs) on running and opening pull requests to forks. The action running in this step is maintained by its respective maintainers, and not the Kubescape team, so you should review its documentation when troubleshooting the process of triggering the workflow run and opening pull requests.
83+
3884
## Inputs
3985
4086
| Name | Description | Required |
4187
| --- | --- | ---|
4288
| files | YAML files or Helm charts to scan for misconfigurations. The files need to be provided with the complete path from the root of the repository. | No (default is `.` which scans the whole repository) |
43-
| outputFile | Name of the output file where the scan result will be stored. | No (default is `results.out`) |
89+
| outputFile | Name of the output file where the scan result will be stored without the extension. | No (default is `results`) |
4490
| frameworks | Security framework(s) to scan the files against. Multiple frameworks can be specified separated by a comma with no spaces. Example - `nsa,devopsbest`. Run `kubescape list frameworks` in the [Kubescape CLI](https://hub.armo.cloud/docs/installing-kubescape) to get a list of all frameworks. Either frameworks have to be specified or controls. | No |
4591
| controls | Security control(s) to scan the files against. Multiple controls can be specified separated by a comma with no spaces. Example - `Configured liveness probe,Pods in default namespace`. Run `kubescape list controls` in the [Kubescape CLI](https://hub.armo.cloud/docs/installing-kubescape) to get a list of all controls. You can use either the complete control name or the control ID such as `C-0001` to specify the control you want use. You must specify either the control(s) or the framework(s) you want used in the scan. | No |
4692
| account | Account ID for [Kubescape cloud](https://cloud.armosec.io/). Used for custom configuration, such as frameworks, control configuration, etc. | No |
@@ -64,7 +110,7 @@ jobs:
64110
continue-on-error: true
65111
with:
66112
format: sarif
67-
outputFile: results.sarif
113+
outputFile: results
68114
# Specify the Kubescape cloud account ID
69115
account: ${{secrets.KUBESCAPE_ACCOUNT}}
70116
- name: Upload Kubescape scan results to Github Code Scanning
@@ -89,7 +135,7 @@ jobs:
89135
continue-on-error: true
90136
with:
91137
format: sarif
92-
outputFile: results.sarif
138+
outputFile: results
93139
# Scan a specific path. Default will scan the whole repository
94140
files: "examples/kubernetes-manifests/*.yaml"
95141
- name: Upload Kubescape scan results to Github Code Scanning
@@ -114,7 +160,7 @@ jobs:
114160
continue-on-error: true
115161
with:
116162
format: sarif
117-
outputFile: results.sarif
163+
outputFile: results
118164
frameworks: |
119165
nsa,mitre
120166
- name: Upload Kubescape scan results to Github Code Scanning
@@ -139,7 +185,7 @@ jobs:
139185
continue-on-error: false
140186
with:
141187
format: sarif
142-
outputFile: results.sarif
188+
outputFile: results
143189
failedThreshold: 50
144190
- name: Upload Kubescape scan results to Github Code Scanning
145191
uses: github/codeql-action/upload-sarif@v2
@@ -162,7 +208,7 @@ jobs:
162208
continue-on-error: false
163209
with:
164210
format: sarif
165-
outputFile: results.sarif
211+
outputFile: results
166212
severityThreshold: medium
167213
- name: Upload Kubescape scan results to Github Code Scanning
168214
uses: github/codeql-action/upload-sarif@v2

action.yml

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ inputs:
1919
required: false
2020
outputFile:
2121
description: |
22-
Name of the output file. Default is "results.out".
22+
Name of the output file, without the extension.
23+
24+
Default is "results".
25+
26+
Kubescape adds the appropriate extension automatically to support both
27+
single and multiple output formats.
2328
required: false
2429
frameworks:
2530
description: |
@@ -42,10 +47,26 @@ inputs:
4247
description: |
4348
Output format.
4449
45-
Can accept multiple comma-separated formats, e.g. "sarif,json,html"
46-
Run `kubescape scan -h` for listing supported formats
50+
Can take one or more formats. To use one format, omit the comma, e.g
51+
`format: json`. To produce results in multiple formats, separate them with
52+
a comma: `format: sarif,json`.
53+
54+
For example, when using `output: "results"` and `format: "sarif,json"`,
55+
Kubescape will produce 2 files: `results.sarif` and `results.json`. You
56+
can then use `results.sarif` to publish results to Github Code Scanning
57+
and `results.json` to suggest automatic fixes.
58+
59+
Run `kubescape scan -h` to see a list of supported formats.
4760
required: false
4861
default: junit
62+
fixFiles:
63+
description: |
64+
Whether Kubescape will automatically fix files or not.
65+
66+
If enabled, Kubescape will make fixes to the input files. You can then
67+
use these fixes to open Pull Requests from your CI/CD pipeline.
68+
required: false
69+
default: false
4970
runs:
5071
using: docker
5172
image: Dockerfile

entrypoint.sh

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,96 @@
11
#!/bin/sh
22

3+
# Checks if `string` contains `substring`.
4+
#
5+
# Arguments:
6+
# String to check.
7+
#
8+
# Returns:
9+
# 0 if `string` contains `substring`, otherwise 1.
10+
contains() {
11+
case "$1" in
12+
*$2*) return 0 ;;
13+
*) return 1 ;;
14+
esac
15+
}
16+
317
set -e
418

5-
# Declear ks client
19+
# Kubescape uses the client name to make a request for checking for updates
620
export KS_CLIENT="github_actions"
721

8-
if [ ! -z "$INPUT_FRAMEWORKS" ] && [ ! -z "$INPUT_CONTROLS" ]; then
9-
echo "Framework and Control is specified. Please specify either one of them or neither"
10-
exit 1
22+
if [ -n "${INPUT_FRAMEWORKS}" ] && [ -n "${INPUT_CONTROLS}" ]; then
23+
echo "Framework and Control is specified. Please specify either one of them or neither"
24+
exit 1
1125
fi
1226

1327
# Split the controls by comma and concatenate with quotes around each control
14-
if [ ! -z "$INPUT_CONTROLS" ]; then
15-
CONTROLS=""
28+
if [ -n "${INPUT_CONTROLS}" ]; then
29+
controls=""
1630
set -f; IFS=','
17-
set -- $INPUT_CONTROLS
31+
set -- "${INPUT_CONTROLS}"
1832
set +f; unset IFS
1933
for control in "$@"
2034
do
21-
control=$(echo $control | xargs) # Remove leading/trailing whitespaces
22-
CONTROLS="$CONTROLS\"$control\","
35+
control=$(echo "${control}" | xargs) # Remove leading/trailing whitespaces
36+
controls="${controls}\"${control}\","
2337
done
24-
CONTROLS=$(echo "${CONTROLS%?}")
38+
controls=$(echo "${controls%?}")
39+
fi
40+
41+
frameworks_cmd=$([ -n "${INPUT_FRAMEWORKS}" ] && echo "framework ${INPUT_FRAMEWORKS}" || echo "")
42+
controls_cmd=$([ -n "${INPUT_CONTROLS}" ] && echo control "${controls}" || echo "")
43+
44+
files=$([ -n "${INPUT_FILES}" ] && echo "${INPUT_FILES}" || echo .)
45+
46+
output_formats="${INPUT_FORMAT}"
47+
have_json_format="false"
48+
if [ -n "${output_formats}" ] && contains "${output_formats}" "json"; then
49+
have_json_format="true"
2550
fi
2651

27-
# Subcommands
28-
ARTIFACTS_PATH="/home/ks/.kubescape"
29-
FRAMEWORKS_CMD=$([ ! -z "$INPUT_FRAMEWORKS" ] && echo "framework $INPUT_FRAMEWORKS" || echo "")
30-
CONTROLS_CMD=$([ ! -z "$INPUT_CONTROLS" ] && echo control $CONTROLS || echo "")
52+
should_fix_files="false"
53+
if [ "${INPUT_FIXFILES}" = "true" ]; then
54+
should_fix_files="true"
55+
fi
56+
57+
# If a user requested Kubescape to fix their files, but forgot to ask for JSON
58+
# output, do it for them
59+
if [ "${should_fix_files}" = "true" ] && [ "${have_json_format}" != "true" ]; then
60+
output_formats="${output_formats},json"
61+
fi
3162

32-
# Files to scan
33-
FILES=$([ ! -z "$INPUT_FILES" ] && echo "$INPUT_FILES" || echo .)
63+
output_file=$([ -n "${INPUT_OUTPUTFILE}" ] && echo "${INPUT_OUTPUTFILE}" || echo "results")
3464

35-
# Output file name
36-
OUTPUT_FILE=$([ ! -z "$INPUT_OUTPUTFILE" ] && echo "$INPUT_OUTPUTFILE" || echo "results.out")
65+
account_opt=$([ -n "${INPUT_ACCOUNT}" ] && echo --account "${INPUT_ACCOUNT}" || echo "")
3766

38-
# Command-line options
39-
ACCOUNT_OPT=$([ ! -z "$INPUT_ACCOUNT" ] && echo --account $INPUT_ACCOUNT || echo "")
67+
# If account ID is empty, we load artifacts from the local path, otherwise we
68+
# load from the cloud (this will enable custom framework support)
69+
artifacts_path="/home/ks/.kubescape"
70+
artifacts_opt=$([ -n "${INPUT_ACCOUNT}" ] && echo "" || echo --use-artifacts-from "${artifacts_path}")
4071

41-
# If account ID is empty, we load artifacts from the local path, otherwise we load from the cloud (this will enable custom framework support)
42-
ARTIFACTS=$([ ! -z "$INPUT_ACCOUNT" ] && echo "" || echo --use-artifacts-from $ARTIFACTS_PATH)
72+
fail_threshold_opt=$([ -n "${INPUT_FAILEDTHRESHOLD}" ] && echo --fail-threshold "${INPUT_FAILEDTHRESHOLD}" || echo "")
4373

44-
FAIL_THRESHOLD_OPT=$([ ! -z "$INPUT_FAILEDTHRESHOLD" ] && echo --fail-threshold $INPUT_FAILEDTHRESHOLD || echo "")
45-
SEVERITY_THRESHOLD_OPT=$([ ! -z "$INPUT_SEVERITYTHRESHOLD" ] && echo --severity-threshold $INPUT_SEVERITYTHRESHOLD || echo "")
74+
# When a user requests to fix files, the action should not fail because the
75+
# results exceed severity. This is subject to change in the future.
76+
severity_threshold_opt=$(\
77+
[ -n "${INPUT_SEVERITYTHRESHOLD}" ] \
78+
&& [ "${should_fix_files}" = "false" ] \
79+
&& echo --severity-threshold "${INPUT_SEVERITYTHRESHOLD}" \
80+
|| echo "" \
81+
)
4682

47-
COMMAND="kubescape scan $FRAMEWORKS_CMD $CONTROLS_CMD $FILES $ACCOUNT_OPT $FAIL_THRESHOLD_OPT $SEVERITY_THRESHOLD_OPT --format $INPUT_FORMAT --output $OUTPUT_FILE $ARTIFACTS"
83+
# The `kubescape fix` subcommand requires the latest "json" format version.
84+
# Other formats ignore this flag.
85+
format_version_opt="--format-version v2"
4886

49-
eval $COMMAND
87+
# TODO: include artifacts_opt once https://github.com/kubescape/kubescape/issues/1040 is resolved
88+
scan_command="kubescape scan ${frameworks_cmd} ${controls_cmd} ${files} ${account_opt} ${fail_threshold_opt} ${severity_threshold_opt} --format ${output_formats} ${format_version_opt} --output ${output_file}"
5089

90+
echo "${scan_command}"
91+
eval "${scan_command}"
92+
93+
if [ "$should_fix_files" = "true" ]; then
94+
fix_command="kubescape fix --no-confirm ${output_file}.json"
95+
eval "${fix_command}"
96+
fi

0 commit comments

Comments
 (0)