Compare commits

..

102 Commits

Author SHA1 Message Date
David Wertenteil
955d6751a9 Merge pull request #956 from kubescape/dev
Enhance `host-scanner`
2022-12-08 22:51:26 +02:00
David Wertenteil
30c43bff10 Merge pull request #958 from Moshe-Rappaport-CA/dev
Fix Junit format
2022-12-08 19:41:31 +02:00
Moshe-Rappaport-CA
e009244566 Fix Junit format 2022-12-08 17:56:16 +02:00
David Wertenteil
3d3cd2c2d8 Added Kubescape flow.drawio 2022-12-06 15:44:34 +02:00
David Wertenteil
f5498371ec Merge pull request #942 from kooomix/eran-dev
new host-scanner endpoint - cloudProviderInfo
2022-12-06 15:20:24 +02:00
David Wertenteil
c3b95bed8c Merge branch 'dev' into eran-dev 2022-12-06 14:17:49 +02:00
David Wertenteil
8ce7d6c0f6 Merge pull request #930 from JusteenR/issue929
Issue929
2022-12-06 14:15:35 +02:00
David Wertenteil
e875f429a9 Merge pull request #948 from YiscahLevySilas1/dev
Print host scanner version
2022-12-06 14:13:47 +02:00
David Wertenteil
b6beff0488 Merge pull request #946 from suhasgumma/dev
Fixed: CIS control link not working for html output format
2022-12-06 14:13:06 +02:00
David Wertenteil
60c69ac3f0 Merge pull request #950 from fredbi/fix-789
fix(giturlparse): fixes panic on unexpected gitlab remote URL
2022-12-06 14:12:25 +02:00
David Wertenteil
1fb9320421 Merge pull request #941 from dwertent/master
Updating examples
2022-12-06 14:11:07 +02:00
David Wertenteil
9a176f6667 remove tag latest 2022-12-06 11:42:34 +02:00
David Wertenteil
96ea9a9e42 fixed scanning example 2022-12-06 11:41:12 +02:00
David Wertenteil
e39fca0c11 do not build dev images 2022-12-06 11:05:21 +02:00
David Wertenteil
2ec035005d fixed echo command 2022-12-04 15:45:23 +02:00
Frederic BIDON
b734b3aef0 go mod tidy ancillary modules manifest
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
2022-12-04 12:39:34 +01:00
yiscah
0f5635f42d move parsing of version to GetVersion 2022-12-04 12:17:04 +02:00
Frederic BIDON
8557075b7c fix(giturlparse): fixes panic on unexpected gitlab remote URL
* replaced dependencies to github.com/armosec/go-git-url by
github.com/kubescape/go-git-url
* fixes #789

NOTE: this requires kubescape/go-git-url#2 to be merged, a new release
of that repo to be cut, in order to finalize the dependency update.

Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
2022-12-02 16:09:25 +01:00
David Wertenteil
bc0f0e7087 Merge branch 'master' of github.com:dwertent/kubescape 2022-12-02 02:31:14 +02:00
David Wertenteil
8ce5f9aea3 fixed typo 2022-12-02 02:30:35 +02:00
David Wertenteil
050f9d3a4e Update cmd/scan/framework.go
Co-authored-by: craigbox <craig.box@gmail.com>
2022-12-02 02:16:43 +02:00
David Wertenteil
a81bf0deb4 deprecate set-output 2022-12-02 01:43:45 +02:00
David Wertenteil
2059324c27 testing release 2022-12-02 01:35:57 +02:00
David Wertenteil
a09a0a1bca Merge pull request #9 from dwertent/fix-dev-image
run build only if secret is set
2022-12-02 01:32:26 +02:00
David Wertenteil
83712bb9f5 run build only if secret is set 2022-12-02 01:30:24 +02:00
David Wertenteil
728ae47b9a Merge pull request #8 from dwertent/fix-dev-image
Fix dev image
2022-12-02 00:56:12 +02:00
David Wertenteil
2a9b272a14 tagging only main image as latest 2022-12-02 00:54:03 +02:00
David Wertenteil
8662deac43 update repository scanning URL 2022-12-02 00:42:28 +02:00
yiscah
e42644bbd8 update hostscanner version 2022-12-01 08:57:58 +02:00
YiscahLevySilas1
07d30b6272 Merge branch 'kubescape:dev' into dev 2022-11-30 20:52:48 +02:00
yiscah
2a4f8543cc added logs of host scanner version 2022-11-30 20:51:45 +02:00
suhasgumma
186b293cce fix link for cis controls in html output 2022-11-30 01:23:45 +05:30
David Wertenteil
2bfe72f39d Merge pull request #944 from kooomix/dev
opa-utils adjustments + dataControlInputs support
2022-11-29 19:11:49 +02:00
kooomix
f99f955223 go mod tidy 2022-11-29 15:26:30 +02:00
kooomix
ec56e69a3c minor fix 2022-11-29 14:55:30 +02:00
kooomix
3942583b1d Merge pull request #1 from kooomix/dataControlInputs
update opa-utils functions
2022-11-29 14:35:08 +02:00
kooomix
a10b15ba4b update opa-utils functions 2022-11-29 14:29:33 +02:00
David Wertenteil
5003cbd7a8 Merge pull request #943 from suhasgumma/invalidformat
Handle Invalid Formats
2022-11-28 17:39:14 +02:00
kooomix
481a137c23 Update host-scanner image version to v1.0.38 2022-11-28 16:46:32 +02:00
suhasgumma
c3f7f0938d Handle Invalid Formats 2022-11-28 19:56:27 +05:30
kooomix
b1925fa38d Support in new host-scanner endpoint - cloudProviderInfo 2022-11-28 09:18:43 +02:00
David Wertenteil
d9f8a7a46f Merge pull request #918 from suhasgumma/dev1
Store Git Repo's root path as localRootPath
2022-11-27 16:25:24 +02:00
David Wertenteil
846a072bf9 Merge pull request #917 from suhasgumma/dev
Fixed: Wrong Relative Path When scanning Local Directory
2022-11-27 16:24:19 +02:00
kooomix
5dd7bbd8a7 Merge pull request #938 from kooomix/eran-dev
Added cloudProvider to postureControlInputs
2022-11-27 09:06:56 +02:00
kooomix
e1773acf24 Getting cloud provider from gitversion of discovered API version 2022-11-25 09:27:27 +02:00
kooomix
03a0f97669 Getting cluster name from context 2022-11-24 16:09:05 +02:00
David Wertenteil
917a3f41e8 Merge pull request #925 from amirmalka/dev
Omit raw resources flag in json output
2022-11-24 14:47:14 +02:00
David Wertenteil
3c8da1b299 supporting client type from env 2022-11-24 11:09:30 +02:00
David Wertenteil
c61c7edbd0 update examples 2022-11-24 11:06:37 +02:00
kooomix
53402d9a1c Added "CloudProvider" to postureControlInputs 2022-11-23 11:57:36 +02:00
David Wertenteil
de9278b388 Merge pull request #935 from mkilchhofer/bugfix/use_correct_directory
fix: filepath.Dir requires trailing slash
2022-11-23 10:49:16 +02:00
Marco Kilchhofer
4fef6200f8 fix: filepath.Dir requires trailing slash
Signed-off-by: Marco Kilchhofer <mkilchhofer@users.noreply.github.com>
2022-11-22 21:26:37 +01:00
JusteenR
81771b7bd7 Adding frameworks column to control command 2022-11-20 15:42:13 -08:00
Moshe Rappaport
2fee77c42c Merge pull request #928 from Moshe-Rappaport-CA/PER-633-support-loading-exceptions-from-cache-kubescape
PER-633 support loading exceptions from cache
2022-11-20 14:09:30 +02:00
Moshe-Rappaport-CA
968ecdb31d PER-633 support loading exceptions from cache 2022-11-20 12:22:15 +02:00
David Wertenteil
af7b36a88b Merge pull request #927 from Moshe-Rappaport-CA/PER-550-support-loading-attack-tracks-from-cache-kubescape
Per 550 support loading attack tracks from cache kubescape
2022-11-20 11:24:38 +02:00
Moshe-Rappaport-CA
6ad58d38e2 PER-550 Support loading attack tracks from cache 2022-11-17 16:31:51 +02:00
Moshe-Rappaport-CA
681b4ce155 stash 2022-11-17 10:49:36 +02:00
Moshe Rappaport
9d21ac1b16 Merge pull request #924 from Moshe-Rappaport-CA/dev
revert change in Junit format
2022-11-16 15:36:48 +02:00
Amir Malka
2b3fcca7e8 omit raw resources flag in json output 2022-11-16 12:15:17 +02:00
David Wertenteil
af8e786ab5 Merge pull request #914 from kubescape/dev
Closing issues
2022-11-16 10:59:59 +02:00
Moshe-Rappaport-CA
c8df1b8f1f Merge remote-tracking branch 'armo/dev' into dev 2022-11-15 17:34:45 +02:00
Moshe-Rappaport-CA
4f921ddf6f Revert PR #802 to the old junit format 2022-11-15 16:59:37 +02:00
David Wertenteil
4f5839870b Merge pull request #920 from amirmalka/dev
Fixed docker build to support ARM #919
2022-11-15 14:53:20 +02:00
Amir Malka
c0d7f51d6c test build flow 2022-11-15 13:29:35 +02:00
Amir Malka
a81d770360 fixed docker build to support arm 2022-11-15 10:57:29 +02:00
suhasgumma
f64d5eab50 Fix RootDir Info 2022-11-15 12:38:57 +05:30
suhasgumma
d773397fe9 replace src with RelSrc 2022-11-15 10:34:36 +05:30
suhasgumma
2e30995bfc Relative Path When scanning Local Repos 2022-11-15 10:22:04 +05:30
David Wertenteil
17a2547f18 Merge pull request #915 from kubescape/change-test-control-name
replace control 0006 by 0048
2022-11-14 14:56:34 +02:00
David Wertenteil
87a5cd66c8 replace control 0006 by 0048 2022-11-14 14:36:37 +02:00
David Wertenteil
9436ace64f continue when resource not found 2022-11-14 13:52:46 +02:00
David Wertenteil
fde00f6bd8 Merge pull request #909 from suhasgumma/dev
pretty-print Controls format  made Pretty
2022-11-13 17:04:46 +02:00
David Wertenteil
04a72a069a Merge pull request #913 from dwertent/ignore-missing-resource
Do not exit on error
2022-11-13 16:04:15 +02:00
David Wertenteil
e2dcb5bc15 Merge pull request #912 from dwertent/dep-rbac-submit
Deprecate rbac submit
2022-11-13 16:03:51 +02:00
suhasgumma
c7040a257c Pretty Print frameworks and exceptions 2022-11-13 19:29:26 +05:30
suhasgumma
602dc00c65 Shift GetControlLink to cautils 2022-11-13 19:09:30 +05:30
David Wertenteil
0339691571 Merge pull request #911 from dwertent/adding-remidiation
Adding remediation
2022-11-13 15:12:21 +02:00
David Wertenteil
9e1f3ec131 remove from smoke test 2022-11-13 15:10:05 +02:00
David Wertenteil
b8589819dc Do not exit on error 2022-11-13 15:06:32 +02:00
David Wertenteil
a3e87f4c01 Updating json v1 deprecation message 2022-11-13 15:03:22 +02:00
David Wertenteil
21ab5a602e Deprecate rbac submit 2022-11-13 15:01:32 +02:00
David Wertenteil
5d97d7b4b2 adding Remediation to message 2022-11-13 14:55:52 +02:00
suhasgumma
d8d7d0b372 Updated and Used GetControlLink 2022-11-13 17:56:39 +05:30
suhasgumma
b8323d41fc Modified Link Convention for CIS Controls 2022-11-13 17:22:37 +05:30
suhasgumma
d0b5314201 Improve Code Quality 2022-11-13 15:39:04 +05:30
suhasgumma
547e36e73f Pretty Print Controls made Pretty 2022-11-13 14:29:30 +05:30
David Wertenteil
e593a772cb Merge pull request #908 from Moshe-Rappaport-CA/update-k8s-interface-version
Update k8s-interface version and rbac-utils
2022-11-13 09:31:00 +02:00
Moshe-Rappaport-CA
4da09529b6 Update rbac-utils tag 2022-11-10 18:56:28 +02:00
Moshe-Rappaport-CA
de375992e8 Fix go.mod in httphandler 2022-11-10 17:54:44 +02:00
Moshe-Rappaport-CA
0bc4a29881 Update k8s-interface version 2022-11-10 17:38:32 +02:00
David Wertenteil
9575c92713 Merge pull request #906 from suhasgumma/dev
Fixed: Empty Lines before printing Controls and Added Invalid Format Error
2022-11-10 11:27:22 +02:00
David Wertenteil
cf277874eb Merge pull request #907 from matthyx/ioutil
remove deprecated ioutil package
2022-11-10 11:23:10 +02:00
Matthias Bertschy
746e060402 remove deprecated ioutil package 2022-11-10 09:58:07 +01:00
suhasgumma
dd3a7c816e Invalid Format Error 2022-11-10 11:57:57 +05:30
suhasgumma
814bc3ab2c Solved: Empty Lines before printing Controls 2022-11-10 11:17:48 +05:30
David Wertenteil
dbaf6761df Merge pull request #905 from matthyx/900
900
2022-11-10 06:52:34 +02:00
Matthias Bertschy
580e45827d add IDs to controls list, deprecate id flag 2022-11-09 22:08:04 +01:00
David Wertenteil
f3b8de9d1f fixing readme (#899) 2022-11-08 12:02:52 +02:00
David Wertenteil
6e9a2f55fd Merge pull request #894 from kubescape/dev
Enhancing CLI capabilities and SARIF output
2022-11-06 15:40:00 +02:00
David Wertenteil
dd7a8fd0c1 Merge pull request #883 from kubescape/dev
Minor changes
2022-10-26 13:31:04 +03:00
David Wertenteil
3373b728b7 Merge pull request #877 from kubescape/dev
Enhance configuration usage
2022-10-24 12:00:27 +03:00
125 changed files with 789 additions and 2737 deletions

View File

@@ -26,14 +26,24 @@ on:
type: boolean
description: 'support amd64/arm64'
secrets:
QUAYIO_REGISTRY_USERNAME:
required: true
QUAYIO_REGISTRY_PASSWORD:
required: true
jobs:
check-secret:
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
runs-on: ubuntu-latest
outputs:
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
steps:
- name: Check whether unity activation requests should be done
id: check-secret-set
env:
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
run: |
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
build-image:
needs: [check-secret]
if: needs.check-secret.outputs.is-secret-set == 'true'
name: Build image and upload to registry
runs-on: ubuntu-latest
permissions:
@@ -61,10 +71,10 @@ jobs:
- name: Build and push image
if: ${{ inputs.support_platforms }}
run: docker buildx build . --file build/Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --push --platform linux/amd64,linux/arm64
- name: Build and push image without amd64/arm64 support
if: ${{ !inputs.support_platforms }}
run: docker buildx build . --file build/Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --push
run: docker buildx build . --file build/Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --push
- name: Install cosign
uses: sigstore/cosign-installer@main
@@ -75,6 +85,5 @@ jobs:
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign sign --force ${{ inputs.image_name }}:latest
cosign sign --force ${{ inputs.image_name }}:${{ inputs.image_tag }}
cosign sign --force ${{ inputs.image_name }}

View File

@@ -4,7 +4,6 @@ on:
push:
branches: [ master ]
paths-ignore:
# Do not run the pipeline if only Markdown files changed
- '**.md'
jobs:
test:
@@ -79,13 +78,12 @@ jobs:
asset_content_type: application/octet-stream
publish-image:
if: ${{ github.repository == 'kubescape/kubescape' }} # TODO
uses: ./.github/workflows/build-image.yaml
needs: create-release
with:
client: "image-release"
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: "v2.0.${{ github.run_number }}"
support_platforms: false
support_platforms: true
cosign: true
secrets: inherit

View File

@@ -13,14 +13,13 @@ jobs:
release: "v2.0.${{ github.run_number }}"
client: test
publish-dev-image:
if: ${{ github.repository == 'kubescape/kubescape' }} # TODO
uses: ./.github/workflows/build-image.yaml
needs: test
with:
client: "image-dev"
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: "dev-v2.0.${{ github.run_number }}"
support_platforms: false
cosign: true
secrets: inherit
# publish-dev-image:
# uses: ./.github/workflows/build-image.yaml
# needs: test
# with:
# client: "image-dev"
# image_name: "quay.io/${{ github.repository_owner }}/kubescape"
# image_tag: "dev-v2.0.${{ github.run_number }}"
# support_platforms: true
# cosign: true
# secrets: inherit

View File

@@ -52,6 +52,9 @@ kubescape scan --enable-host-scan --verbose
</br>
## Architecture in short
[Component architecture](docs/architecture.drawio.svg)
### [CLI](#kubescape-cli)
<div align="center">
<img src="docs/ks-cli-arch.png" width="300" alt="cli-diagram">
@@ -220,6 +223,8 @@ kubescape scan *.yaml
```
#### Scan Kubernetes manifest files from a git repository
```
kubescape scan https://github.com/kubescape/kubescape
```

View File

@@ -57,7 +57,7 @@ def main():
if client_name:
ldflags += " -X {}={}".format(client_var, client_name)
build_command = ["go", "build", "-tags=static", "-o", ks_file, "-ldflags" ,ldflags]
build_command = ["go", "build", "-buildmode=pie", "-tags=static", "-o", ks_file, "-ldflags" ,ldflags]
print("Building kubescape and saving here: {}".format(ks_file))
print("Build command: {}".format(" ".join(build_command)))

View File

@@ -12,7 +12,7 @@ ENV CGO_ENABLED=1
# Install required python/pip
ENV PYTHONUNBUFFERED=1
RUN apk add --update --no-cache python3 git openssl-dev musl-dev gcc make cmake pkgconfig && ln -sf python3 /usr/bin/python
RUN apk add --update --no-cache python3 gcc make git libc-dev binutils-gold cmake pkgconfig && ln -sf python3 /usr/bin/python
RUN python3 -m ensurepip
RUN pip3 install --no-cache --upgrade pip setuptools

View File

@@ -24,8 +24,8 @@ var (
# Download the NSA framework. Run 'kubescape list frameworks' for all frameworks names
kubescape download framework nsa
# Download the "Allowed hostPath" control. Run 'kubescape list controls' for all controls names
kubescape download control "Allowed hostPath"
# Download the "HostPath mount" control. Run 'kubescape list controls' for all controls names
kubescape download control "HostPath mount"
# Download the "C-0001" control. Run 'kubescape list controls --id' for all controls ids
kubescape download control C-0001
@@ -36,6 +36,8 @@ var (
# Download the configured controls-inputs
kubescape download controls-inputs
# Download the attack tracks
kubescape download attack-tracks
`
)

