diff --git a/incubating/kubescape/CHANGELOG.md b/incubating/kubescape/CHANGELOG.md new file mode 100644 index 000000000..6661463f0 --- /dev/null +++ b/incubating/kubescape/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## [1.0.0] - 2024-01-22 + +Original version diff --git a/incubating/kubescape/Dockerfile b/incubating/kubescape/Dockerfile new file mode 100644 index 000000000..aa167d80d --- /dev/null +++ b/incubating/kubescape/Dockerfile @@ -0,0 +1,8 @@ +FROM quay.io/kubescape/kubescape-cli:v3.0.1 + +# Kubescape uses root privileges for writing the results to a file +USER root + +COPY entrypoint.sh /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/incubating/kubescape/README.md b/incubating/kubescape/README.md new file mode 100644 index 000000000..fa309c519 --- /dev/null +++ b/incubating/kubescape/README.md @@ -0,0 +1,11 @@ +# kubescape CLI + +Docker image which invokes security script using kubescape CLI + +### Prerequisites: + +Codefresh Subscription (Dedicated Infrastructure/Hybrid) - https://codefresh.io/ + +### Documentation: + +kubescape CLI: https://github.com/kubescape/kubescape/blob/master/docs/getting-started.md diff --git a/incubating/kubescape/entrypoint.sh b/incubating/kubescape/entrypoint.sh new file mode 100755 index 000000000..0c413a415 --- /dev/null +++ b/incubating/kubescape/entrypoint.sh @@ -0,0 +1,171 @@ +#!/busybox/sh + +# Checks if `string` contains `substring`. +# +# Arguments: +# String to check. +# +# Returns: +# 0 if `string` contains `substring`, otherwise 1. +contains() { + case "$1" in + *$2*) return 0 ;; + *) return 1 ;; + esac +} + +set -e + +# Kubescape uses the client name to make a request for checking for updates +export KS_CLIENT="codefresh" + +if [ -n "${FRAMEWORKS}" ] && [ -n "${CONTROLS}" ]; then + echo "Framework and Control are specified. Please specify either one of them" + exit 1 +fi + +if [ -z "${FRAMEWORKS}" ] && [ -z "${CONTROLS}" ] && [ -z "${IMAGE}" ]; then + echo "Neither Framework, Control nor image are specified. Please specify one of them" + exit 1 +fi + + +if [ -n "${FRAMEWORKS}" ] && [ -n "${IMAGE}" ] || [ -n "${CONTROLS}" ] && [ -n "${IMAGE}" ] ; then + errmsg="Image and Framework / Control are specified. Kubescape does not support scanning both at the moment." + errmsg="${errmsg} Please specify either one of them or neither." + echo "${errmsg}" + exit 1 +fi + +if [ -n "${IMAGE}" ] && [ "${FIXFILES}" = "true" ]; then + errmsg="The run requests both an image scan and file fix suggestions. Kubescape does not support fixing image scan results at the moment." + errmsg="${errmsg} Please specify either one of them or neither." + echo "${errmsg}" + exit 1 +fi + +# Split the controls by comma and concatenate with quotes around each control +if [ -n "${CONTROLS}" ]; then + controls="" + set -f + IFS=',' + set -- "${CONTROLS}" + set +f + unset IFS + for control in "$@"; do + control=$(echo "${control}" | xargs) # Remove leading/trailing whitespaces + controls="${controls}\"${control}\"," + done + controls=$(echo "${controls%?}") +fi + +frameworks_cmd=$([ -n "${FRAMEWORKS}" ] && echo "framework ${FRAMEWORKS}" || echo "") +controls_cmd=$([ -n "${CONTROLS}" ] && echo control "${controls}" || echo "") + +scan_input=$([ -n "${FILES}" ] && echo "${FILES}" || echo .) + +output_formats="${FORMAT}" +have_json_format="false" +if [ -n "${output_formats}" ] && contains "${output_formats}" "json"; then + have_json_format="true" +fi + +verbose="" +if [ -n "${VERBOSE}" ] && [ "${VERBOSE}" != "false" ]; then + verbose="--verbose" +fi + +exceptions="" +if [ -n "$EXCEPTIONS" ]; then + exceptions="--exceptions ${EXCEPTIONS}" +fi + +controls_config="" +if [ -n "$CONTROLSCONFIG" ]; then + controls_config="--controls-config ${CONTROLSCONFIG}" +fi + +should_fix_files="false" +if [ "${FIXFILES}" = "true" ]; then + should_fix_files="true" +fi + +# If a user requested Kubescape to fix their files, but forgot to ask for JSON +# output, do it for them +if [ "${should_fix_files}" = "true" ] && [ "${have_json_format}" != "true" ]; then + output_formats="${output_formats},json" +fi + +output_file=$([ -n "${OUTPUTFILE}" ] && echo "${OUTPUTFILE}" || echo "results") + +account_opt=$([ -n "${ACCOUNT}" ] && echo --account "${ACCOUNT}" || echo "") +access_key_opt=$([ -n "${ACCESSKEY}" ] && echo --access-key "${ACCESSKEY}" || echo "") +server_opt=$([ -n "${SERVER}" ] && echo --server "${SERVER}" || echo "") + +# If account ID is empty, we load artifacts from the local path, otherwise we +# load from the cloud (this will enable custom framework support) +artifacts_path="/home/ks/.kubescape" +artifacts_opt=$([ -n "${ACCOUNT}" ] && echo "" || echo --use-artifacts-from "${artifacts_path}") + +if [ -n "${FAILEDTHRESHOLD}" ] && [ -n "${COMPLIANCETHRESHOLD}" ]; then + echo "Both failedThreshold and complianceThreshold are specified. Please specify either one of them or neither" + exit 1 +fi + +fail_threshold_opt=$([ -n "${FAILEDTHRESHOLD}" ] && echo --fail-threshold "${FAILEDTHRESHOLD}" || echo "") +compliance_threshold_opt=$([ -n "${COMPLIANCETHRESHOLD}" ] && echo --compliance-threshold "${COMPLIANCETHRESHOLD}" || echo "") + +# When a user requests to fix files, the action should not fail because the +# results exceed severity. This is subject to change in the future. +severity_threshold_opt=$( + [ -n "${SEVERITYTHRESHOLD}" ] && + [ "${should_fix_files}" = "false" ] && + echo --severity-threshold "${SEVERITYTHRESHOLD}" || + echo "" +) + +# Handle image scanning request +image_subcmd="" +echo "image is <${IMAGE}>" +if [ -n "${IMAGE}" ]; then + + # By default, assume we are not authenticated. This means we can pull public + # images from the container runtime daemon + image_arg="${IMAGE}" + + severity_threshold_opt=$( + [ -n "${SEVERITYTHRESHOLD}" ] && + echo --severity-threshold "${SEVERITYTHRESHOLD}" || + echo "" + ) + + auth_opts="" + if [ -n "${REGISTRYUSERNAME}" ] && [ -n "${REGISTRYPASSWORD}" ]; then + auth_opts="--username=${REGISTRYUSERNAME} --password=${REGISTRYPASSWORD}" + + # When trying to authenticate, we cannot assume that the runner has access + # to an *authenticated* container runtime daemon, so we should always try + # to pull images from the registry + image_arg="registry://${image_arg}" + else + echo "NOTICE: Received no registry credentials, pulling without authentication." + printf "Hint: If you provide credentials, make sure you include both the username and password.\n\n" + fi + + # Build the image scanning subcommand with options + image_subcmd="image ${auth_opts}" + # Override the scan input + scan_input="${image_arg}" + echo "Scan subcommand: ${image_subcmd}" +fi + +# TODO: include artifacts_opt once https://github.com/kubescape/kubescape/issues/1040 is resolved +scan_command="kubescape scan ${image_subcmd} ${frameworks_cmd} ${controls_cmd} ${scan_input} ${account_opt} ${access_key_opt} ${server_opt} ${fail_threshold_opt} ${compliance_threshold_opt} ${severity_threshold_opt} --format ${output_formats} --output ${output_file} ${verbose} ${exceptions} ${controls_config}" + +echo "${scan_command}" +eval "${scan_command}" + +if [ "$should_fix_files" = "true" ]; then + fix_command="kubescape fix --no-confirm ${output_file}.json" + eval "${fix_command}" +fi diff --git a/incubating/kubescape/icon.png b/incubating/kubescape/icon.png new file mode 100644 index 000000000..e93cda3fc Binary files /dev/null and b/incubating/kubescape/icon.png differ diff --git a/incubating/kubescape/step.yaml b/incubating/kubescape/step.yaml new file mode 100644 index 000000000..3a4118f65 --- /dev/null +++ b/incubating/kubescape/step.yaml @@ -0,0 +1,146 @@ +kind: step-type +version: '1.0' +metadata: + name: kubescape + version: 3.0.1 + title: Run a kubescape security scan + isPublic: true + description: Scan a cluster with the kubescape security service. + sources: + - 'https://github.com/codefresh-io/steps/tree/master/incubating/kubescape' + stage: incubating + maintainers: + - name: Laurent Rochette + email: laurent.rochette@codefresh.io + - name: Matthias Bertschy + email: matthiasb@armosec.io + categories: + - security + official: true + tags: [] + icon: + type: image + size: + large: + url: >- + https://cdn.jsdelivr.net/gh/codefresh-io/steps/incubating/kubescape/icon.png + examples: + - description: example-1 + workflow: + kubescape: + type: kubescape:3.0.1 + arguments: + VERBOSE: on + OUTPUTFILE: results + FRAMEWORKS: MITRE + ACCESSKEY: ${{KEY}} + ACCOUNT: ${{ACCOUNT_ID}} + FORMAT: sarif + +spec: + arguments: |- + { + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "patterns": [], + "required": [], + "properties": { + "FILES": { + "type": "string", + "description": "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. Default is '.' which scans the whole repository" + }, + "OUTPUTFILE": { + "type": "string", + "description": "Name of the output file where the scan result will be stored without the extension. Default is 'result'" + }, + "FRAMEWORKS": { + "type": "string", + "description": "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 to get a list of all frameworks. Either frameworks have to be specified or controls." + }, + "CONTROLS": { + "type": "string", + "description": "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 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." + }, + "ACCOUNT": { + "type": "string", + "description": "account ID for integrating with a third-party server" + }, + "ACCESSKEY": { + "type": "string", + "description": "access-key for integrating with a third-party server" + }, + "SERVER": { + "type": "string", + "description": "URL for integrating with a third-party server" + }, + "FAILEDTHRESHOLD": { + "type": "string", + "description": "Failure threshold is the percent above which the command fails and returns exit code 1. Default is 0 i.e, action fails if any control fails" + }, + "SEVERITYTHRESHOLD": { + "type": "string", + "description": "Severity threshold is the severity of a failed control at or above which the command terminates with an exit code 1. Default is 'high', i.e. the action fails if any High severity control fails" + }, + "COMPLIANCETHRESHOLD": { + "type": "string", + "description": "Compliance threshold is the percent bellow which the command fails and returns exit code 1 (example: if set to 100 the command will fail if any control fails)" + }, + "VERBOSE": { + "type": "string", + "description": "on|off - Display all of the input resources and not only failed resources. Default is 'off'" + }, + "EXCEPTIONS": { + "type": "string", + "description": "The JSON file containing at least one resource and one policy. Refer exceptions docs for more info. Objects with exceptions will be presented as exclude and not fail." + }, + "FORMAT": { + "type": "string", + "description": "Output format. Can take one or more formats, comma separated", + "default": "junit" + }, + "CONTROLSCONFIG": { + "type": "string", + "description": "The file containing controls configuration. Use 'kubescape download controls-inputs' to download the configured controls-inputs." + }, + "FIXFILES": { + "type": "string", + "default": "false", + "description": "Whether Kubescape will automatically fix files or not. If enabled, Kubescape will make fixes to the input files. You can then use these fixes to open Pull Requests from your CI/CD pipeline." + }, + "IMAGE": { + "type": "string", + "description": "The image you wish to scan. Launches an image scan, which cannot run together with configuration scans." + }, + "REGISTRYUSERNAME": { + "type": "string", + "description": "Username to a private registry that hosts the scanned image." + }, + "REGISTRYPASSWORD": { + "type": "string", + "description": "Password to a private registry that hosts the scanned image." + }, + "KS_IMAGE": { + "type": "string", + "default": "quay.io/codefreshplugins/kubescape", + "description": "Kubescape image to use" + }, + "KS_IMAGE_VERSION": { + "type": "string", + "default": "3.0.1", + "description": "Version of the kubescape image to use" + } + } + } + stepsTemplate: |- + kubescan: + image: '[[.Arguments.KS_IMAGE]]:[[.Arguments.KS_IMAGE_VERSION]]' + title: kubescape scan + environment: + [[ range $key, $val := .Arguments ]] + - '[[ $key ]]=[[ $val ]]' + [[- end ]] + delimiters: + left: '[[' + right: ']]'