View File

@@ -1,45 +0,0 @@
package fix
import (
"errors"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
var fixCmdExamples = `
Fix command is for fixing kubernetes manifest files based on a scan command output.
Use with caution, this command will change your files in-place.
# Fix kubernetes YAML manifest files based on a scan command output (output.json)
1) kubescape scan --format json --format-version v2 --output output.json
2) kubescape fix output.json
`
func GetFixCmd(ks meta.IKubescape) *cobra.Command {
var fixInfo metav1.FixInfo
fixCmd := &cobra.Command{
Use: "fix <report output file>",
Short: "Fix misconfiguration in files",
Long: ``,
Example: fixCmdExamples,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("report output file is required")
}
fixInfo.ReportFile = args[0]
return ks.Fix(&fixInfo)
},
}
fixCmd.PersistentFlags().BoolVar(&fixInfo.NoConfirm, "no-confirm", false, "No confirmation will be given to the user before applying the fix (default false)")
fixCmd.PersistentFlags().BoolVar(&fixInfo.DryRun, "dry-run", false, "No changes will be applied (default false)")
fixCmd.PersistentFlags().BoolVar(&fixInfo.SkipUserValues, "skip-user-values", true, "Changes which involve user-defined values will be skipped")
return fixCmd
}

View File

@@ -20,11 +20,8 @@ var (
# List all supported frameworks names
kubescape list frameworks --account <account id>
# List all supported controls names
# List all supported controls names with ids
kubescape list controls
# List all supported controls ids
kubescape list controls --id
Control documentation:
https://hub.armosec.io/docs/controls
@@ -67,8 +64,8 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-printer'/'json'")
listCmd.PersistentFlags().BoolVarP(&listPolicies.ListIDs, "id", "", false, "List control ID's instead of controls names")
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-print'/'json'")
listCmd.PersistentFlags().MarkDeprecated("id", "Control ID's are included in list outpus")
return listCmd
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/kubescape/kubescape/v2/cmd/config"
"github.com/kubescape/kubescape/v2/cmd/delete"
"github.com/kubescape/kubescape/v2/cmd/download"
"github.com/kubescape/kubescape/v2/cmd/fix"
"github.com/kubescape/kubescape/v2/cmd/list"
"github.com/kubescape/kubescape/v2/cmd/scan"
"github.com/kubescape/kubescape/v2/cmd/submit"
@@ -28,7 +27,7 @@ var rootInfo cautils.RootInfo
var ksExamples = `
# Scan command
kubescape scan --submit
kubescape scan
# List supported frameworks
kubescape list frameworks
@@ -79,7 +78,6 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.AddCommand(version.GetVersionCmd())
rootCmd.AddCommand(config.GetConfigCmd(ks))
rootCmd.AddCommand(update.GetUpdateCmd())
rootCmd.AddCommand(fix.GetFixCmd(ks))
return rootCmd
}

View File

@@ -23,7 +23,7 @@ var (
kubescape scan control "privileged container"
# Scan list of controls separated with a comma
kubescape scan control "privileged container","allowed hostpath"
kubescape scan control "privileged container","HostPath mount"
# Scan list of controls using the control ID separated with a comma
kubescape scan control C-0058,C-0057
@@ -61,7 +61,7 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
if err := validateFrameworkScanInfo(scanInfo); err != nil {
return err
}
// flagValidationControl(scanInfo)
scanInfo.PolicyIdentifier = []cautils.PolicyIdentifier{}
@@ -109,7 +109,7 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold)))
}
enforceSeverityThresholds(&results.GetResults().SummaryDetails.SeverityCounters, scanInfo, terminateOnExceedingSeverity)
enforceSeverityThresholds(results.GetResults().SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
return nil
},
@@ -120,6 +120,10 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
func validateControlScanInfo(scanInfo *cautils.ScanInfo) error {
severity := scanInfo.FailThresholdSeverity
if scanInfo.Submit && scanInfo.OmitRawResources {
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
}
if err := validateSeverity(severity); severity != "" && err != nil {
return err
}

View File

@@ -22,8 +22,8 @@ import (
var (
frameworkExample = `
# Scan all frameworks and submit the results
kubescape scan framework all --submit
# Scan all frameworks
kubescape scan framework all
# Scan the NSA framework
kubescape scan framework nsa
@@ -35,7 +35,7 @@ var (
kubescape scan framework all
# Scan kubernetes YAML manifest files (single file or glob)
kubescape scan framework nsa *.yaml
kubescape scan framework nsa .
Run 'kubescape list frameworks' for the list of supported frameworks
`
@@ -119,7 +119,7 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold)))
}
enforceSeverityThresholds(&results.GetData().Report.SummaryDetails.SeverityCounters, scanInfo, terminateOnExceedingSeverity)
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
return nil
},
}
@@ -136,10 +136,10 @@ func countersExceedSeverityThreshold(severityCounters reportsummary.ISeverityCou
SeverityName string
GetFailedResources func() int
}{
{reporthandlingapis.SeverityLowString, severityCounters.NumberOfResourcesWithLowSeverity},
{reporthandlingapis.SeverityMediumString, severityCounters.NumberOfResourcesWithMediumSeverity},
{reporthandlingapis.SeverityHighString, severityCounters.NumberOfResourcesWithHighSeverity},
{reporthandlingapis.SeverityCriticalString, severityCounters.NumberOfResourcesWithCriticalSeverity},
{reporthandlingapis.SeverityLowString, severityCounters.NumberOfLowSeverity},
{reporthandlingapis.SeverityMediumString, severityCounters.NumberOfMediumSeverity},
{reporthandlingapis.SeverityHighString, severityCounters.NumberOfHighSeverity},
{reporthandlingapis.SeverityCriticalString, severityCounters.NumberOfCriticalSeverity},
}
targetSeverityIdx := 0
@@ -201,7 +201,9 @@ func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
return fmt.Errorf("bad argument: out of range threshold")
}
if scanInfo.Submit && scanInfo.OmitRawResources {
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
}
severity := scanInfo.FailThresholdSeverity
if err := validateSeverity(severity); severity != "" && err != nil {
return err

View File

@@ -17,10 +17,10 @@ var scanCmdExamples = `
kubescape scan --enable-host-scan --verbose
# Scan kubernetes YAML manifest files
kubescape scan *.yaml
kubescape scan .
# Scan and save the results in the JSON format
kubescape scan --format json --output results.json
kubescape scan --format json --output results.json --format-version=v2
# Display all resources
kubescape scan --verbose
@@ -88,11 +88,13 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v1", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
scanCmd.PersistentFlags().StringVar(&scanInfo.CustomClusterName, "cluster-name", "", "Set the custom name of the cluster. Not same as the kube-context flag")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Submit the scan results to Kubescape SaaS where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.OmitRawResources, "omit-raw-resources", "", false, "Omit raw resources from the output. By default the raw resources are included in the output")
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
// hidden flags
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemonSet can not run pods on the nodes
scanCmd.PersistentFlags().MarkHidden("omit-raw-resources")
// Retrieve --kubeconfig flag from https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/cmd.go
scanCmd.PersistentFlags().AddGoFlag(flag.Lookup("kubeconfig"))

View File

@@ -24,91 +24,91 @@ func TestExceedsSeverity(t *testing.T) {
{
Description: "Critical failed resource should exceed Critical threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "critical"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
Want: true,
},
{
Description: "Critical failed resource should exceed Critical threshold set as constant",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: apis.SeverityCriticalString},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
Want: true,
},
{
Description: "High failed resource should not exceed Critical threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "critical"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
Want: false,
},
{
Description: "Critical failed resource exceeds High threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
Want: true,
},
{
Description: "High failed resource exceeds High threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
Want: true,
},
{
Description: "Medium failed resource does not exceed High threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithMediumSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
Want: false,
},
{
Description: "Critical failed resource exceeds Medium threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
Want: true,
},
{
Description: "High failed resource exceeds Medium threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
Want: true,
},
{
Description: "Medium failed resource exceeds Medium threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithMediumSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
Want: true,
},
{
Description: "Low failed resource does not exceed Medium threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithLowSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
Want: false,
},
{
Description: "Critical failed resource exceeds Low threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
Want: true,
},
{
Description: "High failed resource exceeds Low threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
Want: true,
},
{
Description: "Medium failed resource exceeds Low threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithMediumSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
Want: true,
},
{
Description: "Low failed resource exceeds Low threshold",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithLowSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
Want: true,
},
{
Description: "Unknown severity returns an error",
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "unknown"},
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithLowSeverityCounter: 1},
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
Want: false,
Error: ErrUnknownSeverity,
},
@@ -139,7 +139,7 @@ func Test_enforceSeverityThresholds(t *testing.T) {
}{
{
"Exceeding Critical severity counter should call the terminating function",
&reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
&reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
&cautils.ScanInfo{FailThresholdSeverity: apis.SeverityCriticalString},
true,
},

View File

@@ -31,10 +31,11 @@ var (
// getRBACCmd represents the RBAC command
func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
return &cobra.Command{
Use: "rbac",
Example: rbacExamples,
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
Use: "rbac",
Deprecated: "This command is deprecated and will not be supported after 1/Jan/2023. Please use the 'scan' command instead.",
Example: rbacExamples,
Short: "Submit cluster's Role-Based Access Control(RBAC)",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
if err := flagValidationSubmit(submitInfo); err != nil {

View File

@@ -7,16 +7,21 @@ import (
)
var submitCmdExamples = `
# Submit Kubescape scan results file
kubescape submit results
# Submit exceptions file to Kubescape SaaS
kubescape submit exceptions
`
func GetSubmitCmd(ks meta.IKubescape) *cobra.Command {
var submitInfo metav1.Submit
submitCmd := &cobra.Command{
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Use: "submit <command>",
Short: "Submit an object to the Kubescape SaaS version",
Long: ``,
Example: submitCmdExamples,
Run: func(cmd *cobra.Command, args []string) {
},
}

View File

@@ -0,0 +1,12 @@
package cautils
import (
"fmt"
"strings"
)
func GetControlLink(controlID string) string {
// For CIS Controls, cis-1.1.3 will be transformed to cis-1-1-3 in documentation link.
docLinkID := strings.ReplaceAll(controlID, ".", "-")
return fmt.Sprintf("https://hub.armosec.io/docs/%s", strings.ToLower(docLinkID))
}

View File

@@ -18,18 +18,19 @@ type OPASessionObj struct {
K8SResources *K8SResources // input k8s objects
ArmoResource *KSResources // input ARMO objects
AllPolicies *Policies // list of all frameworks
Policies []reporthandling.Framework // list of frameworks to scan
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<resource ID>]<resource>
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<resource ID>]<resource result>
ResourceSource map[string]reporthandling.Source // resources sources, map[<resource ID>]<resource result>
ResourcesPrioritized map[string]prioritization.PrioritizedResource // resources prioritization information, map[<resource ID>]<prioritized resource>
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
RegoInputData RegoInputData // input passed to rego for scanning. map[<control name>][<input arguments>]
Metadata *reporthandlingv2.Metadata
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
SessionID string // SessionID
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
SessionID string // SessionID
Policies []reporthandling.Framework // list of frameworks to scan
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
OmitRawResources bool // omit raw resources from output
}
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources, scanInfo *ScanInfo) *OPASessionObj {
@@ -45,6 +46,7 @@ func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SRe
ResourceSource: make(map[string]reporthandling.Source),
SessionID: scanInfo.ScanID,
Metadata: scanInfoToScanMetadata(scanInfo),
OmitRawResources: scanInfo.OmitRawResources,
}
}
@@ -94,6 +96,7 @@ type Exception struct {
type RegoInputData struct {
PostureControlInputs map[string][]string `json:"postureControlInputs"`
DataControlInputs map[string]string `json:"dataControlInputs"`
// ClusterName string `json:"clusterName"`
// K8sConfig RegoK8sConfig `json:"k8sconfig"`
}

View File

@@ -1,6 +1,7 @@
package getter
import (
"fmt"
"strings"
"github.com/armosec/armoapi-go/armotypes"
@@ -55,13 +56,29 @@ func (drp *DownloadReleasedPolicy) ListFrameworks() ([]string, error) {
return drp.gs.GetOPAFrameworksNamesList()
}
func (drp *DownloadReleasedPolicy) ListControls(listType ListType) ([]string, error) {
switch listType {
case ListID:
return drp.gs.GetOPAControlsIDsList()
default:
return drp.gs.GetOPAControlsNamesList()
func (drp *DownloadReleasedPolicy) ListControls() ([]string, error) {
controlsIDsList, err := drp.gs.GetOPAControlsIDsList()
if err != nil {
return []string{}, err
}
controlsNamesList, err := drp.gs.GetOPAControlsNamesList()
if err != nil {
return []string{}, err
}
controls, err := drp.gs.GetOPAControls()
if err != nil {
return []string{}, err
}
var controlsFrameworksList [][]string
for _, control := range controls {
controlsFrameworksList = append(controlsFrameworksList, control.FrameworkNames)
}
controlsNamesWithIDsandFrameworksList := make([]string, len(controlsIDsList))
// by design all slices have the same lengt
for i := range controlsIDsList {
controlsNamesWithIDsandFrameworksList[i] = fmt.Sprintf("%v|%v|%v", controlsIDsList[i], controlsNamesList[i], strings.Join(controlsFrameworksList[i], ","))
}
return controlsNamesWithIDsandFrameworksList, nil
}
func (drp *DownloadReleasedPolicy) GetControlsInputs(clusterName string) (map[string][]string, error) {

View File

@@ -6,19 +6,13 @@ import (
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
)
// supported listing
type ListType string
const ListID ListType = "id"
const ListName ListType = "name"
type IPolicyGetter interface {
GetFramework(name string) (*reporthandling.Framework, error)
GetFrameworks() ([]reporthandling.Framework, error)
GetControl(name string) (*reporthandling.Control, error)
ListFrameworks() ([]string, error)
ListControls(ListType) ([]string, error)
ListControls() ([]string, error)
}
type IExceptionsGetter interface {

View File

@@ -4,7 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"strings"
"time"
@@ -306,7 +306,7 @@ func (api *KSCloudAPI) ListFrameworks() ([]string, error) {
return frameworkList, nil
}
func (api *KSCloudAPI) ListControls(l ListType) ([]string, error) {
func (api *KSCloudAPI) ListControls() ([]string, error) {
return nil, fmt.Errorf("control api is not public")
}
@@ -358,7 +358,7 @@ func (api *KSCloudAPI) Login() error {
return fmt.Errorf("error authenticating: %d", resp.StatusCode)
}
responseBody, err := ioutil.ReadAll(resp.Body)
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
)
// =======================================================================================================================
@@ -109,7 +110,7 @@ func (lp *LoadPolicy) ListFrameworks() ([]string, error) {
return fwNames, nil
}
func (lp *LoadPolicy) ListControls(listType ListType) ([]string, error) {
func (lp *LoadPolicy) ListControls() ([]string, error) {
// TODO - Support
return []string{}, fmt.Errorf("loading controls list from file is not supported")
}
@@ -152,3 +153,18 @@ func (lp *LoadPolicy) filePath() string {
}
return ""
}
func (lp *LoadPolicy) GetAttackTracks() ([]v1alpha1.AttackTrack, error) {
attackTracks := []v1alpha1.AttackTrack{}
f, err := os.ReadFile(lp.filePath())
if err != nil {
return nil, err
}
if err := json.Unmarshal(f, &attackTracks); err != nil {
return nil, err
}
return attackTracks, nil
}

View File

@@ -0,0 +1,16 @@
package cautils
import (
"testing"
giturl "github.com/kubescape/go-git-url"
"github.com/stretchr/testify/require"
)
func TestEnsureRemoteParsed(t *testing.T) {
const remote = "git@gitlab.com:foobar/gitlab-tests/sample-project.git"
require.NotPanics(t, func() {
_, _ = giturl.NewGitURL(remote)
})
}

View File

@@ -3,7 +3,6 @@ package cautils
import (
_ "embed"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -39,7 +38,7 @@ func (s *HelmChartTestSuite) SetupSuite() {
}
var obj interface{}
file, _ := ioutil.ReadFile(filepath.Join("testdata", "helm_expected_default_values.json"))
file, _ := os.ReadFile(filepath.Join("testdata", "helm_expected_default_values.json"))
_ = json.Unmarshal([]byte(file), &obj)
s.expectedDefaultValues = obj.(map[string]interface{})
}

View File

@@ -6,10 +6,10 @@ import (
"strings"
"time"
"github.com/armosec/go-git-url/apis"
gitv5 "github.com/go-git/go-git/v5"
configv5 "github.com/go-git/go-git/v5/config"
plumbingv5 "github.com/go-git/go-git/v5/plumbing"
"github.com/kubescape/go-git-url/apis"
git2go "github.com/libgit2/git2go/v33"
)

View File

@@ -3,7 +3,6 @@ package cautils
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -11,7 +10,7 @@ import (
"github.com/armosec/armoapi-go/armotypes"
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
giturl "github.com/armosec/go-git-url"
giturl "github.com/kubescape/go-git-url"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
@@ -40,7 +39,8 @@ const (
// ScanCluster string = "cluster"
// ScanLocalFiles string = "yaml"
localControlInputsFilename string = "controls-inputs.json"
localExceptionsFilename string = "exceptions.json"
LocalExceptionsFilename string = "exceptions.json"
LocalAttackTracksFilename string = "attack-tracks.json"
)
type BoolPtrFlag struct {
@@ -128,6 +128,7 @@ type ScanInfo struct {
KubeContext string // context name
FrameworkScan bool // false if scanning control
ScanAll bool // true if scan all frameworks
OmitRawResources bool // true if omit raw resources from the output
}
type Getters struct {
@@ -159,7 +160,7 @@ func (scanInfo *ScanInfo) setUseArtifactsFrom() {
scanInfo.UseArtifactsFrom = dir
}
// set frameworks files
files, err := ioutil.ReadDir(scanInfo.UseArtifactsFrom)
files, err := os.ReadDir(scanInfo.UseArtifactsFrom)
if err != nil {
logger.L().Fatal("failed to read files from directory", helpers.String("dir", scanInfo.UseArtifactsFrom), helpers.Error(err))
}
@@ -176,7 +177,7 @@ func (scanInfo *ScanInfo) setUseArtifactsFrom() {
// set config-inputs file
scanInfo.ControlsInputs = filepath.Join(scanInfo.UseArtifactsFrom, localControlInputsFilename)
// set exceptions
scanInfo.UseExceptions = filepath.Join(scanInfo.UseArtifactsFrom, localExceptionsFilename)
scanInfo.UseExceptions = filepath.Join(scanInfo.UseArtifactsFrom, LocalExceptionsFilename)
}
func (scanInfo *ScanInfo) setUseFrom() {

View File

@@ -14,8 +14,9 @@ import (
"golang.org/x/mod/semver"
)
const SKIP_VERSION_CHECK_DEPRECATED = "KUBESCAPE_SKIP_UPDATE_CHECK"
const SKIP_VERSION_CHECK = "KS_SKIP_UPDATE_CHECK"
const SKIP_VERSION_CHECK_DEPRECATED_ENV = "KUBESCAPE_SKIP_UPDATE_CHECK"
const SKIP_VERSION_CHECK_ENV = "KS_SKIP_UPDATE_CHECK"
const CLIENT_ENV = "KS_CLIENT"
var BuildNumber string
var Client string
@@ -31,9 +32,14 @@ func NewIVersionCheckHandler() IVersionCheckHandler {
if BuildNumber == "" {
logger.L().Warning("unknown build number, this might affect your scan results. Please make sure you are updated to latest version")
}
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK); ok && boolutils.StringToBool(v) {
if v, ok := os.LookupEnv(CLIENT_ENV); ok && v != "" {
Client = v
}
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK_ENV); ok && boolutils.StringToBool(v) {
return NewVersionCheckHandlerMock()
} else if v, ok := os.LookupEnv(SKIP_VERSION_CHECK_DEPRECATED); ok && boolutils.StringToBool(v) {
} else if v, ok := os.LookupEnv(SKIP_VERSION_CHECK_DEPRECATED_ENV); ok && boolutils.StringToBool(v) {
return NewVersionCheckHandlerMock()
}
return NewVersionCheckHandler()

View File

@@ -19,6 +19,7 @@ var (
"KubeletInfo",
"KubeProxyInfo",
"ControlPlaneInfo",
"CloudProviderInfo",
}
CloudResources = []string{
"ClusterDescribe",

View File

@@ -19,6 +19,7 @@ var downloadFunc = map[string]func(*metav1.DownloadInfo) error{
"control": downloadControl,
"framework": downloadFramework,
"artifacts": downloadArtifacts,
"attack-tracks": downloadAttackTracks,
}
func DownloadSupportCommands() []string {
@@ -70,6 +71,7 @@ func downloadArtifacts(downloadInfo *metav1.DownloadInfo) error {
"controls-inputs": downloadConfigInputs,
"exceptions": downloadExceptions,
"framework": downloadFramework,
"attack-tracks": downloadAttackTracks,
}
for artifact := range artifacts {
if err := downloadArtifact(&metav1.DownloadInfo{Target: artifact, Path: downloadInfo.Path, FileName: fmt.Sprintf("%s.json", artifact)}, artifacts); err != nil {
@@ -108,12 +110,12 @@ func downloadExceptions(downloadInfo *metav1.DownloadInfo) error {
exceptionsGetter := getExceptionsGetter("", tenant.GetAccountID(), nil)
exceptions := []armotypes.PostureExceptionPolicy{}
if tenant.GetAccountID() != "" {
exceptions, err = exceptionsGetter.GetExceptions(tenant.GetContextName())
if err != nil {
return err
}
exceptions, err = exceptionsGetter.GetExceptions(tenant.GetContextName())
if err != nil {
return err
}
if downloadInfo.FileName == "" {
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
}
@@ -126,6 +128,30 @@ func downloadExceptions(downloadInfo *metav1.DownloadInfo) error {
return nil
}
func downloadAttackTracks(downloadInfo *metav1.DownloadInfo) error {
var err error
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
attackTracksGetter := getAttackTracksGetter(tenant.GetAccountID(), nil)
attackTracks, err := attackTracksGetter.GetAttackTracks()
if err != nil {
return err
}
if downloadInfo.FileName == "" {
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
}
// save in file
err = getter.SaveInFile(attackTracks, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
if err != nil {
return err
}
logger.L().Success("Downloaded", helpers.String("attack tracks", downloadInfo.Target), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
return nil
}
func downloadFramework(downloadInfo *metav1.DownloadInfo) error {
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())

View File

@@ -1,72 +0,0 @@
package core
import (
"fmt"
"strings"
logger "github.com/kubescape/go-logger"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v2/core/pkg/fixhandler"
)
const NoChangesApplied = "No changes were applied."
const NoResourcesToFix = "No issues to fix."
const ConfirmationQuestion = "Would you like to apply the changes to the files above? [y|n]: "
func (ks *Kubescape) Fix(fixInfo *metav1.FixInfo) error {
logger.L().Info("Reading report file...")
handler, err := fixhandler.NewFixHandler(fixInfo)
if err != nil {
return err
}
resourcesToFix := handler.PrepareResourcesToFix()
if len(resourcesToFix) == 0 {
logger.L().Info(NoResourcesToFix)
return nil
}
handler.PrintExpectedChanges(resourcesToFix)
if fixInfo.DryRun {
logger.L().Info(NoChangesApplied)
return nil
}
if !fixInfo.NoConfirm && !userConfirmed() {
logger.L().Info(NoChangesApplied)
return nil
}
updatedFilesCount, errors := handler.ApplyChanges(resourcesToFix)
logger.L().Info(fmt.Sprintf("Fixed resources in %d files.", updatedFilesCount))
if len(errors) > 0 {
for _, err := range errors {
logger.L().Error(err.Error())
}
return fmt.Errorf("Failed to fix some resources, check the logs for more details")
}
return nil
}
func userConfirmed() bool {
var input string
for {
fmt.Printf(ConfirmationQuestion)
if _, err := fmt.Scanln(&input); err != nil {
continue
}
input = strings.ToLower(input)
if input == "y" || input == "yes" {
return true
} else if input == "n" || input == "no" {
return false
}
}
}

View File

@@ -45,8 +45,9 @@ func getExceptionsGetter(useExceptions string, accountID string, downloadRelease
if downloadReleasedPolicy == nil {
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy()
}
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil {
logger.L().Warning("failed to get exceptions from github release, this may affect the scanning results", helpers.Error(err))
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull attack tracks, fallback to cache
logger.L().Warning("failed to get exceptions from github release, loading attack tracks from cache", helpers.Error(err))
return getter.NewLoadPolicy([]string{getter.GetDefaultPath(cautils.LocalExceptionsFilename)})
}
return downloadReleasedPolicy
@@ -247,8 +248,9 @@ func getAttackTracksGetter(accountID string, downloadReleasedPolicy *getter.Down
if downloadReleasedPolicy == nil {
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy()
}
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil {
logger.L().Warning("failed to get attack tracks from github release, this may affect the scanning results", helpers.Error(err))
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull attack tracks, fallback to cache
logger.L().Warning("failed to get attack tracks from github release, loading attack tracks from cache", helpers.Error(err))
return getter.NewLoadPolicy([]string{getter.GetDefaultPath(cautils.LocalAttackTracksFilename)})
}
return downloadReleasedPolicy
}

View File

@@ -6,8 +6,11 @@ import (
"sort"
"strings"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/cautils"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer"
v2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2"
"github.com/olekukonko/tablewriter"
)
var listFunc = map[string]func(*metav1.ListPolicies) ([]string, error){
@@ -16,7 +19,7 @@ var listFunc = map[string]func(*metav1.ListPolicies) ([]string, error){
"exceptions": listExceptions,
}
var listFormatFunc = map[string]func(*metav1.ListPolicies, []string){
var listFormatFunc = map[string]func(string, []string){
"pretty-print": prettyPrintListFormat,
"json": jsonListFormat,
}
@@ -29,14 +32,18 @@ func ListSupportActions() []string {
return commands
}
func (ks *Kubescape) List(listPolicies *metav1.ListPolicies) error {
if f, ok := listFunc[listPolicies.Target]; ok {
policies, err := f(listPolicies)
if policyListerFunc, ok := listFunc[listPolicies.Target]; ok {
policies, err := policyListerFunc(listPolicies)
if err != nil {
return err
}
sort.Strings(policies)
listFormatFunc[listPolicies.Format](listPolicies, policies)
if listFormatFunction, ok := listFormatFunc[listPolicies.Format]; ok {
listFormatFunction(listPolicies.Target, policies)
} else {
return fmt.Errorf("Invalid format \"%s\", Supported formats: 'pretty-print'/'json' ", listPolicies.Format)
}
return nil
}
@@ -45,20 +52,16 @@ func (ks *Kubescape) List(listPolicies *metav1.ListPolicies) error {
func listFrameworks(listPolicies *metav1.ListPolicies) ([]string, error) {
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
policyGetter := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
return listFrameworksNames(g), nil
return listFrameworksNames(policyGetter), nil
}
func listControls(listPolicies *metav1.ListPolicies) ([]string, error) {
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
l := getter.ListName
if listPolicies.ListIDs {
l = getter.ListID
}
return g.ListControls(l)
policyGetter := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
return policyGetter.ListControls()
}
func listExceptions(listPolicies *metav1.ListPolicies) ([]string, error) {
@@ -77,12 +80,73 @@ func listExceptions(listPolicies *metav1.ListPolicies) ([]string, error) {
return exceptionsNames, nil
}
func prettyPrintListFormat(listPolicies *metav1.ListPolicies, policies []string) {
sep := "\n * "
fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep))
func prettyPrintListFormat(targetPolicy string, policies []string) {
if targetPolicy == "controls" {
prettyPrintControls(policies)
return
}
header := fmt.Sprintf("Supported %s", targetPolicy)
policyTable := tablewriter.NewWriter(printer.GetWriter(""))
policyTable.SetAutoWrapText(true)
policyTable.SetHeader([]string{header})
policyTable.SetHeaderLine(true)
policyTable.SetRowLine(true)
data := v2.Matrix{}
controlRows := generatePolicyRows(policies)
data = append(data, controlRows...)
policyTable.SetAlignment(tablewriter.ALIGN_CENTER)
policyTable.AppendBulk(data)
policyTable.Render()
}
func jsonListFormat(listPolicies *metav1.ListPolicies, policies []string) {
func jsonListFormat(targetPolicy string, policies []string) {
j, _ := json.MarshalIndent(policies, "", " ")
fmt.Printf("%s\n", j)
}
func prettyPrintControls(policies []string) {
controlsTable := tablewriter.NewWriter(printer.GetWriter(""))
controlsTable.SetAutoWrapText(true)
controlsTable.SetHeader([]string{"Control ID", "Control Name", "Docs", "Frameworks"})
controlsTable.SetHeaderLine(true)
controlsTable.SetRowLine(true)
data := v2.Matrix{}
controlRows := generateControlRows(policies)
data = append(data, controlRows...)
controlsTable.AppendBulk(data)
controlsTable.Render()
}
func generateControlRows(policies []string) [][]string {
rows := [][]string{}
for _, control := range policies {
idAndControlAndFrameworks := strings.Split(control, "|")
id, control, framework := idAndControlAndFrameworks[0], idAndControlAndFrameworks[1], idAndControlAndFrameworks[2]
docs := cautils.GetControlLink(id)
currentRow := []string{id, control, docs, framework}
rows = append(rows, currentRow)
}
return rows
}
func generatePolicyRows(policies []string) [][]string {
rows := [][]string{}
for _, policy := range policies {
currentRow := []string{policy}
rows = append(rows, currentRow)
}
return rows
}

View File

@@ -54,6 +54,10 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
if err := tenantConfig.SetTenant(); err != nil {
logger.L().Error(err.Error())
}
if scanInfo.OmitRawResources {
logger.L().Warning("omit-raw-resources flag will be ignored in submit mode")
}
}
// ================== version testing ======================================

View File

@@ -1,8 +0,0 @@
package v1
type FixInfo struct {
ReportFile string // path to report file (mandatory)
NoConfirm bool // if true, no confirmation will be given to the user before applying the fix
SkipUserValues bool // if true, user values will not be changed
DryRun bool // if true, no changes will be applied
}

View File

@@ -4,7 +4,6 @@ import "github.com/kubescape/kubescape/v2/core/cautils"
type ListPolicies struct {
Target string
ListIDs bool
Format string
Credentials cautils.Credentials
}

View File

@@ -25,7 +25,4 @@ type IKubescape interface {
// delete
DeleteExceptions(deleteexceptions *metav1.DeleteExceptions) error
// fix
Fix(fixInfo *metav1.FixInfo) error
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/kubescape/opa-utils/reporthandling"
)
var mockControl_0006 = `{"guid":"","name":"Allowed hostPath","attributes":{"armoBuiltin":true},"id":"C-0006","controlID":"C-0006","creationTime":"","description":"Mounting host directory to the container can be abused to get access to sensitive data and gain persistence on the host machine.","remediation":"Refrain from using host path mount.","rules":[{"guid":"","name":"alert-rw-hostpath","attributes":{"armoBuiltin":true,"m$K8sThreatMatrix":"Persistence::Writable hostPath mount, Lateral Movement::Writable volume mounts on the host"},"creationTime":"","rule":"package armo_builtins\n\n# input: pod\n# apiversion: v1\n# does: returns hostPath volumes\n\ndeny[msga] {\n pod := input[_]\n pod.kind == \"Pod\"\n volumes := pod.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := pod.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n podname := pod.metadata.name\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"pod: %v has: %v as hostPath volume\", [podname, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [pod]\n\t\t}\n\t}\n}\n\n#handles majority of workload resources\ndeny[msga] {\n\twl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n volumes := wl.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := wl.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t\n\t}\n}\n\n#handles CronJobs\ndeny[msga] {\n\twl := input[_]\n\twl.kind == \"CronJob\"\n volumes := wl.spec.jobTemplate.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\n\tcontainer = wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.jobTemplate.spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\"packagename\": \"armo_builtins\",\n\t\"alertScore\": 7,\n\t\"failedPaths\": [result],\n\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\nisRWMount(mount, begginingOfPath, i, k) = path {\n not mount.readOnly == true\n not mount.readOnly == false\n path = \"\"\n}\nisRWMount(mount, begginingOfPath, i, k) = path {\n mount.readOnly == false\n path = sprintf(\"%vcontainers[%v].volumeMounts[%v].readOnly\", [begginingOfPath, format_int(i, 10), format_int(k, 10)])\n} ","resourceEnumerator":"","ruleLanguage":"Rego","match":[{"apiGroups":["*"],"apiVersions":["*"],"resources":["Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","CronJob","Pod"]}],"ruleDependencies":[{"packageName":"cautils"},{"packageName":"kubernetes.api.client"}],"configInputs":null,"controlConfigInputs":null,"description":"determines if any workload contains a hostPath volume with rw permissions","remediation":"Set the readOnly field of the mount to true","ruleQuery":""}],"rulesIDs":[""],"baseScore":6}`
var mockControl_0006 = `{"guid":"","name":"HostPath mount","attributes":{"armoBuiltin":true},"id":"C-0048","controlID":"C-0048","creationTime":"","description":"Mounting host directory to the container can be abused to get access to sensitive data and gain persistence on the host machine.","remediation":"Refrain from using host path mount.","rules":[{"guid":"","name":"alert-rw-hostpath","attributes":{"armoBuiltin":true,"m$K8sThreatMatrix":"Persistence::Writable hostPath mount, Lateral Movement::Writable volume mounts on the host"},"creationTime":"","rule":"package armo_builtins\n\n# input: pod\n# apiversion: v1\n# does: returns hostPath volumes\n\ndeny[msga] {\n pod := input[_]\n pod.kind == \"Pod\"\n volumes := pod.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := pod.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n podname := pod.metadata.name\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"pod: %v has: %v as hostPath volume\", [podname, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [pod]\n\t\t}\n\t}\n}\n\n#handles majority of workload resources\ndeny[msga] {\n\twl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n volumes := wl.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := wl.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t\n\t}\n}\n\n#handles CronJobs\ndeny[msga] {\n\twl := input[_]\n\twl.kind == \"CronJob\"\n volumes := wl.spec.jobTemplate.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\n\tcontainer = wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.jobTemplate.spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\"packagename\": \"armo_builtins\",\n\t\"alertScore\": 7,\n\t\"failedPaths\": [result],\n\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\nisRWMount(mount, begginingOfPath, i, k) = path {\n not mount.readOnly == true\n not mount.readOnly == false\n path = \"\"\n}\nisRWMount(mount, begginingOfPath, i, k) = path {\n mount.readOnly == false\n path = sprintf(\"%vcontainers[%v].volumeMounts[%v].readOnly\", [begginingOfPath, format_int(i, 10), format_int(k, 10)])\n} ","resourceEnumerator":"","ruleLanguage":"Rego","match":[{"apiGroups":["*"],"apiVersions":["*"],"resources":["Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","CronJob","Pod"]}],"ruleDependencies":[{"packageName":"cautils"},{"packageName":"kubernetes.api.client"}],"configInputs":null,"controlConfigInputs":null,"description":"determines if any workload contains a hostPath volume with rw permissions","remediation":"Set the readOnly field of the mount to true","ruleQuery":""}],"rulesIDs":[""],"baseScore":6}`
var mockControl_0044 = `{"guid":"","name":"Container hostPort","attributes":{"armoBuiltin":true},"id":"C-0044","controlID":"C-0044","creationTime":"","description":"Configuring hostPort limits you to a particular port, and if any two workloads that specify the same HostPort they cannot be deployed to the same node. Therefore, if the number of replica of such workload is higher than the number of nodes, the deployment will fail.","remediation":"Avoid usage of hostPort unless it is absolutely necessary. Use NodePort / ClusterIP instead.","rules":[{"guid":"","name":"container-hostPort","attributes":{"armoBuiltin":true},"creationTime":"","rule":"package armo_builtins\n\n\n# Fails if pod has container with hostPort\ndeny[msga] {\n pod := input[_]\n pod.kind == \"Pod\"\n container := pod.spec.containers[i]\n\tbegginingOfPath := \"spec.\"\n\tpath := isHostPort(container, i, begginingOfPath)\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"Container: %v has Host-port\", [ container.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": path,\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [pod]\n\t\t}\n\t}\n}\n\n# Fails if workload has container with hostPort\ndeny[msga] {\n wl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n container := wl.spec.template.spec.containers[i]\n\tbegginingOfPath := \"spec.template.spec.\"\n path := isHostPort(container, i, begginingOfPath)\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"Container: %v in %v: %v has Host-port\", [ container.name, wl.kind, wl.metadata.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": path,\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\n# Fails if cronjob has container with hostPort\ndeny[msga] {\n \twl := input[_]\n\twl.kind == \"CronJob\"\n\tcontainer = wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tbegginingOfPath := \"spec.jobTemplate.spec.template.spec.\"\n path := isHostPort(container, i, begginingOfPath)\n msga := {\n\t\t\"alertMessage\": sprintf(\"Container: %v in %v: %v has Host-port\", [ container.name, wl.kind, wl.metadata.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": path,\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\n\n\nisHostPort(container, i, begginingOfPath) = path {\n\tpath = [sprintf(\"%vcontainers[%v].ports[%v].hostPort\", [begginingOfPath, format_int(i, 10), format_int(j, 10)]) | port = container.ports[j]; port.hostPort]\n\tcount(path) > 0\n}\n","resourceEnumerator":"","ruleLanguage":"Rego","match":[{"apiGroups":["*"],"apiVersions":["*"],"resources":["Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","Pod","CronJob"]}],"ruleDependencies":[],"configInputs":null,"controlConfigInputs":null,"description":"fails if container has hostPort","remediation":"Make sure you do not configure hostPort for the container, if necessary use NodePort / ClusterIP","ruleQuery":"armo_builtins"}],"rulesIDs":[""],"baseScore":4}`
@@ -31,7 +31,7 @@ func MockFramework_0013() *reporthandling.Framework {
return fw
}
// MockFramework_0006_0013 mock control 0013 and control 0006 - "Non-root containers" and "Allowed hostPath"
// MockFramework_0006_0013 mock control 0013 and control 0006 - "Non-root containers" and "HostPath mount"
func MockFramework_0006_0013() *reporthandling.Framework {
fw := &reporthandling.Framework{
PortalBase: armotypes.PortalBase{

View File

@@ -1,63 +0,0 @@
package fixhandler
import (
"github.com/armosec/armoapi-go/armotypes"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/opa-utils/reporthandling"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
"gopkg.in/yaml.v3"
)
// FixHandler is a struct that holds the information of the report to be fixed
type FixHandler struct {
fixInfo *metav1.FixInfo
reportObj *reporthandlingv2.PostureReport
localBasePath string
}
// ResourceFixInfo is a struct that holds the information about the resource that needs to be fixed
type ResourceFixInfo struct {
YamlExpressions map[string]*armotypes.FixPath
Resource *reporthandling.Resource
FilePath string
DocumentIndex int
}
// NodeInfo holds extra information about the node
type nodeInfo struct {
node *yaml.Node
parent *yaml.Node
// position of the node among siblings
index int
}
// FixInfoMetadata holds the arguments "getFixInfo" function needs to pass to the
// functions it uses
type fixInfoMetadata struct {
originalList *[]nodeInfo
fixedList *[]nodeInfo
originalListTracker int
fixedListTracker int
contentToAdd *[]contentToAdd
linesToRemove *[]linesToRemove
}
// ContentToAdd holds the information about where to insert the new changes in the existing yaml file
type contentToAdd struct {
// Line where the fix should be applied to
line int
// Content is a string representation of the YAML node that describes a suggested fix
content string
}
// LinesToRemove holds the line numbers to remove from the existing yaml file
type linesToRemove struct {
startLine int
endLine int
}
type fileFixInfo struct {
contentsToAdd *[]contentToAdd
linesToRemove *[]linesToRemove
}

View File

@@ -1,346 +0,0 @@
package fixhandler
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"github.com/armosec/armoapi-go/armotypes"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/opa-utils/objectsenvelopes"
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
"github.com/mikefarah/yq/v4/pkg/yqlib"
"gopkg.in/op/go-logging.v1"
)
const UserValuePrefix = "YOUR_"
func NewFixHandler(fixInfo *metav1.FixInfo) (*FixHandler, error) {
jsonFile, err := os.Open(fixInfo.ReportFile)
if err != nil {
return nil, err
}
defer jsonFile.Close()
byteValue, _ := ioutil.ReadAll(jsonFile)
var reportObj reporthandlingv2.PostureReport
if err = json.Unmarshal(byteValue, &reportObj); err != nil {
return nil, err
}
if err = isSupportedScanningTarget(&reportObj); err != nil {
return nil, err
}
localPath := getLocalPath(&reportObj)
if _, err = os.Stat(localPath); err != nil {
return nil, err
}
backendLoggerLeveled := logging.AddModuleLevel(logging.NewLogBackend(logger.L().GetWriter(), "", 0))
backendLoggerLeveled.SetLevel(logging.ERROR, "")
yqlib.GetLogger().SetBackend(backendLoggerLeveled)
return &FixHandler{
fixInfo: fixInfo,
reportObj: &reportObj,
localBasePath: localPath,
}, nil
}
func isSupportedScanningTarget(report *reporthandlingv2.PostureReport) error {
if report.Metadata.ScanMetadata.ScanningTarget == reporthandlingv2.GitLocal || report.Metadata.ScanMetadata.ScanningTarget == reporthandlingv2.Directory {
return nil
}
return fmt.Errorf("unsupported scanning target. Only local git and directory scanning targets are supported")
}
func getLocalPath(report *reporthandlingv2.PostureReport) string {
if report.Metadata.ScanMetadata.ScanningTarget == reporthandlingv2.GitLocal {
return report.Metadata.ContextMetadata.RepoContextMetadata.LocalRootPath
}
if report.Metadata.ScanMetadata.ScanningTarget == reporthandlingv2.Directory {
return report.Metadata.ContextMetadata.DirectoryContextMetadata.BasePath
}
return ""
}
func (h *FixHandler) buildResourcesMap() map[string]*reporthandling.Resource {
resourceIdToRawResource := make(map[string]*reporthandling.Resource)
for i := range h.reportObj.Resources {
resourceIdToRawResource[h.reportObj.Resources[i].GetID()] = &h.reportObj.Resources[i]
}
for i := range h.reportObj.Results {
if h.reportObj.Results[i].RawResource == nil {
continue
}
resourceIdToRawResource[h.reportObj.Results[i].RawResource.GetID()] = h.reportObj.Results[i].RawResource
}
return resourceIdToRawResource
}
func (h *FixHandler) getPathFromRawResource(obj map[string]interface{}) string {
if localworkload.IsTypeLocalWorkload(obj) {
localwork := localworkload.NewLocalWorkload(obj)
return localwork.GetPath()
} else if objectsenvelopes.IsTypeRegoResponseVector(obj) {
regoResponseVectorObject := objectsenvelopes.NewRegoResponseVectorObject(obj)
relatedObjects := regoResponseVectorObject.GetRelatedObjects()
for _, relatedObject := range relatedObjects {
if localworkload.IsTypeLocalWorkload(relatedObject.GetObject()) {
return relatedObject.(*localworkload.LocalWorkload).GetPath()
}
}
}
return ""
}
func (h *FixHandler) PrepareResourcesToFix() []ResourceFixInfo {
resourceIdToResource := h.buildResourcesMap()
resourcesToFix := make([]ResourceFixInfo, 0)
for _, result := range h.reportObj.Results {
if !result.GetStatus(nil).IsFailed() {
continue
}
resourceID := result.ResourceID
resourceObj := resourceIdToResource[resourceID]
resourcePath := h.getPathFromRawResource(resourceObj.GetObject())
if resourcePath == "" {
continue
}
if resourceObj.Source == nil || resourceObj.Source.FileType != reporthandling.SourceTypeYaml {
continue
}
relativePath, documentIndex, err := h.getFilePathAndIndex(resourcePath)
if err != nil {
logger.L().Error("Skipping invalid resource path: " + resourcePath)
continue
}
absolutePath := path.Join(h.localBasePath, relativePath)
if _, err := os.Stat(absolutePath); err != nil {
logger.L().Error("Skipping missing file: " + absolutePath)
continue
}
rfi := ResourceFixInfo{
FilePath: absolutePath,
Resource: resourceObj,
YamlExpressions: make(map[string]*armotypes.FixPath, 0),
DocumentIndex: documentIndex,
}
for i := range result.AssociatedControls {
if result.AssociatedControls[i].GetStatus(nil).IsFailed() {
rfi.addYamlExpressionsFromResourceAssociatedControl(documentIndex, &result.AssociatedControls[i], h.fixInfo.SkipUserValues)
}
}
if len(rfi.YamlExpressions) > 0 {
resourcesToFix = append(resourcesToFix, rfi)
}
}
return resourcesToFix
}
func (h *FixHandler) PrintExpectedChanges(resourcesToFix []ResourceFixInfo) {
var sb strings.Builder
sb.WriteString("The following changes will be applied:\n")
for _, resourceFixInfo := range resourcesToFix {
sb.WriteString(fmt.Sprintf("File: %s\n", resourceFixInfo.FilePath))
sb.WriteString(fmt.Sprintf("Resource: %s\n", resourceFixInfo.Resource.GetName()))
sb.WriteString(fmt.Sprintf("Kind: %s\n", resourceFixInfo.Resource.GetKind()))
sb.WriteString("Changes:\n")
i := 1
for _, fixPath := range resourceFixInfo.YamlExpressions {
sb.WriteString(fmt.Sprintf("\t%d) %s = %s\n", i, (*fixPath).Path, (*fixPath).Value))
i++
}
sb.WriteString("\n------\n")
}
logger.L().Info(sb.String())
}
func (h *FixHandler) ApplyChanges(resourcesToFix []ResourceFixInfo) (int, []error) {
updatedFiles := make(map[string]bool)
errors := make([]error, 0)
fileYamlExpressions := h.getFileYamlExpressions(resourcesToFix)
for filepath, yamlExpression := range fileYamlExpressions {
fileAsString, err := getFileString(filepath)
if err != nil {
errors = append(errors, err)
continue
}
fixedYamlString, err := h.ApplyFixToContent(fileAsString, yamlExpression)
if err != nil {
errors = append(errors, fmt.Errorf("Failed to fix file %s: %w ", filepath, err))
continue
} else {
updatedFiles[filepath] = true
}
err = writeFixesToFile(filepath, fixedYamlString)
if err != nil {
logger.L().Error(fmt.Sprintf("Failed to write fixes to file %s, %v", filepath, err.Error()))
errors = append(errors, err)
}
}
return len(updatedFiles), errors
}
func (h *FixHandler) getFilePathAndIndex(filePathWithIndex string) (filePath string, documentIndex int, err error) {
splittedPath := strings.Split(filePathWithIndex, ":")
if len(splittedPath) <= 1 {
return "", 0, fmt.Errorf("expected to find ':' in file path")
}
filePath = splittedPath[0]
if documentIndex, err := strconv.Atoi(splittedPath[1]); err != nil {
return "", 0, err
} else {
return filePath, documentIndex, nil
}
}
func (h *FixHandler) ApplyFixToContent(yamlAsString, yamlExpression string) (fixedString string, err error) {
yamlLines := strings.Split(yamlAsString, "\n")
originalRootNodes, err := decodeDocumentRoots(yamlAsString)
if err != nil {
return "", err
}
fixedRootNodes, err := getFixedNodes(yamlAsString, yamlExpression)
if err != nil {
return "", err
}
fileFixInfo := getFixInfo(originalRootNodes, fixedRootNodes)
fixedYamlLines := getFixedYamlLines(yamlLines, fileFixInfo)
fixedString = getStringFromSlice(fixedYamlLines)
return fixedString, nil
}
func (h *FixHandler) getFileYamlExpressions(resourcesToFix []ResourceFixInfo) map[string]string {
fileYamlExpressions := make(map[string]string, 0)
for _, resourceToFix := range resourcesToFix {
singleExpression := reduceYamlExpressions(&resourceToFix)
resourceFilePath := resourceToFix.FilePath
if _, pathExistsInMap := fileYamlExpressions[resourceFilePath]; !pathExistsInMap {
fileYamlExpressions[resourceFilePath] = singleExpression
} else {
fileYamlExpressions[resourceFilePath] = joinStrings(fileYamlExpressions[resourceFilePath], " | ", singleExpression)
}
}
return fileYamlExpressions
}
func (rfi *ResourceFixInfo) addYamlExpressionsFromResourceAssociatedControl(documentIndex int, ac *resourcesresults.ResourceAssociatedControl, skipUserValues bool) {
for _, rule := range ac.ResourceAssociatedRules {
if !rule.GetStatus(nil).IsFailed() {
continue
}
for _, rulePaths := range rule.Paths {
if rulePaths.FixPath.Path == "" {
continue
}
if strings.HasPrefix(rulePaths.FixPath.Value, UserValuePrefix) && skipUserValues {
continue
}
yamlExpression := fixPathToValidYamlExpression(rulePaths.FixPath.Path, rulePaths.FixPath.Value, documentIndex)
rfi.YamlExpressions[yamlExpression] = &rulePaths.FixPath
}
}
}
// reduceYamlExpressions reduces the number of yaml expressions to a single one
func reduceYamlExpressions(resource *ResourceFixInfo) string {
expressions := make([]string, 0, len(resource.YamlExpressions))
for expr := range resource.YamlExpressions {
expressions = append(expressions, expr)
}
return strings.Join(expressions, " | ")
}
func fixPathToValidYamlExpression(fixPath, value string, documentIndexInYaml int) string {
isStringValue := true
if _, err := strconv.ParseBool(value); err == nil {
isStringValue = false
} else if _, err := strconv.ParseFloat(value, 64); err == nil {
isStringValue = false
} else if _, err := strconv.Atoi(value); err == nil {
isStringValue = false
}
// Strings should be quoted
if isStringValue {
value = fmt.Sprintf("\"%s\"", value)
}
// select document index and add a dot for the root node
return fmt.Sprintf("select(di==%d).%s |= %s", documentIndexInYaml, fixPath, value)
}
func joinStrings(inputStrings ...string) string {
return strings.Join(inputStrings, "")
}
func getFileString(filepath string) (string, error) {
bytes, err := ioutil.ReadFile(filepath)
if err != nil {
return "", fmt.Errorf("Error reading file %s", filepath)
}
return string(bytes), nil
}
func writeFixesToFile(filepath, content string) error {
err := ioutil.WriteFile(filepath, []byte(content), 0644)
if err != nil {
return fmt.Errorf("Error writing fixes to file: %w", err)
}
return nil
}

View File

@@ -1,248 +0,0 @@
package fixhandler
import (
"os"
"path/filepath"
"testing"
logger "github.com/kubescape/go-logger"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
"github.com/mikefarah/yq/v4/pkg/yqlib"
"github.com/stretchr/testify/assert"
"gopkg.in/op/go-logging.v1"
)
type indentationTestCase struct {
inputFile string
yamlExpression string
expectedFile string
}
func NewFixHandlerMock() (*FixHandler, error) {
backendLoggerLeveled := logging.AddModuleLevel(logging.NewLogBackend(logger.L().GetWriter(), "", 0))
backendLoggerLeveled.SetLevel(logging.ERROR, "")
yqlib.GetLogger().SetBackend(backendLoggerLeveled)
return &FixHandler{
fixInfo: &metav1.FixInfo{},
reportObj: &reporthandlingv2.PostureReport{},
localBasePath: "",
}, nil
}
func getTestdataPath() string {
currentDir, _ := os.Getwd()
return filepath.Join(currentDir, "testdata")
}
func getTestCases() []indentationTestCase {
indentationTestCases := []indentationTestCase{
// Insertion Scenarios
{
"inserts/tc-01-00-input-mapping-insert-mapping.yaml",
"select(di==0).spec.containers[0].securityContext.allowPrivilegeEscalation |= false",
"inserts/tc-01-01-expected.yaml",
},
{
"inserts/tc-02-00-input-mapping-insert-mapping-with-list.yaml",
"select(di==0).spec.containers[0].securityContext.capabilities.drop += [\"NET_RAW\"]",
"inserts/tc-02-01-expected.yaml",
},
{
"inserts/tc-03-00-input-list-append-scalar.yaml",
"select(di==0).spec.containers[0].securityContext.capabilities.drop += [\"SYS_ADM\"]",
"inserts/tc-03-01-expected.yaml",
},
{
"inserts/tc-04-00-input-multiple-inserts.yaml",
`select(di==0).spec.template.spec.securityContext.allowPrivilegeEscalation |= false |
select(di==0).spec.template.spec.containers[0].securityContext.capabilities.drop += ["NET_RAW"] |
select(di==0).spec.template.spec.containers[0].securityContext.seccompProfile.type |= "RuntimeDefault" |
select(di==0).spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation |= false |
select(di==0).spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem |= true`,
"inserts/tc-04-01-expected.yaml",
},
{
"inserts/tc-05-00-input-comment-blank-line-single-insert.yaml",
"select(di==0).spec.containers[0].securityContext.allowPrivilegeEscalation |= false",
"inserts/tc-05-01-expected.yaml",
},
{
"inserts/tc-06-00-input-list-append-scalar-oneline.yaml",
"select(di==0).spec.containers[0].securityContext.capabilities.drop += [\"SYS_ADM\"]",
"inserts/tc-06-01-expected.yaml",
},
{
"inserts/tc-07-00-input-multiple-documents.yaml",
`select(di==0).spec.containers[0].securityContext.allowPrivilegeEscalation |= false |
select(di==1).spec.containers[0].securityContext.allowPrivilegeEscalation |= false`,
"inserts/tc-07-01-expected.yaml",
},
{
"inserts/tc-08-00-input-mapping-insert-mapping-indented.yaml",
"select(di==0).spec.containers[0].securityContext.capabilities.drop += [\"NET_RAW\"]",
"inserts/tc-08-01-expected.yaml",
},
{
"inserts/tc-09-00-input-list-insert-new-mapping-indented.yaml",
`select(di==0).spec.containers += {"name": "redis", "image": "redis"}`,
"inserts/tc-09-01-expected.yaml",
},
{
"inserts/tc-10-00-input-list-insert-new-mapping.yaml",
`select(di==0).spec.containers += {"name": "redis", "image": "redis"}`,
"inserts/tc-10-01-expected.yaml",
},
// Removal Scenarios
{
"removals/tc-01-00-input.yaml",
"del(select(di==0).spec.containers[0].securityContext)",
"removals/tc-01-01-expected.yaml",
},
{
"removals/tc-02-00-input.yaml",
"del(select(di==0).spec.containers[1])",
"removals/tc-02-01-expected.yaml",
},
{
"removals/tc-03-00-input.yaml",
"del(select(di==0).spec.containers[0].securityContext.capabilities.drop[1])",
"removals/tc-03-01-expected.yaml",
},
{
"removes/tc-04-00-input.yaml",
`del(select(di==0).spec.containers[0].securityContext) |
del(select(di==1).spec.containers[1])`,
"removes/tc-04-01-expected.yaml",
},
// Replace Scenarios
{
"replaces/tc-01-00-input.yaml",
"select(di==0).spec.containers[0].securityContext.runAsRoot |= false",
"replaces/tc-01-01-expected.yaml",
},
{
"replaces/tc-02-00-input.yaml",
`select(di==0).spec.containers[0].securityContext.capabilities.drop[0] |= "SYS_ADM" |
select(di==0).spec.containers[0].securityContext.capabilities.add[0] |= "NET_RAW"`,
"replaces/tc-02-01-expected.yaml",
},
// Hybrid Scenarios
{
"hybrids/tc-01-00-input.yaml",
`del(select(di==0).spec.containers[0].securityContext) |
select(di==0).spec.securityContext.runAsRoot |= false`,
"hybrids/tc-01-01-expected.yaml",
},
{
"hybrids/tc-02-00-input-indented-list.yaml",
`del(select(di==0).spec.containers[0].securityContext) |
select(di==0).spec.securityContext.runAsRoot |= false`,
"hybrids/tc-02-01-expected.yaml",
},
{
"hybrids/tc-03-00-input-comments.yaml",
`del(select(di==0).spec.containers[0].securityContext) |
select(di==0).spec.securityContext.runAsRoot |= false`,
"hybrids/tc-03-01-expected.yaml",
},
{
"hybrids/tc-04-00-input-separated-keys.yaml",
`del(select(di==0).spec.containers[0].securityContext) |
select(di==0).spec.securityContext.runAsRoot |= false`,
"hybrids/tc-04-01-expected.yaml",
},
}
return indentationTestCases
}
func TestApplyFixKeepsFormatting(t *testing.T) {
testCases := getTestCases()
for _, tc := range testCases {
t.Run(tc.inputFile, func(t *testing.T) {
getTestDataPath := func(filename string) string {
currentDir, _ := os.Getwd()
currentFile := "testdata/" + filename
return filepath.Join(currentDir, currentFile)
}
input, _ := os.ReadFile(getTestDataPath(tc.inputFile))
wantRaw, _ := os.ReadFile(getTestDataPath(tc.expectedFile))
want := string(wantRaw)
expression := tc.yamlExpression
h, _ := NewFixHandlerMock()
got, _ := h.ApplyFixToContent(string(input), expression)
assert.Equalf(
t, want, got,
"Contents of the fixed file don't match the expectation.\n"+
"Input file: %s\n\n"+
"Got: <%s>\n\n"+
"Want: <%s>",
tc.inputFile, got, want,
)
},
)
}
}
func Test_fixPathToValidYamlExpression(t *testing.T) {
type args struct {
fixPath string
value string
documentIndexInYaml int
}
tests := []struct {
name string
args args
want string
}{
{
name: "fix path with boolean value",
args: args{
fixPath: "spec.template.spec.containers[0].securityContext.privileged",
value: "true",
documentIndexInYaml: 2,
},
want: "select(di==2).spec.template.spec.containers[0].securityContext.privileged |= true",
},
{
name: "fix path with string value",
args: args{
fixPath: "metadata.namespace",
value: "YOUR_NAMESPACE",
documentIndexInYaml: 0,
},
want: "select(di==0).metadata.namespace |= \"YOUR_NAMESPACE\"",
},
{
name: "fix path with number",
args: args{
fixPath: "xxx.yyy",
value: "123",
documentIndexInYaml: 0,
},
want: "select(di==0).xxx.yyy |= 123",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := fixPathToValidYamlExpression(tt.args.fixPath, tt.args.value, tt.args.documentIndexInYaml); got != tt.want {
t.Errorf("fixPathToValidYamlExpression() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,19 +0,0 @@
# Fix to Apply:
# REMOVE:
# "del(select(di==0).spec.containers[0].securityContext)"
# INSERT:
# select(di==0).spec.securityContext.runAsRoot: false
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
runAsRoot: true

View File

@@ -1,19 +0,0 @@
# Fix to Apply:
# REMOVE:
# "del(select(di==0).spec.containers[0].securityContext)"
# INSERT:
# select(di==0).spec.securityContext.runAsRoot: false
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
runAsRoot: false

View File

@@ -1,19 +0,0 @@
# Fix to Apply:
# REMOVE:
# "del(select(di==0).spec.containers[0].securityContext)"
# INSERT:
# select(di==0).spec.securityContext.runAsRoot: false
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
runAsRoot: true

View File

@@ -1,19 +0,0 @@
# Fix to Apply:
# REMOVE:
# "del(select(di==0).spec.containers[0].securityContext)"
# INSERT:
# select(di==0).spec.securityContext.runAsRoot: false
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
runAsRoot: false

View File

@@ -1,21 +0,0 @@
# Fix to Apply:
# REMOVE:
# "del(select(di==0).spec.containers[0].securityContext)"
# INSERT:
# select(di==0).spec.securityContext.runAsRoot: false
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
# These are the container comments
containers:
# These are the first containers comments
- name: nginx_container
image: nginx
securityContext:
runAsRoot: true

View File

@@ -1,21 +0,0 @@
# Fix to Apply:
# REMOVE:
# "del(select(di==0).spec.containers[0].securityContext)"
# INSERT:
# select(di==0).spec.securityContext.runAsRoot: false
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
# These are the container comments
containers:
# These are the first containers comments
- name: nginx_container
image: nginx
securityContext:
runAsRoot: false

View File

@@ -1,21 +0,0 @@
# Fix to Apply:
# REMOVE:
# "del(select(di==0).spec.containers[0].securityContext)"
# INSERT:
# select(di==0).spec.securityContext.runAsRoot: false
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
runAsRoot: true

View File

@@ -1,21 +0,0 @@
# Fix to Apply:
# REMOVE:
# "del(select(di==0).spec.containers[0].securityContext)"
# INSERT:
# select(di==0).spec.securityContext.runAsRoot: false
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
runAsRoot: false

View File

@@ -1,12 +0,0 @@
# Fix to Apply:
# "select(di==0).spec.containers[0].securityContext.allowPrivilegeEscalation |= false"
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx

View File

@@ -1,14 +0,0 @@
# Fix to Apply:
# "select(di==0).spec.containers[0].securityContext.allowPrivilegeEscalation |= false"
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
allowPrivilegeEscalation: false

View File

@@ -1,11 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers[0].securityContext.capabilities.drop += ["NET_RAW"]
apiVersion: v1
kind: Pod
metadata:
name: insert_list
spec:
containers:
- name: nginx_container
image: nginx

View File

@@ -1,15 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers[0].securityContext.capabilities.drop += ["NET_RAW"]
apiVersion: v1
kind: Pod
metadata:
name: insert_list
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
capabilities:
drop:
- NET_RAW

View File

@@ -1,15 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers[0].securityContext.capabilities.drop += ["SYS_ADM"]
apiVersion: v1
kind: Pod
metadata:
name: insert_list
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
capabilities:
drop:
- NET_RAW

View File

@@ -1,16 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers[0].securityContext.capabilities.drop += ["SYS_ADM"]
apiVersion: v1
kind: Pod
metadata:
name: insert_list
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
capabilities:
drop:
- NET_RAW
- SYS_ADM

View File

@@ -1,47 +0,0 @@
# Fixes to Apply:
# 1) select(di==0).spec.template.spec.securityContext.allowPrivilegeEscalation = false
# 2) select(di==0).spec.template.spec.containers[0].securityContext.capabilities.drop += ["NET_RAW"]
# 3) select(di==0).spec.template.spec.containers[0].securityContext.seccompProfile.type = RuntimeDefault
# 4) select(di==0).spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation |= false
# 5) select(di==0).spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem |= true
apiVersion: apps/v1
kind: Deployment
metadata:
name: multiple_inserts
spec:
selector:
matchLabels:
app: example_4
template:
metadata:
labels:
app: example_4
spec:
serviceAccountName: default
terminationGracePeriodSeconds: 5
containers:
- name: example_4
image: nginx
ports:
- containerPort: 3000
env:
- name: PORT
value: "3000"
resources:
requests:
cpu: 200m
memory: 180Mi
limits:
cpu: 300m
memory: 300Mi
readinessProbe:
initialDelaySeconds: 20
periodSeconds: 15
exec:
command: ["/bin/grpc_health_probe", "-addr=:3000"]
livenessProbe:
initialDelaySeconds: 20
periodSeconds: 15
exec:
command: ["/bin/grpc_health_probe", "-addr=:3000"]

View File

@@ -1,57 +0,0 @@
# Fixes to Apply:
# 1) select(di==0).spec.template.spec.securityContext.allowPrivilegeEscalation = false
# 2) select(di==0).spec.template.spec.containers[0].securityContext.capabilities.drop += ["NET_RAW"]
# 3) select(di==0).spec.template.spec.containers[0].securityContext.seccompProfile.type = RuntimeDefault
# 4) select(di==0).spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation |= false
# 5) select(di==0).spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem |= true
apiVersion: apps/v1
kind: Deployment
metadata:
name: multiple_inserts
spec:
selector:
matchLabels:
app: example_4
template:
metadata:
labels:
app: example_4
spec:
serviceAccountName: default
terminationGracePeriodSeconds: 5
containers:
- name: example_4
image: nginx
ports:
- containerPort: 3000
env:
- name: PORT
value: "3000"
resources:
requests:
cpu: 200m
memory: 180Mi
limits:
cpu: 300m
memory: 300Mi
readinessProbe:
initialDelaySeconds: 20
periodSeconds: 15
exec:
command: ["/bin/grpc_health_probe", "-addr=:3000"]
livenessProbe:
initialDelaySeconds: 20
periodSeconds: 15
exec:
command: ["/bin/grpc_health_probe", "-addr=:3000"]
securityContext:
capabilities:
drop:
- NET_RAW
seccompProfile:
type: RuntimeDefault
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
securityContext:
allowPrivilegeEscalation: false

View File

@@ -1,16 +0,0 @@
# Fix to Apply:
# "select(di==0).spec.containers[0].securityContext.allowPrivilegeEscalation |= false"
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
# Testing if comments are retained as intended
securityContext:
runAsRoot: false

View File

@@ -1,18 +0,0 @@
# Fix to Apply:
# "select(di==0).spec.containers[0].securityContext.allowPrivilegeEscalation |= false"
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
allowPrivilegeEscalation: false
# Testing if comments are retained as intended
securityContext:
runAsRoot: false

View File

@@ -1,14 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers[0].securityContext.capabilities.drop += ["SYS_ADM"]
apiVersion: v1
kind: Pod
metadata:
name: insert_list
spec:
containers:
- name: nginx1
image: nginx
securityContext:
capabilities:
drop: [NET_RAW]

View File

@@ -1,14 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers[0].securityContext.capabilities.drop += ["SYS_ADM"]
apiVersion: v1
kind: Pod
metadata:
name: insert_list
spec:
containers:
- name: nginx1
image: nginx
securityContext:
capabilities:
drop: [NET_RAW, SYS_ADM]

View File

@@ -1,27 +0,0 @@
# Fix to Apply:
# "select(di==0).spec.containers[0].securityContext.allowPrivilegeEscalation |= false"
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
---
# Fix to Apply:
# "select(di==1).spec.containers[0].securityContext.allowPrivilegeEscalation |= false"
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx

View File

@@ -1,31 +0,0 @@
# Fix to Apply:
# "select(di==0).spec.containers[0].securityContext.allowPrivilegeEscalation |= false"
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
allowPrivilegeEscalation: false
---
# Fix to Apply:
# "select(di==1).spec.containers[0].securityContext.allowPrivilegeEscalation |= false"
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
allowPrivilegeEscalation: false

View File

@@ -1,11 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers[0].securityContext.capabilities.drop += ["NET_RAW"]
apiVersion: v1
kind: Pod
metadata:
name: indented-parent-list-insert-list-value
spec:
containers:
- name: nginx_container
image: nginx

View File

@@ -1,15 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers[0].securityContext.capabilities.drop += ["NET_RAW"]
apiVersion: v1
kind: Pod
metadata:
name: indented-parent-list-insert-list-value
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
capabilities:
drop:
- NET_RAW

View File

@@ -1,11 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers += {"name": "redis", "image": "redis"}
apiVersion: v1
kind: Pod
metadata:
name: indented-parent-list-insert-list-value
spec:
containers:
- name: nginx_container
image: nginx

View File

@@ -1,13 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers += {"name": "redis", "image": "redis"}
apiVersion: v1
kind: Pod
metadata:
name: indented-parent-list-insert-list-value
spec:
containers:
- name: nginx_container
image: nginx
- name: redis
image: redis

View File

@@ -1,11 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers += {"name": "redis", "image": "redis"}
apiVersion: v1
kind: Pod
metadata:
name: indented-list-insert-new-object
spec:
containers:
- name: nginx_container
image: nginx

View File

@@ -1,13 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers += {"name": "redis", "image": "redis"}
apiVersion: v1
kind: Pod
metadata:
name: indented-list-insert-new-object
spec:
containers:
- name: nginx_container
image: nginx
- name: redis
image: redis

View File

@@ -1,14 +0,0 @@
# Fix to Apply:
# del(select(di==0).spec.containers[0].securityContext)
apiVersion: v1
kind: Pod
metadata:
name: remove_example
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
runAsRoot: false

View File

@@ -1,12 +0,0 @@
# Fix to Apply:
# del(select(di==0).spec.containers[0].securityContext)
apiVersion: v1
kind: Pod
metadata:
name: remove_example
spec:
containers:
- name: nginx_container
image: nginx

View File

@@ -1,15 +0,0 @@
# Fix to Apply:
# del(select(di==0).spec.containers[1])
apiVersion: v1
kind: Pod
metadata:
name: remove_example
spec:
containers:
- name: nginx_container
image: nginx
- name: container_with_security_issues
image: image_with_security_issues

View File

@@ -1,12 +0,0 @@
# Fix to Apply:
# del(select(di==0).spec.containers[1])
apiVersion: v1
kind: Pod
metadata:
name: remove_example
spec:
containers:
- name: nginx_container
image: nginx

View File

@@ -1,14 +0,0 @@
# Fix to Apply:
# del(select(di==0).spec.containers[0].securityContext.capabilities.drop[1])
apiVersion: v1
kind: Pod
metadata:
name: insert_list
spec:
containers:
- name: nginx1
image: nginx
securityContext:
capabilities:
drop: ["NET_RAW", "SYS_ADM"]

View File

@@ -1,14 +0,0 @@
# Fix to Apply:
# del(select(di==0).spec.containers[0].securityContext.capabilities.drop[1])
apiVersion: v1
kind: Pod
metadata:
name: insert_list
spec:
containers:
- name: nginx1
image: nginx
securityContext:
capabilities:
drop: ["NET_RAW"]

View File

@@ -1,32 +0,0 @@
# Fix to Apply:
# del(select(di==0).spec.containers[0].securityContext)
apiVersion: v1
kind: Pod
metadata:
name: remove_example
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
runAsRoot: false
---
# Fix to Apply:
# del(select(di==0).spec.containers[1])
apiVersion: v1
kind: Pod
metadata:
name: remove_example
spec:
containers:
- name: nginx_container
image: nginx
- name: container_with_security_issues
image: image_with_security_issues

View File

@@ -1,27 +0,0 @@
# Fix to Apply:
# del(select(di==0).spec.containers[0].securityContext)
apiVersion: v1
kind: Pod
metadata:
name: remove_example
spec:
containers:
- name: nginx_container
image: nginx
---
# Fix to Apply:
# del(select(di==0).spec.containers[1])
apiVersion: v1
kind: Pod
metadata:
name: remove_example
spec:
containers:
- name: nginx_container
image: nginx

View File

@@ -1,14 +0,0 @@
# Fix to Apply:
# "select(di==0).spec.containers[0].securityContext.runAsRoot |= false"
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
runAsRoot: true

View File

@@ -1,14 +0,0 @@
# Fix to Apply:
# "select(di==0).spec.containers[0].securityContext.runAsRoot |= false"
apiVersion: v1
kind: Pod
metadata:
name: insert_to_mapping_node_1
spec:
containers:
- name: nginx_container
image: nginx
securityContext:
runAsRoot: false

View File

@@ -1,18 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers[0].securityContext.capabilities.drop[0] |= "SYS_ADM"
# select(di==0).spec.containers[0].securityContext.capabilities.add[0] |= "NET_RAW"
apiVersion: v1
kind: Pod
metadata:
name: insert_list
spec:
containers:
- name: nginx1
image: nginx
securityContext:
capabilities:
drop:
- "NET_RAW"
add: ["SYS_ADM"]

View File

@@ -1,18 +0,0 @@
# Fix to Apply:
# select(di==0).spec.containers[0].securityContext.capabilities.drop[0] |= "SYS_ADM"
# select(di==0).spec.containers[0].securityContext.capabilities.add[0] |= "NET_RAW"
apiVersion: v1
kind: Pod
metadata:
name: insert_list
spec:
containers:
- name: nginx1
image: nginx
securityContext:
capabilities:
drop:
- "SYS_ADM"
add: ["NET_RAW"]

View File

@@ -1,286 +0,0 @@
package fixhandler
import (
"container/list"
"errors"
"fmt"
"io"
"strings"
"github.com/mikefarah/yq/v4/pkg/yqlib"
"gopkg.in/yaml.v3"
)
// decodeDocumentRoots decodes all YAML documents stored in a given `filepath` and returns a slice of their root nodes
func decodeDocumentRoots(yamlAsString string) ([]yaml.Node, error) {
fileReader := strings.NewReader(yamlAsString)
dec := yaml.NewDecoder(fileReader)
nodes := make([]yaml.Node, 0)
for {
var node yaml.Node
err := dec.Decode(&node)
nodes = append(nodes, node)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return nil, fmt.Errorf("Cannot Decode File as YAML")
}
}
return nodes, nil
}
func getFixedNodes(yamlAsString, yamlExpression string) ([]yaml.Node, error) {
preferences := yqlib.ConfiguredYamlPreferences
preferences.EvaluateTogether = true
decoder := yqlib.NewYamlDecoder(preferences)
var allDocuments = list.New()
reader := strings.NewReader(yamlAsString)
fileDocuments, err := readDocuments(reader, decoder)
if err != nil {
return nil, err
}
allDocuments.PushBackList(fileDocuments)
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
fixedCandidateNodes, err := allAtOnceEvaluator.EvaluateCandidateNodes(yamlExpression, allDocuments)
if err != nil {
return nil, fmt.Errorf("Error fixing YAML, %w", err)
}
fixedNodes := make([]yaml.Node, 0)
var fixedNode *yaml.Node
for fixedCandidateNode := fixedCandidateNodes.Front(); fixedCandidateNode != nil; fixedCandidateNode = fixedCandidateNode.Next() {
fixedNode = fixedCandidateNode.Value.(*yqlib.CandidateNode).Node
fixedNodes = append(fixedNodes, *fixedNode)
}
return fixedNodes, nil
}
func flattenWithDFS(node *yaml.Node) *[]nodeInfo {
dfsOrder := make([]nodeInfo, 0)
flattenWithDFSHelper(node, nil, &dfsOrder, 0)
return &dfsOrder
}
func flattenWithDFSHelper(node *yaml.Node, parent *yaml.Node, dfsOrder *[]nodeInfo, index int) {
dfsNode := nodeInfo{
node: node,
parent: parent,
index: index,
}
*dfsOrder = append(*dfsOrder, dfsNode)
for idx, child := range node.Content {
flattenWithDFSHelper(child, node, dfsOrder, idx)
}
}
func getFixInfo(originalRootNodes, fixedRootNodes []yaml.Node) fileFixInfo {
contentToAdd := make([]contentToAdd, 0)
linesToRemove := make([]linesToRemove, 0)
for idx := 0; idx < len(fixedRootNodes); idx++ {
originalList := flattenWithDFS(&originalRootNodes[idx])
fixedList := flattenWithDFS(&fixedRootNodes[idx])
nodeContentToAdd, nodeLinesToRemove := getFixInfoHelper(*originalList, *fixedList)
contentToAdd = append(contentToAdd, nodeContentToAdd...)
linesToRemove = append(linesToRemove, nodeLinesToRemove...)
}
return fileFixInfo{
contentsToAdd: &contentToAdd,
linesToRemove: &linesToRemove,
}
}
func getFixInfoHelper(originalList, fixedList []nodeInfo) ([]contentToAdd, []linesToRemove) {
// While obtaining fixedYamlNode, comments and empty lines at the top are ignored.
// This causes a difference in Line numbers across the tree structure. In order to
// counter this, line numbers are adjusted in fixed list.
adjustFixedListLines(&originalList, &fixedList)
contentToAdd := make([]contentToAdd, 0)
linesToRemove := make([]linesToRemove, 0)
originalListTracker, fixedListTracker := 0, 0
fixInfoMetadata := &fixInfoMetadata{
originalList: &originalList,
fixedList: &fixedList,
originalListTracker: originalListTracker,
fixedListTracker: fixedListTracker,
contentToAdd: &contentToAdd,
linesToRemove: &linesToRemove,
}
for originalListTracker < len(originalList) && fixedListTracker < len(fixedList) {
matchNodeResult := matchNodes(originalList[originalListTracker].node, fixedList[fixedListTracker].node)
fixInfoMetadata.originalListTracker = originalListTracker
fixInfoMetadata.fixedListTracker = fixedListTracker
switch matchNodeResult {
case sameNodes:
originalListTracker += 1
fixedListTracker += 1
case removedNode:
originalListTracker, fixedListTracker = addLinesToRemove(fixInfoMetadata)
case insertedNode:
originalListTracker, fixedListTracker = addLinesToInsert(fixInfoMetadata)
case replacedNode:
originalListTracker, fixedListTracker = updateLinesToReplace(fixInfoMetadata)
}
}
// Some nodes are still not visited if they are removed at the end of the list
for originalListTracker < len(originalList) {
fixInfoMetadata.originalListTracker = originalListTracker
originalListTracker, _ = addLinesToRemove(fixInfoMetadata)
}
// Some nodes are still not visited if they are inserted at the end of the list
for fixedListTracker < len(fixedList) {
// Use negative index of last node in original list as a placeholder to determine the last line number later
fixInfoMetadata.originalListTracker = -(len(originalList) - 1)
fixInfoMetadata.fixedListTracker = fixedListTracker
_, fixedListTracker = addLinesToInsert(fixInfoMetadata)
}
return contentToAdd, linesToRemove
}
// Adds the lines to remove and returns the updated originalListTracker
func addLinesToRemove(fixInfoMetadata *fixInfoMetadata) (int, int) {
isOneLine, line := isOneLineSequenceNode(fixInfoMetadata.originalList, fixInfoMetadata.originalListTracker)
if isOneLine {
// Remove the entire line and replace it with the sequence node in fixed info. This way,
// the original formatting is not lost.
return replaceSingleLineSequence(fixInfoMetadata, line)
}
currentDFSNode := (*fixInfoMetadata.originalList)[fixInfoMetadata.originalListTracker]
newOriginalListTracker := updateTracker(fixInfoMetadata.originalList, fixInfoMetadata.originalListTracker)
*fixInfoMetadata.linesToRemove = append(*fixInfoMetadata.linesToRemove, linesToRemove{
startLine: currentDFSNode.node.Line,
endLine: getNodeLine(fixInfoMetadata.originalList, newOriginalListTracker),
})
return newOriginalListTracker, fixInfoMetadata.fixedListTracker
}
// Adds the lines to insert and returns the updated fixedListTracker
func addLinesToInsert(fixInfoMetadata *fixInfoMetadata) (int, int) {
isOneLine, line := isOneLineSequenceNode(fixInfoMetadata.fixedList, fixInfoMetadata.fixedListTracker)
if isOneLine {
return replaceSingleLineSequence(fixInfoMetadata, line)
}
currentDFSNode := (*fixInfoMetadata.fixedList)[fixInfoMetadata.fixedListTracker]
lineToInsert := getLineToInsert(fixInfoMetadata)
contentToInsert := getContent(currentDFSNode.parent, fixInfoMetadata.fixedList, fixInfoMetadata.fixedListTracker)
newFixedTracker := updateTracker(fixInfoMetadata.fixedList, fixInfoMetadata.fixedListTracker)
*fixInfoMetadata.contentToAdd = append(*fixInfoMetadata.contentToAdd, contentToAdd{
line: lineToInsert,
content: contentToInsert,
})
return fixInfoMetadata.originalListTracker, newFixedTracker
}
// Adds the lines to remove and insert and updates the fixedListTracker and originalListTracker
func updateLinesToReplace(fixInfoMetadata *fixInfoMetadata) (int, int) {
isOneLine, line := isOneLineSequenceNode(fixInfoMetadata.fixedList, fixInfoMetadata.fixedListTracker)
if isOneLine {
return replaceSingleLineSequence(fixInfoMetadata, line)
}
currentDFSNode := (*fixInfoMetadata.fixedList)[fixInfoMetadata.fixedListTracker]
// If only the value node is changed, entire "key-value" pair is replaced
if isValueNodeinMapping(&currentDFSNode) {
fixInfoMetadata.originalListTracker -= 1
fixInfoMetadata.fixedListTracker -= 1
}
addLinesToRemove(fixInfoMetadata)
updatedOriginalTracker, updatedFixedTracker := addLinesToInsert(fixInfoMetadata)
return updatedOriginalTracker, updatedFixedTracker
}
func removeNewLinesAtTheEnd(yamlLines []string) []string {
for idx := 1; idx < len(yamlLines); idx++ {
if yamlLines[len(yamlLines)-idx] != "\n" {
yamlLines = yamlLines[:len(yamlLines)-idx+1]
break
}
}
return yamlLines
}
func getFixedYamlLines(yamlLines []string, fileFixInfo fileFixInfo) (fixedYamlLines []string) {
// Determining last line requires original yaml lines slice. The placeholder for last line is replaced with the real last line
assignLastLine(fileFixInfo.contentsToAdd, fileFixInfo.linesToRemove, &yamlLines)
removeLines(fileFixInfo.linesToRemove, &yamlLines)
fixedYamlLines = make([]string, 0)
lineIdx, lineToAddIdx := 1, 0
// Ideally, new node is inserted at line before the next node in DFS order. But, when the previous line contains a
// comment or empty line, we need to insert new nodes before them.
adjustContentLines(fileFixInfo.contentsToAdd, &yamlLines)
for lineToAddIdx < len(*fileFixInfo.contentsToAdd) {
for lineIdx <= (*fileFixInfo.contentsToAdd)[lineToAddIdx].line {
// Check if the current line is not removed
if yamlLines[lineIdx-1] != "*" {
fixedYamlLines = append(fixedYamlLines, yamlLines[lineIdx-1])
}
lineIdx += 1
}
content := (*fileFixInfo.contentsToAdd)[lineToAddIdx].content
fixedYamlLines = append(fixedYamlLines, content)
lineToAddIdx += 1
}
for lineIdx <= len(yamlLines) {
if yamlLines[lineIdx-1] != "*" {
fixedYamlLines = append(fixedYamlLines, yamlLines[lineIdx-1])
}
lineIdx += 1
}
fixedYamlLines = removeNewLinesAtTheEnd(fixedYamlLines)
return fixedYamlLines
}

View File

@@ -1,406 +0,0 @@
package fixhandler
import (
"bufio"
"bytes"
"container/list"
"errors"
"fmt"
"io"
"math"
"os"
"strings"
logger "github.com/kubescape/go-logger"
"github.com/mikefarah/yq/v4/pkg/yqlib"
"gopkg.in/yaml.v3"
)
type NodeRelation int
const (
sameNodes NodeRelation = iota
insertedNode
removedNode
replacedNode
)
func matchNodes(nodeOne, nodeTwo *yaml.Node) NodeRelation {
isNewNode := nodeTwo.Line == 0 && nodeTwo.Column == 0
sameLines := nodeOne.Line == nodeTwo.Line
sameColumns := nodeOne.Column == nodeTwo.Column
isSameNode := isSameNode(nodeOne, nodeTwo)
switch {
case isSameNode:
return sameNodes
case isNewNode:
return insertedNode
case sameLines && sameColumns:
return replacedNode
default:
return removedNode
}
}
func adjustContentLines(contentToAdd *[]contentToAdd, linesSlice *[]string) {
for contentIdx, content := range *contentToAdd {
line := content.line
// Adjust line numbers such that there are no "empty lines or comment lines of next nodes" before them
for idx := line - 1; idx >= 0; idx-- {
if isEmptyLineOrComment((*linesSlice)[idx]) {
(*contentToAdd)[contentIdx].line -= 1
} else {
break
}
}
}
}
func adjustFixedListLines(originalList, fixedList *[]nodeInfo) {
differenceAtTop := (*originalList)[0].node.Line - (*fixedList)[0].node.Line
if differenceAtTop <= 0 {
return
}
for _, node := range *fixedList {
// line numbers should not be changed for new nodes.
if node.node.Line != 0 {
node.node.Line += differenceAtTop
}
}
return
}
func enocodeIntoYaml(parentNode *yaml.Node, nodeList *[]nodeInfo, tracker int) (string, error) {
content := make([]*yaml.Node, 0)
currentNode := (*nodeList)[tracker].node
content = append(content, currentNode)
// Add the value in "key-value" pair to construct if the parent is mapping node
if parentNode.Kind == yaml.MappingNode {
valueNode := (*nodeList)[tracker+1].node
content = append(content, valueNode)
}
// The parent is added at the top to encode into YAML
parentForContent := yaml.Node{
Kind: parentNode.Kind,
Content: content,
}
buf := new(bytes.Buffer)
encoder := yaml.NewEncoder(buf)
encoder.SetIndent(2)
errorEncoding := encoder.Encode(parentForContent)
if errorEncoding != nil {
return "", fmt.Errorf("Error debugging node, %v", errorEncoding.Error())
}
errorClosingEncoder := encoder.Close()
if errorClosingEncoder != nil {
return "", fmt.Errorf("Error closing encoder: %v", errorClosingEncoder.Error())
}
return fmt.Sprintf(`%v`, buf.String()), nil
}
func getContent(parentNode *yaml.Node, nodeList *[]nodeInfo, tracker int) string {
content, err := enocodeIntoYaml(parentNode, nodeList, tracker)
if err != nil {
logger.L().Fatal("Cannot Encode into YAML")
}
indentationSpaces := parentNode.Column - 1
content = indentContent(content, indentationSpaces)
return strings.TrimSuffix(content, "\n")
}
func indentContent(content string, indentationSpaces int) string {
indentedContent := ""
indentSpaces := strings.Repeat(" ", indentationSpaces)
scanner := bufio.NewScanner(strings.NewReader(content))
for scanner.Scan() {
line := scanner.Text()
indentedContent += (indentSpaces + line + "\n")
}
return indentedContent
}
func getLineToInsert(fixInfoMetadata *fixInfoMetadata) int {
var lineToInsert int
// Check if lineToInsert is last line
if fixInfoMetadata.originalListTracker < 0 {
originalListTracker := int(math.Abs(float64(fixInfoMetadata.originalListTracker)))
// Storing the negative value of line of last node as a placeholder to determine the last line later.
lineToInsert = -(*fixInfoMetadata.originalList)[originalListTracker].node.Line
} else {
lineToInsert = (*fixInfoMetadata.originalList)[fixInfoMetadata.originalListTracker].node.Line - 1
}
return lineToInsert
}
func assignLastLine(contentsToAdd *[]contentToAdd, linesToRemove *[]linesToRemove, linesSlice *[]string) {
for idx, contentToAdd := range *contentsToAdd {
if contentToAdd.line < 0 {
currentLine := int(math.Abs(float64(contentToAdd.line)))
(*contentsToAdd)[idx].line, _ = getLastLineOfResource(linesSlice, currentLine)
}
}
for idx, lineToRemove := range *linesToRemove {
if lineToRemove.endLine < 0 {
endLine, _ := getLastLineOfResource(linesSlice, lineToRemove.startLine)
(*linesToRemove)[idx].endLine = endLine
}
}
}
func getLastLineOfResource(linesSlice *[]string, currentLine int) (int, error) {
// Get lastlines of all resources...
lastLinesOfResources := make([]int, 0)
for lineNumber, lineContent := range *linesSlice {
if lineContent == "---" {
for lastLine := lineNumber - 1; lastLine >= 0; lastLine-- {
if !isEmptyLineOrComment((*linesSlice)[lastLine]) {
lastLinesOfResources = append(lastLinesOfResources, lastLine+1)
break
}
}
}
}
lastLine := len(*linesSlice)
for lastLine >= 0 {
if !isEmptyLineOrComment((*linesSlice)[lastLine-1]) {
lastLinesOfResources = append(lastLinesOfResources, lastLine)
break
} else {
lastLine--
}
}
// Get last line of the resource we need
for _, endLine := range lastLinesOfResources {
if currentLine <= endLine {
return endLine, nil
}
}
return 0, fmt.Errorf("Provided line is greater than the length of YAML file")
}
func getNodeLine(nodeList *[]nodeInfo, tracker int) int {
if tracker < len(*nodeList) {
return (*nodeList)[tracker].node.Line
} else {
return -1
}
}
// Checks if the node is value node in "key-value" pairs of mapping node
func isValueNodeinMapping(node *nodeInfo) bool {
if node.parent.Kind == yaml.MappingNode && node.index%2 != 0 {
return true
}
return false
}
// Checks if the node is part of single line sequence node and returns the line
func isOneLineSequenceNode(list *[]nodeInfo, currentTracker int) (bool, int) {
parentNode := (*list)[currentTracker].parent
if parentNode.Kind != yaml.SequenceNode {
return false, -1
}
var currentNode, prevNode nodeInfo
currentTracker -= 1
for (*list)[currentTracker].node != parentNode {
currentNode = (*list)[currentTracker]
prevNode = (*list)[currentTracker-1]
if currentNode.node.Line != prevNode.node.Line {
return false, -1
}
currentTracker -= 1
}
parentNodeInfo := (*list)[currentTracker]
if parentNodeInfo.parent.Kind == yaml.MappingNode {
keyNodeInfo := (*list)[currentTracker-1]
if keyNodeInfo.node.Line == parentNode.Line {
return true, parentNode.Line
} else {
return false, -1
}
} else {
if parentNodeInfo.parent.Line == parentNode.Line {
return true, parentNode.Line
} else {
return false, -1
}
}
}
// Checks if nodes are of same kind, value, line and column
func isSameNode(nodeOne, nodeTwo *yaml.Node) bool {
sameLines := nodeOne.Line == nodeTwo.Line
sameColumns := nodeOne.Column == nodeTwo.Column
sameKinds := nodeOne.Kind == nodeTwo.Kind
sameValues := nodeOne.Value == nodeTwo.Value
return sameKinds && sameValues && sameLines && sameColumns
}
// Checks if the line is empty or a comment
func isEmptyLineOrComment(lineContent string) bool {
lineContent = strings.TrimSpace(lineContent)
if lineContent == "" {
return true
} else if lineContent[0:1] == "#" {
return true
}
return false
}
func readDocuments(reader io.Reader, decoder yqlib.Decoder) (*list.List, error) {
err := decoder.Init(reader)
if err != nil {
return nil, fmt.Errorf("Error Initializing the decoder, %w", err)
}
inputList := list.New()
var currentIndex uint
for {
candidateNode, errorReading := decoder.Decode()
if errors.Is(errorReading, io.EOF) {
switch reader := reader.(type) {
case *os.File:
safelyCloseFile(reader)
}
return inputList, nil
} else if errorReading != nil {
return nil, fmt.Errorf("Error Decoding YAML file, %w", errorReading)
}
candidateNode.Document = currentIndex
candidateNode.EvaluateTogether = true
inputList.PushBack(candidateNode)
currentIndex = currentIndex + 1
}
}
func safelyCloseFile(file *os.File) {
err := file.Close()
if err != nil {
logger.L().Error("Error Closing File")
}
}
// Remove the entire line and replace it with the sequence node in fixed info. This way,
// the original formatting is lost.
func replaceSingleLineSequence(fixInfoMetadata *fixInfoMetadata, line int) (int, int) {
originalListTracker := getFirstNodeInLine(fixInfoMetadata.originalList, line)
fixedListTracker := getFirstNodeInLine(fixInfoMetadata.fixedList, line)
currentDFSNode := (*fixInfoMetadata.fixedList)[fixedListTracker]
contentToInsert := getContent(currentDFSNode.parent, fixInfoMetadata.fixedList, fixedListTracker)
// Remove the Single line
*fixInfoMetadata.linesToRemove = append(*fixInfoMetadata.linesToRemove, linesToRemove{
startLine: line,
endLine: line,
})
// Encode entire Sequence Node and Insert
*fixInfoMetadata.contentToAdd = append(*fixInfoMetadata.contentToAdd, contentToAdd{
line: line,
content: contentToInsert,
})
originalListTracker = updateTracker(fixInfoMetadata.originalList, originalListTracker)
fixedListTracker = updateTracker(fixInfoMetadata.fixedList, fixedListTracker)
return originalListTracker, fixedListTracker
}
// Returns the first node in the given line that is not mapping node
func getFirstNodeInLine(list *[]nodeInfo, line int) int {
tracker := 0
currentNode := (*list)[tracker].node
for currentNode.Line != line || currentNode.Kind == yaml.MappingNode {
tracker += 1
currentNode = (*list)[tracker].node
}
return tracker
}
// To not mess with the line number while inserting, removed lines are not deleted but replaced with "*"
func removeLines(linesToRemove *[]linesToRemove, linesSlice *[]string) {
var startLine, endLine int
for _, lineToRemove := range *linesToRemove {
startLine = lineToRemove.startLine - 1
endLine = lineToRemove.endLine - 1
for line := startLine; line <= endLine; line++ {
lineContent := (*linesSlice)[line]
// When determining the endLine, empty lines and comments which are not intended to be removed are included.
// To deal with that, we need to refrain from removing empty lines and comments
if isEmptyLineOrComment(lineContent) {
break
}
(*linesSlice)[line] = "*"
}
}
}
// Skips the current node including it's children in DFS order and returns the new tracker.
func skipCurrentNode(node *yaml.Node, currentTracker int) int {
updatedTracker := currentTracker + getChildrenCount(node)
return updatedTracker
}
func getChildrenCount(node *yaml.Node) int {
totalChildren := 1
for _, child := range node.Content {
totalChildren += getChildrenCount(child)
}
return totalChildren
}
// The current node along with it's children is skipped and the tracker is moved to next sibling
// of current node. If parent is mapping node, "value" in "key-value" pairs is also skipped.
func updateTracker(nodeList *[]nodeInfo, tracker int) int {
currentNode := (*nodeList)[tracker]
var updatedTracker int
if currentNode.parent.Kind == yaml.MappingNode {
valueNode := (*nodeList)[tracker+1]
updatedTracker = skipCurrentNode(valueNode.node, tracker+1)
} else {
updatedTracker = skipCurrentNode(currentNode.node, tracker)
}
return updatedTracker
}
func getStringFromSlice(yamlLines []string) (fixedYamlString string) {
return strings.Join(yamlLines, "\n")
}

View File

@@ -36,7 +36,7 @@ spec:
effect: NoSchedule
containers:
- name: host-sensor
image: quay.io/kubescape/host-scanner:v1.0.32
image: quay.io/kubescape/host-scanner:v1.0.39
securityContext:
privileged: true
readOnlyRootFilesystem: true

View File

@@ -3,6 +3,7 @@ package hostsensorutils
import (
"encoding/json"
"fmt"
"strings"
"sync"
logger "github.com/kubescape/go-logger"
@@ -99,6 +100,30 @@ func (hsh *HostSensorHandler) sendAllPodsHTTPGETRequest(path, requestKind string
return res, nil
}
// return host-scanner version
func (hsh *HostSensorHandler) GetVersion() (string, error) {
// loop over pods and port-forward it to each of them
podList, err := hsh.getPodList()
if err != nil {
return "", fmt.Errorf("failed to sendAllPodsHTTPGETRequest: %v", err)
}
// initialization of the channels
hsh.workerPool.init(len(podList))
hsh.workerPool.hostSensorApplyJobs(podList, "/version", "version")
for job := range hsh.workerPool.jobs {
resBytes, err := hsh.HTTPGetToPod(job.podName, job.path)
if err != nil {
return "", err
} else {
version := strings.ReplaceAll(string(resBytes), "\"", "")
version = strings.ReplaceAll(version, "\n", "")
return version, nil
}
}
return "", nil
}
// return list of LinuxKernelVariables
func (hsh *HostSensorHandler) GetKernelVariables() ([]hostsensor.HostSensorDataEnvelope, error) {
// loop over pods and port-forward it to each of them
@@ -135,6 +160,12 @@ func (hsh *HostSensorHandler) GetControlPlaneInfo() ([]hostsensor.HostSensorData
return hsh.sendAllPodsHTTPGETRequest("/controlPlaneInfo", ControlPlaneInfo)
}
// return list of KubeProxyInfo
func (hsh *HostSensorHandler) GetCloudProviderInfo() ([]hostsensor.HostSensorDataEnvelope, error) {
// loop over pods and port-forward it to each of them
return hsh.sendAllPodsHTTPGETRequest("/cloudProviderInfo", CloudProviderInfo)
}
// return list of KubeletCommandLine
func (hsh *HostSensorHandler) GetKubeletCommandLine() ([]hostsensor.HostSensorDataEnvelope, error) {
// loop over pods and port-forward it to each of them
@@ -192,6 +223,16 @@ func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnv
var kcData []hostsensor.HostSensorDataEnvelope
var err error
logger.L().Debug("Accessing host scanner")
version, err := hsh.GetVersion()
if err != nil {
logger.L().Warning(err.Error())
}
if len(version) > 0 {
logger.L().Info("Host scanner version : " + version)
} else {
logger.L().Info("Unknown host scanner version")
}
//
kcData, err = hsh.GetKubeletConfigurations()
if err != nil {
addInfoToMap(KubeletConfiguration, infoMap, err)
@@ -285,6 +326,16 @@ func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnv
res = append(res, kcData...)
}
// GetCloudProviderInfo
kcData, err = hsh.GetCloudProviderInfo()
if err != nil {
addInfoToMap(CloudProviderInfo, infoMap, err)
logger.L().Warning(err.Error())
}
if len(kcData) > 0 {
res = append(res, kcData...)
}
logger.L().Debug("Done reading information from host scanner")
return res, infoMap, nil
}

View File

@@ -16,6 +16,7 @@ var (
KubeletInfo = "KubeletInfo"
KubeProxyInfo = "KubeProxyInfo"
ControlPlaneInfo = "ControlPlaneInfo"
CloudProviderInfo = "CloudProviderInfo"
MapHostSensorResourceToApiGroup = map[string]string{
KubeletConfiguration: "hostdata.kubescape.cloud/v1beta0",
@@ -28,6 +29,7 @@ var (
KubeletInfo: "hostdata.kubescape.cloud/v1beta0",
KubeProxyInfo: "hostdata.kubescape.cloud/v1beta0",
ControlPlaneInfo: "hostdata.kubescape.cloud/v1beta0",
CloudProviderInfo: "hostdata.kubescape.cloud/v1beta0",
}
)

View File

@@ -35,6 +35,7 @@ type OPAProcessor struct {
func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *resources.RegoDependenciesData) *OPAProcessor {
if regoDependenciesData != nil && sessionObj != nil {
regoDependenciesData.PostureControlInputs = sessionObj.RegoInputData.PostureControlInputs
regoDependenciesData.DataControlInputs = sessionObj.RegoInputData.DataControlInputs
}
return &OPAProcessor{
OPASessionObj: sessionObj,
@@ -153,12 +154,16 @@ func (opap *OPAProcessor) processControl(control *reporthandling.Control) (map[s
func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule, fixedControlInputs map[string][]string) (map[string]*resourcesresults.ResourceAssociatedRule, error) {
postureControlInputs := opap.regoDependenciesData.GetFilteredPostureControlInputs(rule.ConfigInputs) // get store
dataControlInputs := map[string]string{"cloudProvider": opap.OPASessionObj.Report.ClusterCloudProvider}
// Merge configurable control input and fixed control input
for k, v := range fixedControlInputs {
postureControlInputs[k] = v
}
RuleRegoDependenciesData := resources.RegoDependenciesData{DataControlInputs: dataControlInputs,
PostureControlInputs: postureControlInputs}
inputResources, err := reporthandling.RegoResourcesAggregator(rule, getAllSupportedObjects(opap.K8SResources, opap.ArmoResource, opap.AllResources, rule))
if err != nil {
return nil, fmt.Errorf("error getting aggregated k8sObjects: %s", err.Error())
@@ -185,7 +190,7 @@ func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule, fixedCont
opap.AllResources[inputResources[i].GetID()] = inputResources[i]
}
ruleResponses, err := opap.runOPAOnSingleRule(rule, inputRawResources, ruleData, postureControlInputs)
ruleResponses, err := opap.runOPAOnSingleRule(rule, inputRawResources, ruleData, RuleRegoDependenciesData)
if err != nil {
// TODO - Handle error
logger.L().Error(err.Error())
@@ -217,16 +222,16 @@ func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule, fixedCont
return resources, err
}
func (opap *OPAProcessor) runOPAOnSingleRule(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, postureControlInputs map[string][]string) ([]reporthandling.RuleResponse, error) {
func (opap *OPAProcessor) runOPAOnSingleRule(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, ruleRegoDependenciesData resources.RegoDependenciesData) ([]reporthandling.RuleResponse, error) {
switch rule.RuleLanguage {
case reporthandling.RegoLanguage, reporthandling.RegoLanguage2:
return opap.runRegoOnK8s(rule, k8sObjects, getRuleData, postureControlInputs)
return opap.runRegoOnK8s(rule, k8sObjects, getRuleData, ruleRegoDependenciesData)
default:
return nil, fmt.Errorf("rule: '%s', language '%v' not supported", rule.Name, rule.RuleLanguage)
}
}
func (opap *OPAProcessor) runRegoOnK8s(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, postureControlInputs map[string][]string) ([]reporthandling.RuleResponse, error) {
func (opap *OPAProcessor) runRegoOnK8s(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, ruleRegoDependenciesData resources.RegoDependenciesData) ([]reporthandling.RuleResponse, error) {
// compile modules
modules, err := getRuleDependencies()
@@ -239,7 +244,7 @@ func (opap *OPAProcessor) runRegoOnK8s(rule *reporthandling.PolicyRule, k8sObjec
return nil, fmt.Errorf("in 'runRegoOnSingleRule', failed to compile rule, name: %s, reason: %s", rule.Name, err.Error())
}
store, err := resources.TOStorage(postureControlInputs)
store, err := ruleRegoDependenciesData.TOStorage()
if err != nil {
return nil, err
}
@@ -282,8 +287,12 @@ func (opap *OPAProcessor) enumerateData(rule *reporthandling.PolicyRule, k8sObje
return k8sObjects, nil
}
postureControlInputs := opap.regoDependenciesData.GetFilteredPostureControlInputs(rule.ConfigInputs)
dataControlInputs := map[string]string{"cloudProvider": opap.OPASessionObj.Report.ClusterCloudProvider}
ruleResponse, err := opap.runOPAOnSingleRule(rule, k8sObjects, ruleEnumeratorData, postureControlInputs)
RuleRegoDependenciesData := resources.RegoDependenciesData{DataControlInputs: dataControlInputs,
PostureControlInputs: postureControlInputs}
ruleResponse, err := opap.runOPAOnSingleRule(rule, k8sObjects, ruleEnumeratorData, RuleRegoDependenciesData)
if err != nil {
return nil, err
}

View File

@@ -4,6 +4,8 @@ import (
"fmt"
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/k8s-interface/cloudsupport"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/pkg/resourcehandler"
)
@@ -49,6 +51,17 @@ func (policyHandler *PolicyHandler) CollectResources(policyIdentifier []cautils.
func (policyHandler *PolicyHandler) getResources(policyIdentifier []cautils.PolicyIdentifier, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) error {
opaSessionObj.Report.ClusterAPIServerInfo = policyHandler.resourceHandler.GetClusterAPIServerInfo()
// attempting to get cloud provider from API server git version
if opaSessionObj.Report.ClusterAPIServerInfo != nil {
opaSessionObj.Report.ClusterCloudProvider = cloudsupport.GetCloudProvider(opaSessionObj.Report.ClusterAPIServerInfo.GitVersion)
}
// if didn't succeed getting cloud provider from API server git version, try from context.
if opaSessionObj.Report.ClusterCloudProvider == "" {
clusterName := k8sinterface.GetContextName()
opaSessionObj.Report.ClusterCloudProvider = cloudsupport.GetCloudProvider(clusterName)
}
resourcesMap, allResources, ksResources, err := policyHandler.resourceHandler.GetResources(opaSessionObj, &policyIdentifier[0].Designators)
if err != nil {
return err

View File

@@ -76,9 +76,10 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess
}
if err := fileHandler.registryAdaptors.collectImagesVulnerabilities(k8sResources, allResources, ksResources); err != nil {
logger.L().Warning("failed to collect images vulnerabilities", helpers.Error(err))
}
// Should Kubescape scan image related controls when scanning local files?
// if err := fileHandler.registryAdaptors.collectImagesVulnerabilities(k8sResources, allResources, ksResources); err != nil {
// logger.L().Warning("failed to collect images vulnerabilities", helpers.Error(err))
// }
cautils.StopSpinner()
logger.L().Success("Done accessing local objects")

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"path/filepath"
giturl "github.com/armosec/go-git-url"
giturl "github.com/kubescape/go-git-url"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"

View File

@@ -88,7 +88,7 @@ func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessio
logger.L().Info("Requesting images vulnerabilities results")
cautils.StartSpinner()
if err := k8sHandler.registryAdaptors.collectImagesVulnerabilities(k8sResourcesMap, allResources, ksResourceMap); err != nil {
logger.L().Warning("failed to collect image vulnerabilities", helpers.Error(err))
logger.L().Warning("failed to collect image vulnerabilities", helpers.Error(err), helpers.String("Read more here", "https://hub.armosec.io/docs/configuration-of-image-vulnerabilities"))
cautils.SetInfoMapForResources(fmt.Sprintf("failed to pull image scanning data: %s. for more information: https://hub.armosec.io/docs/configuration-of-image-vulnerabilities", err.Error()), imgVulnResources, sessionObj.InfoMap)
} else {
if isEmptyImgVulns(*ksResourceMap) {

View File

@@ -23,6 +23,7 @@ var (
KubeletInfo = "KubeletInfo"
KubeProxyInfo = "KubeProxyInfo"
ControlPlaneInfo = "ControlPlaneInfo"
CloudProviderInfo = "CloudProviderInfo"
MapResourceToApiGroup = map[string]string{
KubeletConfiguration: "hostdata.kubescape.cloud/v1beta0",
@@ -35,6 +36,7 @@ var (
KubeletInfo: "hostdata.kubescape.cloud/v1beta0",
KubeProxyInfo: "hostdata.kubescape.cloud/v1beta0",
ControlPlaneInfo: "hostdata.kubescape.cloud/v1beta0",
CloudProviderInfo: "hostdata.kubescape.cloud/v1beta0",
}
MapResourceToApiGroupVuln = map[string][]string{
ImageVulnerabilities: {"armo.vuln.images/v1", "image.vulnscan.com/v1"}}

View File

@@ -8,8 +8,8 @@ import (
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
gcpadaptorv1 "github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/gcp/v1"
armosecadaptorv1 "github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/armosec/v1"
gcpadaptorv1 "github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/gcp/v1"
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
"github.com/kubescape/opa-utils/shared"

View File

@@ -6,11 +6,11 @@ import (
nethttp "net/http"
"os"
giturl "github.com/armosec/go-git-url"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
giturl "github.com/kubescape/go-git-url"
)
// To Check if the given repository is Public(No Authentication needed), send a HTTP GET request to the URL

View File

@@ -1,7 +1,7 @@
package resourcehandler
import (
giturl "github.com/armosec/go-git-url"
giturl "github.com/kubescape/go-git-url"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/workloadinterface"

View File

@@ -3,6 +3,8 @@ package resourcesprioritization
import (
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
@@ -19,7 +21,8 @@ func NewResourcesPrioritizationHandler(attackTracksGetter getter.IAttackTracksGe
attackTracks: make([]v1alpha1.IAttackTrack, 0),
}
if tracks, err := attackTracksGetter.GetAttackTracks(); err != nil {
tracks, err := attackTracksGetter.GetAttackTracks()
if err != nil {
return nil, err
} else {
for _, attackTrack := range tracks {
@@ -36,6 +39,12 @@ func NewResourcesPrioritizationHandler(attackTracksGetter getter.IAttackTracksGe
return nil, fmt.Errorf("expected to find at least one attack track")
}
// Store attack tracks in cache
cache := getter.GetDefaultPath(cautils.LocalAttackTracksFilename)
if err := getter.SaveInFile(tracks, cache); err != nil {
logger.L().Warning("failed to cache file", helpers.String("file", cache), helpers.Error(err))
}
return handler, nil
}
@@ -55,7 +64,8 @@ func (handler *ResourcesPrioritizationHandler) PrioritizeResources(sessionObj *c
resourcePriorityVector := []prioritization.ControlsVector{}
resource, exist := sessionObj.AllResources[resourceId]
if !exist {
return fmt.Errorf("expected to find resource id '%s' in scanned resources map", resourceId)
logger.L().Error("resource not found in resources map", helpers.String("resource ID", resourceId))
continue
}
workload := workloadinterface.NewWorkloadObj(resource.GetObject())

View File

@@ -142,7 +142,7 @@
<tr>
<td class="resourceSeverityCell">{{ .Severity }}</td>
<td class="resourceNameCell">{{ .Name }}</td>
<td class="resourceURLCell"><a href="https://hub.armosec.io/docs/{{ lower .URL }}">{{ .URL }}</a></td>
<td class="resourceURLCell"><a href="{{ lower .URL }}">{{ .ID }}</a></td>
<td class="resourceRemediationCell">{{ range .FailedPaths }} <p>{{ . }}</p> {{ end }}</td>
</tr>
{{ end }}

View File

@@ -130,10 +130,11 @@ func buildResourceTableView(opaSessionObj *cautils.OPASessionObj) ResourceTableV
func buildResourceControlResult(resourceControl resourcesresults.ResourceAssociatedControl, control reportsummary.IControlSummary) ResourceControlResult {
ctlSeverity := apis.ControlSeverityToString(control.GetScoreFactor())
ctlName := resourceControl.GetName()
ctlURL := resourceControl.GetID()
ctlID := resourceControl.GetID()
ctlURL := cautils.GetControlLink(resourceControl.GetID())
failedPaths := append(failedPathsToString(&resourceControl), fixPathsToString(&resourceControl)...)
return ResourceControlResult{ctlSeverity, ctlName, ctlURL, failedPaths}
return ResourceControlResult{ctlSeverity, ctlName, ctlID, ctlURL, failedPaths}
}
func buildResourceControlResultTable(resourceControls []resourcesresults.ResourceAssociatedControl, summaryDetails *reportsummary.SummaryDetails) []ResourceControlResult {

View File

@@ -4,6 +4,7 @@ import (
"encoding/xml"
"fmt"
"os"
"sort"
"strings"
logger "github.com/kubescape/go-logger"
@@ -11,9 +12,8 @@ import (
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
"github.com/kubescape/opa-utils/shared"
)
/*
@@ -36,7 +36,6 @@ type JUnitTestSuites struct {
XMLName xml.Name `xml:"testsuites"`
Suites []JUnitTestSuite `xml:"testsuite"` // list of controls
Errors int `xml:"errors,attr"` // total number of tests with error result from all testsuites
Disabled int `xml:"disabled,attr"` // total number of disabled tests from all testsuites
Failures int `xml:"failures,attr"` // total number of failed tests from all testsuites
Tests int `xml:"tests,attr"` // total number of tests from all testsuites. Some software may expect to only see the number of successful tests from all testsuites though
Time string `xml:"time,attr"` // time in seconds to execute all test suites
@@ -46,8 +45,8 @@ type JUnitTestSuites struct {
// JUnitTestSuite represents a single control
type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
Tests int `xml:"tests,attr"` // total number of tests from this testsuite. Some software may expect to only see the number of successful tests though
Name string `xml:"name,attr"` // Full (class) name of the test for non-aggregated testsuite documents. Class name without the package for aggregated testsuites documents. Required
Disabled int `xml:"disabled,attr"` // The total number of disabled tests in the suite. optional. not supported by maven surefire.
Errors int `xml:"errors,attr"` // The total number of tests in the suite that errors
Failures int `xml:"failures,attr"` // The total number of tests in the suite that failed
Hostname string `xml:"hostname,attr"` // Host on which the tests were executed ? cluster name ?
@@ -55,7 +54,6 @@ type JUnitTestSuite struct {
Skipped string `xml:"skipped,attr"` // The total number of skipped tests
Time string `xml:"time,attr"` // Time taken (in seconds) to execute the tests in the suite
Timestamp string `xml:"timestamp,attr"` // when the test was executed in ISO 8601 format (2014-01-21T16:17:18)
File string `xml:"file,attr"` // The file be tested
Properties []JUnitProperty `xml:"properties>property,omitempty"`
TestCases []JUnitTestCase `xml:"testcase"`
}
@@ -64,7 +62,6 @@ type JUnitTestSuite struct {
type JUnitTestCase struct {
XMLName xml.Name `xml:"testcase"`
Classname string `xml:"classname,attr"` // Full class name for the class the test method is in. required
Status string `xml:"status,attr"` // Status
Name string `xml:"name,attr"` // Name of the test method, required
Time string `xml:"time,attr"` // Time taken (in seconds) to execute the test. optional
SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"`
@@ -89,11 +86,6 @@ type JUnitFailure struct {
Contents string `xml:",chardata"`
}
const (
lineSeparator = "\n===================================================================================================================\n\n"
testCaseTypeResources = "Resources"
)
func NewJunitPrinter(verbose bool) *JunitPrinter {
return &JunitPrinter{
verbose: verbose,
@@ -124,118 +116,104 @@ func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionO
func testsSuites(results *cautils.OPASessionObj) *JUnitTestSuites {
return &JUnitTestSuites{
Suites: listTestsSuite(results),
Tests: results.Report.SummaryDetails.NumberOfResources().All(),
Tests: results.Report.SummaryDetails.NumberOfControls().All(),
Name: "Kubescape Scanning",
Failures: results.Report.SummaryDetails.NumberOfResources().Failed(),
Failures: results.Report.SummaryDetails.NumberOfControls().Failed(),
}
}
// aggregate resources source to a list of resources results
func sourceToResourcesResults(results *cautils.OPASessionObj) map[string][]resourcesresults.Result {
resourceResults := make(map[string][]resourcesresults.Result)
for i := range results.ResourceSource {
if r, ok := results.ResourcesResult[i]; ok {
if _, ok := resourceResults[results.ResourceSource[i].RelativePath]; !ok {
resourceResults[results.ResourceSource[i].RelativePath] = []resourcesresults.Result{}
}
resourceResults[results.ResourceSource[i].RelativePath] = append(resourceResults[results.ResourceSource[i].RelativePath], r)
}
}
return resourceResults
}
// listTestsSuite returns a list of testsuites
func listTestsSuite(results *cautils.OPASessionObj) []JUnitTestSuite {
var testSuites []JUnitTestSuite
resourceResults := sourceToResourcesResults(results)
counter := 0
// control scan
for path, resourcesResult := range resourceResults {
if len(results.Report.SummaryDetails.ListFrameworks()) == 0 {
testSuite := JUnitTestSuite{}
testSuite.Tests = results.Report.SummaryDetails.NumberOfControls().All()
testSuite.Failures = results.Report.SummaryDetails.NumberOfControls().Failed()
testSuite.Timestamp = results.Report.ReportGenerationTime.String()
testSuite.ID = counter
counter++
testSuite.File = path
testSuite.TestCases = testsCases(results, resourcesResult)
if len(testSuite.TestCases) > 0 {
testSuites = append(testSuites, testSuite)
}
testSuite.ID = 0
testSuite.Name = "kubescape"
testSuite.Properties = properties(results.Report.SummaryDetails.Score)
testSuite.TestCases = testsCases(results, &results.Report.SummaryDetails.Controls, "Kubescape")
testSuites = append(testSuites, testSuite)
return testSuites
}
for i, f := range results.Report.SummaryDetails.Frameworks {
testSuite := JUnitTestSuite{}
testSuite.Tests = f.NumberOfControls().All()
testSuite.Failures = f.NumberOfControls().Failed()
testSuite.Timestamp = results.Report.ReportGenerationTime.String()
testSuite.ID = i
testSuite.Name = f.Name
testSuite.Properties = properties(f.Score)
testSuite.TestCases = testsCases(results, f.GetControls(), f.GetName())
testSuites = append(testSuites, testSuite)
}
return testSuites
}
func failedControlsToFailureMessage(results *cautils.OPASessionObj, controls []resourcesresults.ResourceAssociatedControl, severityCounter []int) string {
msg := ""
for _, c := range controls {
control := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, c.GetID())
if c.GetStatus(nil).IsFailed() {
msg += fmt.Sprintf("Test: %s\n", control.GetName())
msg += fmt.Sprintf("Severity: %s\n", apis.ControlSeverityToString(control.GetScoreFactor()))
msg += fmt.Sprintf("Remediation: %s\n", control.GetRemediation())
msg += fmt.Sprintf("Link: %s\n", getControlLink(control.GetID()))
if failedPaths := failedPathsToString(&c); len(failedPaths) > 0 {
msg += fmt.Sprintf("Failed paths: \n - %s\n", strings.Join(failedPaths, "\n - "))
}
if fixPaths := fixPathsToString(&c); len(fixPaths) > 0 {
msg += fmt.Sprintf("Available fix: \n - %s\n", strings.Join(fixPaths, "\n - "))
}
msg += "\n"
severityCounter[apis.ControlSeverityToInt(control.GetScoreFactor())] += 1
}
}
return msg
}
// Every testCase includes a file (even if the file contains several resources)
func testsCases(results *cautils.OPASessionObj, resourcesResult []resourcesresults.Result) []JUnitTestCase {
func testsCases(results *cautils.OPASessionObj, controls reportsummary.IControlsSummaries, classname string) []JUnitTestCase {
var testCases []JUnitTestCase
testCase := JUnitTestCase{}
testCaseFailure := JUnitFailure{}
testCaseFailure.Type = testCaseTypeResources
message := ""
// severityCounter represents the severities, 0: Unknown, 1: Low, 2: Medium, 3: High, 4: Critical
severityCounter := make([]int, apis.NumberOfSeverities, apis.NumberOfSeverities)
iter := controls.ListControlsIDs().All()
for iter.HasNext() {
cID := iter.Next()
testCase := JUnitTestCase{}
control := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, cID)
testCase.Name = control.GetName()
testCase.Classname = classname
if control.GetStatus().IsFailed() {
resources := map[string]interface{}{}
resourceIDs := control.ListResourcesIDs().Failed()
for j := range resourceIDs {
resource := results.AllResources[resourceIDs[j]]
sourcePath := ""
if ResourceSourcePath, ok := results.ResourceSource[resourceIDs[j]]; ok {
sourcePath = ResourceSourcePath.RelativePath
}
resources[resourceToString(resource, sourcePath)] = nil
}
resourcesStr := shared.MapStringToSlice(resources)
sort.Strings(resourcesStr)
testCaseFailure := JUnitFailure{}
testCaseFailure.Type = "Control"
// testCaseFailure.Contents =
testCaseFailure.Message = fmt.Sprintf("Remediation: %s\nMore details: %s\n\n%s", control.GetRemediation(), cautils.GetControlLink(control.GetID()), strings.Join(resourcesStr, "\n"))
testCase.Failure = &testCaseFailure
} else if control.GetStatus().IsSkipped() {
testCase.SkipMessage = &JUnitSkipMessage{
Message: "", // TODO - fill after statusInfo is supported
}
for i := range resourcesResult {
if failedControls := failedControlsToFailureMessage(results, resourcesResult[i].ListControls(), severityCounter); failedControls != "" {
message += fmt.Sprintf("%sResource: %s\n\n%s", lineSeparator, resourceNameToString(results.AllResources[resourcesResult[i].GetResourceID()]), failedControls)
}
}
testCaseFailure.Message += fmt.Sprintf("%s\n%s", getSummaryMessage(severityCounter), message)
testCase.Failure = &testCaseFailure
if testCase.Failure.Message != "" {
testCases = append(testCases, testCase)
}
return testCases
}
func getSummaryMessage(severityCounter []int) string {
total := 0
severities := ""
for i, count := range severityCounter {
if apis.SeverityNumberToString(i) == apis.SeverityNumberToString(apis.SeverityUnknown) {
continue
}
severities += fmt.Sprintf("%s: %d, ", apis.SeverityNumberToString(i), count)
total += count
}
if len(severities) == 0 {
return ""
}
return fmt.Sprintf("Total: %d (%s)", total, severities[:len(severities)-2])
}
func resourceNameToString(resource workloadinterface.IMetadata) string {
func resourceToString(resource workloadinterface.IMetadata, sourcePath string) string {
sep := "; "
s := ""
s += fmt.Sprintf("kind=%s/", resource.GetKind())
s += fmt.Sprintf("apiVersion: %s", resource.GetApiVersion()) + sep
s += fmt.Sprintf("kind: %s", resource.GetKind()) + sep
if resource.GetNamespace() != "" {
s += fmt.Sprintf("namespace=%s/", resource.GetNamespace())
s += fmt.Sprintf("namespace: %s", resource.GetNamespace()) + sep
}
s += fmt.Sprintf("name: %s", resource.GetName())
if sourcePath != "" {
s += sep + fmt.Sprintf("sourcePath: %s", sourcePath)
}
s += fmt.Sprintf("name=%s", resource.GetName())
return s
}
func properties(riskScore float32) []JUnitProperty {
return []JUnitProperty{
{
Name: "riskScore",
Value: fmt.Sprintf("%.2f", riskScore),
},
}
}

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"os"
"sort"
"strings"
"github.com/enescakir/emoji"
"github.com/kubescape/k8s-interface/workloadinterface"
@@ -84,7 +83,7 @@ func (prettyPrinter *PrettyPrinter) printSummary(controlName string, controlSumm
}
func (prettyPrinter *PrettyPrinter) printTitle(controlSummary reportsummary.IControlSummary) {
cautils.InfoDisplay(prettyPrinter.writer, "[control: %s - %s] ", controlSummary.GetName(), getControlLink(controlSummary.GetID()))
cautils.InfoDisplay(prettyPrinter.writer, "[control: %s - %s] ", controlSummary.GetName(), cautils.GetControlLink(controlSummary.GetID()))
switch controlSummary.GetStatus().Status() {
case apis.StatusSkipped:
cautils.InfoDisplay(prettyPrinter.writer, "skipped %v\n", emoji.ConfusedFace)
@@ -193,7 +192,7 @@ func (prettyPrinter *PrettyPrinter) printSummaryTable(summaryDetails *reportsumm
return
}
cautils.InfoTextDisplay(prettyPrinter.writer, "\n"+controlCountersForSummary(summaryDetails.NumberOfControls())+"\n")
cautils.InfoTextDisplay(prettyPrinter.writer, renderSeverityCountersSummary(&summaryDetails.SeverityCounters)+"\n\n")
cautils.InfoTextDisplay(prettyPrinter.writer, renderSeverityCountersSummary(summaryDetails.GetResourcesSeverityCounters())+"\n\n")
// cautils.InfoTextDisplay(prettyPrinter.writer, "\n"+"Severities: SOME OTHER"+"\n\n")
@@ -255,16 +254,12 @@ func frameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) stri
return ""
}
func getControlLink(controlID string) string {
return fmt.Sprintf("https://hub.armosec.io/docs/%s", strings.ToLower(controlID))
}
// renderSeverityCountersSummary renders the string that reports severity counters summary
func renderSeverityCountersSummary(counters reportsummary.ISeverityCounters) string {
critical := counters.NumberOfResourcesWithCriticalSeverity()
high := counters.NumberOfResourcesWithHighSeverity()
medium := counters.NumberOfResourcesWithMediumSeverity()
low := counters.NumberOfResourcesWithLowSeverity()
critical := counters.NumberOfCriticalSeverity()
high := counters.NumberOfHighSeverity()
medium := counters.NumberOfMediumSeverity()
low := counters.NumberOfLowSeverity()
return fmt.Sprintf(
"Failed Resources by Severity: Critical — %d, High — %d, Medium — %d, Low — %d",

View File

@@ -289,7 +289,7 @@ func (m *Metrics) setRiskScores(summaryDetails *reportsummary.SummaryDetails) {
controlName: control.GetName(),
controlID: control.GetID(),
riskScore: cautils.Float32ToInt(control.GetScore()),
link: getControlLink(control.GetID()),
link: cautils.GetControlLink(control.GetID()),
severity: apis.ControlSeverityToString(control.GetScoreFactor()),
remediation: control.GetRemediation(),
}

Some files were not shown because too many files have changed in this diff Show More