mirror of
https://github.com/kubescape/kubescape.git
synced 2026-04-10 04:36:52 +00:00
Compare commits
252 Commits
v2.0.170
...
fix-comman
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0f65cce1d | ||
|
|
0e8b2f976d | ||
|
|
f1d646ac97 | ||
|
|
5f668037a7 | ||
|
|
c448c97463 | ||
|
|
da3bc8e8ea | ||
|
|
2fce139a9a | ||
|
|
85d2f5c250 | ||
|
|
c3771eec7e | ||
|
|
76d2154152 | ||
|
|
fa5e7fef23 | ||
|
|
38d2696058 | ||
|
|
e9a8ffbda9 | ||
|
|
0d76fffa48 | ||
|
|
218c77f3ae | ||
|
|
89fd7eb439 | ||
|
|
8079f9ae7d | ||
|
|
f9a26b7a95 | ||
|
|
663401d908 | ||
|
|
926790f49d | ||
|
|
566b7c29c1 | ||
|
|
af5cdefc5f | ||
|
|
36b7b8e2ac | ||
|
|
17c52bd0ae | ||
|
|
e02086e90c | ||
|
|
baf62887b9 | ||
|
|
99fa81e411 | ||
|
|
f64200f42f | ||
|
|
f72cb215d7 | ||
|
|
fa03a9dae3 | ||
|
|
48516b891f | ||
|
|
252a564552 | ||
|
|
30e5b9b57d | ||
|
|
7fcfa27d9a | ||
|
|
4b898b0075 | ||
|
|
f3665866af | ||
|
|
a7989bbe76 | ||
|
|
5ce69a750d | ||
|
|
2b61989073 | ||
|
|
be33054973 | ||
|
|
4b9bd5f3ae | ||
|
|
fb1c728b12 | ||
|
|
6964ca0d18 | ||
|
|
691fa61362 | ||
|
|
0c1eda0d08 | ||
|
|
767eac2fa6 | ||
|
|
6f651fa2d0 | ||
|
|
e3362c2e3d | ||
|
|
08b8b68f9a | ||
|
|
daf9ca9e7f | ||
|
|
d1024359c9 | ||
|
|
ed6070aff9 | ||
|
|
e4dbfa3534 | ||
|
|
ddd2b707c0 | ||
|
|
cd4f1077c2 | ||
|
|
b472d1cb9d | ||
|
|
922e2548f4 | ||
|
|
45caa7c120 | ||
|
|
670ae45d62 | ||
|
|
05bcf018d1 | ||
|
|
0af5d2e0bb | ||
|
|
eaf05fe9be | ||
|
|
e97b23f345 | ||
|
|
83a00ded3d | ||
|
|
78f81cc968 | ||
|
|
5d3347b4fe | ||
|
|
64d2ef8170 | ||
|
|
7c1e360b9a | ||
|
|
575d36dcde | ||
|
|
8dba8f7491 | ||
|
|
cc39e5b905 | ||
|
|
0be7e6018f | ||
|
|
7697e3f0c4 | ||
|
|
379800c49f | ||
|
|
79e2515807 | ||
|
|
342f5743e2 | ||
|
|
0e81870b85 | ||
|
|
4277331ee2 | ||
|
|
53561a728f | ||
|
|
d0fd8c4fe4 | ||
|
|
398989510b | ||
|
|
f8e3ad5685 | ||
|
|
fbea7ef874 | ||
|
|
dc2c6f8a21 | ||
|
|
5ee08583b6 | ||
|
|
bfbd278e7c | ||
|
|
4c6e5903e3 | ||
|
|
a7cd5672c1 | ||
|
|
22521b7159 | ||
|
|
e5fb14138e | ||
|
|
1b2242330c | ||
|
|
356958cc55 | ||
|
|
8f1da32001 | ||
|
|
686352a397 | ||
|
|
ef79c42ebc | ||
|
|
c8fc5378c1 | ||
|
|
c296666d8e | ||
|
|
f193e260b0 | ||
|
|
82981a9a54 | ||
|
|
3be54ca484 | ||
|
|
2f2c177674 | ||
|
|
1f47223918 | ||
|
|
eb646696a3 | ||
|
|
7cfe5160d5 | ||
|
|
95135c4379 | ||
|
|
7e604d6a5b | ||
|
|
64ac2666f9 | ||
|
|
05b3459342 | ||
|
|
92ad5f2407 | ||
|
|
e3c60e3202 | ||
|
|
7b5bcb05b1 | ||
|
|
154f94a0af | ||
|
|
063d3ee313 | ||
|
|
79859d05c0 | ||
|
|
acd3a94c46 | ||
|
|
13f09315e7 | ||
|
|
890528bf14 | ||
|
|
e4aafcf81e | ||
|
|
81c3c34ab8 | ||
|
|
b7b83b26b5 | ||
|
|
639cd3dfae | ||
|
|
7cf1302e8a | ||
|
|
dd5dd53a38 | ||
|
|
7275b8eac7 | ||
|
|
408c6fc998 | ||
|
|
5ce638572f | ||
|
|
4b98490ff9 | ||
|
|
6ea18ec75b | ||
|
|
56e2ffec5c | ||
|
|
fa204a208a | ||
|
|
9ab0fc593f | ||
|
|
3b9c454245 | ||
|
|
a6fc7a0da0 | ||
|
|
53ae57e478 | ||
|
|
1d3401e3b4 | ||
|
|
634198df06 | ||
|
|
cffc3953ea | ||
|
|
ea768602fb | ||
|
|
b4fc6dddd3 | ||
|
|
96d90c217e | ||
|
|
a2f1722455 | ||
|
|
400b51df1c | ||
|
|
0f3ce6917e | ||
|
|
11e57fe7ad | ||
|
|
2ddce8723d | ||
|
|
291668647c | ||
|
|
d3c41f2492 | ||
|
|
10fa3cb27d | ||
|
|
d8f95edff5 | ||
|
|
37ffe86d8b | ||
|
|
87fdbfdcc5 | ||
|
|
424a218860 | ||
|
|
faf0ae6bdc | ||
|
|
e46c42554b | ||
|
|
eb16440ba6 | ||
|
|
12f81353e0 | ||
|
|
fd33a8acd1 | ||
|
|
374e268a4f | ||
|
|
405bfbf9ba | ||
|
|
304227200f | ||
|
|
dc10125380 | ||
|
|
51c417ebc3 | ||
|
|
862230f58a | ||
|
|
6416fc56d7 | ||
|
|
a8ad8e5f5a | ||
|
|
d5edf29554 | ||
|
|
4351099e79 | ||
|
|
196d07edc6 | ||
|
|
f4bb03039a | ||
|
|
d6427f0fc8 | ||
|
|
f33a6d7634 | ||
|
|
2b931fb3f0 | ||
|
|
bb586892ba | ||
|
|
cb18f60f82 | ||
|
|
e5023943e5 | ||
|
|
c565dc5af7 | ||
|
|
5634903aa0 | ||
|
|
ce81a9cb22 | ||
|
|
5a01a1a30a | ||
|
|
cb704cb1e7 | ||
|
|
6e2dda7993 | ||
|
|
15e1d6d1a2 | ||
|
|
ba588b9eef | ||
|
|
f48b848eb6 | ||
|
|
f81fd74aa3 | ||
|
|
ad608b08e0 | ||
|
|
f9e80b709a | ||
|
|
f75b62e62c | ||
|
|
1c24a55d4b | ||
|
|
43dbb55d50 | ||
|
|
03418299b8 | ||
|
|
f5bd86593c | ||
|
|
2af78eaab2 | ||
|
|
67cd003afe | ||
|
|
f7f11abfc2 | ||
|
|
52aa5f02e2 | ||
|
|
ce8175be61 | ||
|
|
0bc542f851 | ||
|
|
8e4f88ce5b | ||
|
|
362ea83549 | ||
|
|
4cb7b999ad | ||
|
|
81482b7421 | ||
|
|
2b7807f300 | ||
|
|
ef23d022ee | ||
|
|
02d7fdc4f9 | ||
|
|
ccb3351607 | ||
|
|
bba70b4c46 | ||
|
|
46073e0a6c | ||
|
|
93a44f494d | ||
|
|
5c96f877ed | ||
|
|
23ea7e0511 | ||
|
|
137b3d7b5d | ||
|
|
13ffd92210 | ||
|
|
4725f8b3ca | ||
|
|
6d65a90de9 | ||
|
|
faf928527d | ||
|
|
18c6e80c3c | ||
|
|
b103e817ed | ||
|
|
2db2f55d16 | ||
|
|
b38ce5e812 | ||
|
|
d03806aea2 | ||
|
|
fc3912ca7d | ||
|
|
a76228c1e1 | ||
|
|
9447f2933a | ||
|
|
26d4664cc5 | ||
|
|
acdad028a3 | ||
|
|
de78615038 | ||
|
|
9c764c90e3 | ||
|
|
95a4c19dc6 | ||
|
|
e3352f90e1 | ||
|
|
677a9da80a | ||
|
|
9a7eb4b9a5 | ||
|
|
903b5f39df | ||
|
|
55f0ca3e9e | ||
|
|
3387e677ba | ||
|
|
5774acfc81 | ||
|
|
0eee2d1d0a | ||
|
|
aade1008c4 | ||
|
|
80adf03926 | ||
|
|
4b9c35d53b | ||
|
|
b936c3f857 | ||
|
|
600b9a6fb0 | ||
|
|
3bec2ef0b7 | ||
|
|
609cbff2da | ||
|
|
a03b0c94c4 | ||
|
|
589d0545cb | ||
|
|
32b74608bf | ||
|
|
9454924b9f | ||
|
|
7233f00c32 | ||
|
|
905db42625 | ||
|
|
05d5ad47f2 | ||
|
|
5ccb858d7f |
80
.github/workflows/build-image.yaml
vendored
Normal file
80
.github/workflows/build-image.yaml
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
client:
|
||||||
|
description: 'client name'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
image_tag:
|
||||||
|
description: 'image tag'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
image_name:
|
||||||
|
description: 'image registry and name'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
cosign:
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
description: 'run cosign on released image'
|
||||||
|
support_platforms:
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
description: 'support amd64/arm64'
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
QUAYIO_REGISTRY_USERNAME:
|
||||||
|
required: true
|
||||||
|
QUAYIO_REGISTRY_PASSWORD:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-image:
|
||||||
|
name: Build image and upload to registry
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
packages: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Login to Quay.io
|
||||||
|
env:
|
||||||
|
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||||
|
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||||
|
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- name: Install cosign
|
||||||
|
uses: sigstore/cosign-installer@main
|
||||||
|
with:
|
||||||
|
cosign-release: 'v1.12.0'
|
||||||
|
- name: sign kubescape container image
|
||||||
|
if: ${{ inputs.cosign }}
|
||||||
|
env:
|
||||||
|
COSIGN_EXPERIMENTAL: "true"
|
||||||
|
run: |
|
||||||
|
cosign sign --force ${{ inputs.image_name }}:latest
|
||||||
|
cosign sign --force ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||||
|
|
||||||
164
.github/workflows/build.yaml
vendored
164
.github/workflows/build.yaml
vendored
@@ -7,26 +7,26 @@ on:
|
|||||||
# Do not run the pipeline if only Markdown files changed
|
# Do not run the pipeline if only Markdown files changed
|
||||||
- '**.md'
|
- '**.md'
|
||||||
jobs:
|
jobs:
|
||||||
once:
|
test:
|
||||||
name: Create release
|
uses: ./.github/workflows/test.yaml
|
||||||
runs-on: ubuntu-latest
|
with:
|
||||||
outputs:
|
release: "v2.0.${{ github.run_number }}"
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
client: test
|
||||||
steps:
|
|
||||||
- name: Create a release
|
create-release:
|
||||||
id: create_release
|
uses: ./.github/workflows/release.yaml
|
||||||
uses: actions/create-release@v1
|
needs: test
|
||||||
env:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
release_name: "Release v2.0.${{ github.run_number }}"
|
||||||
with:
|
tag_name: "v2.0.${{ github.run_number }}"
|
||||||
tag_name: v2.0.${{ github.run_number }}
|
secrets: inherit
|
||||||
release_name: Release v2.0.${{ github.run_number }}
|
|
||||||
draft: false
|
publish-artifacts:
|
||||||
prerelease: false
|
name: Build and publish artifacts
|
||||||
build:
|
needs: create-release
|
||||||
name: Create cross-platform release build, tag and upload binaries
|
|
||||||
needs: once
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
@@ -34,46 +34,11 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Cache Go modules (Linux)
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Cache Go modules (macOS)
|
|
||||||
if: matrix.os == 'macos-latest'
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/Library/Caches/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Cache Go modules (Windows)
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~\AppData\Local\go-build
|
|
||||||
~\go\pkg\mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.18
|
go-version: 1.18
|
||||||
# - name: Test cmd pkg
|
|
||||||
# run: cd cmd && go test -v ./...
|
|
||||||
|
|
||||||
- name: Install MSYS2 & libgit2 (Windows)
|
- name: Install MSYS2 & libgit2 (Windows)
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@@ -84,34 +49,20 @@ jobs:
|
|||||||
run: make libgit2
|
run: make libgit2
|
||||||
if: matrix.os != 'windows-latest'
|
if: matrix.os != 'windows-latest'
|
||||||
|
|
||||||
- name: Test core pkg
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: go test -tags=static -v ./...
|
|
||||||
|
|
||||||
- name: Test httphandler pkg
|
|
||||||
run: cd httphandler && go test -tags=static -v ./...
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
RELEASE: v2.0.${{ github.run_number }}
|
RELEASE: v2.0.${{ github.run_number }}
|
||||||
CLIENT: release
|
CLIENT: release
|
||||||
CGO_ENABLED: 1
|
CGO_ENABLED: 1
|
||||||
run: python3 --version && python3 build.py
|
run: python3 --version && python3 build.py
|
||||||
|
|
||||||
- name: Smoke Testing
|
|
||||||
env:
|
|
||||||
RELEASE: v2.0.${{ github.run_number }}
|
|
||||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
|
||||||
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
|
||||||
|
|
||||||
- name: Upload release binaries
|
- name: Upload release binaries
|
||||||
id: upload-release-asset
|
id: upload-release-asset
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ needs.once.outputs.upload_url }}
|
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||||
asset_path: build/${{ matrix.os }}/kubescape
|
asset_path: build/${{ matrix.os }}/kubescape
|
||||||
asset_name: kubescape-${{ matrix.os }}
|
asset_name: kubescape-${{ matrix.os }}
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
@@ -122,64 +73,19 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ needs.once.outputs.upload_url }}
|
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||||
asset_path: build/${{ matrix.os }}/kubescape.sha256
|
asset_path: build/${{ matrix.os }}/kubescape.sha256
|
||||||
asset_name: kubescape-${{ matrix.os }}-sha256
|
asset_name: kubescape-${{ matrix.os }}-sha256
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
build-docker:
|
|
||||||
name: Build docker container, tag and upload to registry
|
publish-image:
|
||||||
needs: build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.repository == 'kubescape/kubescape' }} # TODO
|
if: ${{ github.repository == 'kubescape/kubescape' }} # TODO
|
||||||
permissions:
|
uses: ./.github/workflows/build-image.yaml
|
||||||
id-token: write
|
needs: create-release
|
||||||
packages: write
|
with:
|
||||||
contents: read
|
client: "image-release"
|
||||||
|
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
|
||||||
steps:
|
image_tag: "v2.0.${{ github.run_number }}"
|
||||||
- uses: actions/checkout@v2
|
support_platforms: false
|
||||||
with:
|
cosign: true
|
||||||
submodules: recursive
|
secrets: inherit
|
||||||
|
|
||||||
- name: Set image version
|
|
||||||
id: image-version
|
|
||||||
run: echo '::set-output name=IMAGE_VERSION::v2.0.${{ github.run_number }}'
|
|
||||||
|
|
||||||
- name: Set image name
|
|
||||||
id: image-name
|
|
||||||
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Login to Quay.io
|
|
||||||
env:
|
|
||||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
|
||||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
|
||||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
|
||||||
|
|
||||||
- name: Build the Docker image
|
|
||||||
run: docker buildx build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:latest --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg client=image-release --push --platform linux/amd64,linux/arm64
|
|
||||||
|
|
||||||
# - name: Login to GitHub Container Registry
|
|
||||||
# uses: docker/login-action@v1
|
|
||||||
# with:
|
|
||||||
# registry: ghcr.io
|
|
||||||
# username: ${{ github.actor }}
|
|
||||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# TODO - Wait for casign to support fixed tags -> https://github.com/sigstore/cosign/issues/1424
|
|
||||||
# - name: Install cosign
|
|
||||||
# uses: sigstore/cosign-installer@main
|
|
||||||
# with:
|
|
||||||
# cosign-release: 'v1.5.1' # optional
|
|
||||||
# - name: sign kubescape container image
|
|
||||||
# env:
|
|
||||||
# COSIGN_EXPERIMENTAL: "true"
|
|
||||||
# run: |
|
|
||||||
# cosign sign --force ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
|
|
||||||
# cosign sign --force ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}
|
|
||||||
|
|
||||||
|
|||||||
148
.github/workflows/build_dev.yaml
vendored
148
.github/workflows/build_dev.yaml
vendored
@@ -7,136 +7,20 @@ on:
|
|||||||
# Do not run the pipeline if only Markdown files changed
|
# Do not run the pipeline if only Markdown files changed
|
||||||
- '**.md'
|
- '**.md'
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
test:
|
||||||
name: Create cross-platform dev build
|
uses: ./.github/workflows/test.yaml
|
||||||
runs-on: ${{ matrix.os }}
|
with:
|
||||||
strategy:
|
release: "v2.0.${{ github.run_number }}"
|
||||||
matrix:
|
client: test
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
||||||
steps:
|
publish-dev-image:
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Cache Go modules (Linux)
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Cache Go modules (macOS)
|
|
||||||
if: matrix.os == 'macos-latest'
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/Library/Caches/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Cache Go modules (Windows)
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~\AppData\Local\go-build
|
|
||||||
~\go\pkg\mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: 1.18
|
|
||||||
|
|
||||||
# - name: Test cmd pkg
|
|
||||||
# run: cd cmd && go test -v ./...
|
|
||||||
|
|
||||||
# - name: Test core pkg
|
|
||||||
# env:
|
|
||||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
# run: cd core && go test -v ./...
|
|
||||||
|
|
||||||
# - name: Test cmd pkg
|
|
||||||
# run: cd cmd && go test -v ./...
|
|
||||||
|
|
||||||
- name: Install MSYS2 & libgit2 (Windows)
|
|
||||||
shell: cmd
|
|
||||||
run: .\build.bat all
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
|
|
||||||
- name: Install libgit2 (Linux/macOS)
|
|
||||||
run: make libgit2
|
|
||||||
if: matrix.os != 'windows-latest'
|
|
||||||
|
|
||||||
- name: Test core pkg
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: go test -tags=static -v ./...
|
|
||||||
|
|
||||||
- name: Test httphandler pkg
|
|
||||||
run: cd httphandler && go test -tags=static -v ./...
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
RELEASE: v2.0.${{ github.run_number }}
|
|
||||||
CLIENT: release-dev
|
|
||||||
CGO_ENABLED: 1
|
|
||||||
run: python3 --version && python3 build.py
|
|
||||||
|
|
||||||
- name: Smoke Testing
|
|
||||||
env:
|
|
||||||
RELEASE: v2.0.${{ github.run_number }}
|
|
||||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
|
||||||
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
|
||||||
|
|
||||||
- name: Upload build artifacts
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: kubescape-${{ matrix.os }}
|
|
||||||
path: build/${{ matrix.os }}/kubescape
|
|
||||||
|
|
||||||
build-docker:
|
|
||||||
name: Build docker container, tag and upload to registry
|
|
||||||
needs: build
|
|
||||||
if: ${{ github.repository == 'kubescape/kubescape' }} # TODO
|
if: ${{ github.repository == 'kubescape/kubescape' }} # TODO
|
||||||
runs-on: ubuntu-latest
|
uses: ./.github/workflows/build-image.yaml
|
||||||
permissions:
|
needs: test
|
||||||
id-token: write
|
with:
|
||||||
packages: write
|
client: "image-dev"
|
||||||
contents: read
|
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
|
||||||
|
image_tag: "dev-v2.0.${{ github.run_number }}"
|
||||||
steps:
|
support_platforms: false
|
||||||
- uses: actions/checkout@v2
|
cosign: true
|
||||||
with:
|
secrets: inherit
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Set image version
|
|
||||||
id: image-version
|
|
||||||
run: echo '::set-output name=IMAGE_VERSION::dev-v2.0.${{ github.run_number }}'
|
|
||||||
|
|
||||||
- name: Set image name
|
|
||||||
id: image-name
|
|
||||||
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Login to Quay.io
|
|
||||||
env:
|
|
||||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
|
||||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
|
||||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
|
||||||
|
|
||||||
- name: Build the Docker image
|
|
||||||
run: docker buildx build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg client=image-dev --push --platform linux/amd64,linux/arm64
|
|
||||||
|
|||||||
6
.github/workflows/community.yml
vendored
6
.github/workflows/community.yml
vendored
@@ -17,6 +17,6 @@ jobs:
|
|||||||
- uses: EddieHubCommunity/gh-action-community/src/welcome@main
|
- uses: EddieHubCommunity/gh-action-community/src/welcome@main
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-message: '<h3>Hey, Welcome to this repo, Congratulations on opening your issue. Keep Contributing to Kubescape</h3>'
|
issue-message: '<h3>Hi! Welcome to Kubescape. Thank you for taking the time and reporting an issue</h3>'
|
||||||
pr-message: '<h3>Hey, Welcome to this repo, Congratulations on opening your Pull Request. Keep Contributing to Kubescape</h3>'
|
pr-message: '<h3>Hi! Welcome to Kubescape. Thank you for taking the time and contributing to the open source community</h3>'
|
||||||
footer: '<h4>We''ll try to review and add you work as soon as possible and a maintainer will get back to you soon!</h4>'
|
footer: '<h4>We will try to review as soon as possible!</h4>'
|
||||||
|
|||||||
88
.github/workflows/pr_checks.yaml
vendored
88
.github/workflows/pr_checks.yaml
vendored
@@ -6,87 +6,11 @@ on:
|
|||||||
types: [ edited, opened, synchronize, reopened ]
|
types: [ edited, opened, synchronize, reopened ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
# Do not run the pipeline if only Markdown files changed
|
# Do not run the pipeline if only Markdown files changed
|
||||||
|
- '**.yaml'
|
||||||
- '**.md'
|
- '**.md'
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
test:
|
||||||
name: Create cross-platform build
|
uses: ./.github/workflows/test.yaml
|
||||||
runs-on: ${{ matrix.os }}
|
with:
|
||||||
strategy:
|
release: "v2.0.${{ github.run_number }}"
|
||||||
matrix:
|
client: test
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Cache Go modules (Linux)
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Cache Go modules (macOS)
|
|
||||||
if: matrix.os == 'macos-latest'
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/Library/Caches/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Cache Go modules (Windows)
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~\AppData\Local\go-build
|
|
||||||
~\go\pkg\mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: 1.18
|
|
||||||
|
|
||||||
- name: Install MSYS2 & libgit2 (Windows)
|
|
||||||
shell: cmd
|
|
||||||
run: .\build.bat all
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
|
|
||||||
- name: Install libgit2 (Linux/macOS)
|
|
||||||
run: make libgit2
|
|
||||||
if: matrix.os != 'windows-latest'
|
|
||||||
|
|
||||||
# - name: Test cmd pkg
|
|
||||||
# run: cd cmd && go test -v ./...
|
|
||||||
|
|
||||||
- name: Test core pkg
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: go test -tags=static -v ./...
|
|
||||||
|
|
||||||
- name: Test httphandler pkg
|
|
||||||
run: cd httphandler && go test -tags=static -v ./...
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
RELEASE: v2.0.${{ github.run_number }}
|
|
||||||
CLIENT: test
|
|
||||||
CGO_ENABLED: 1
|
|
||||||
run: python3 --version && python3 build.py
|
|
||||||
|
|
||||||
- name: Smoke Testing
|
|
||||||
env:
|
|
||||||
RELEASE: v2.0.${{ github.run_number }}
|
|
||||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
|
||||||
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
|
||||||
|
|
||||||
|
|||||||
41
.github/workflows/release.yaml
vendored
Normal file
41
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
release_name:
|
||||||
|
description: 'release'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
tag_name:
|
||||||
|
description: 'tag'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
draft:
|
||||||
|
description: 'create draft release'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
outputs:
|
||||||
|
upload_url:
|
||||||
|
description: "The first output string"
|
||||||
|
value: ${{ jobs.release.outputs.upload_url }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Create release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
steps:
|
||||||
|
- name: Create a release
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ inputs.tag_name }}
|
||||||
|
release_name: ${{ inputs.release_name }}
|
||||||
|
draft: ${{ inputs.draft }}
|
||||||
|
prerelease: false
|
||||||
|
|
||||||
93
.github/workflows/test.yaml
vendored
Normal file
93
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
release:
|
||||||
|
description: 'release'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
client:
|
||||||
|
description: 'Client name'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Create cross-platform build
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Cache Go modules (Linux)
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/go-build
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Cache Go modules (macOS)
|
||||||
|
if: matrix.os == 'macos-latest'
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Caches/go-build
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Cache Go modules (Windows)
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~\AppData\Local\go-build
|
||||||
|
~\go\pkg\mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.18
|
||||||
|
|
||||||
|
- name: Install MSYS2 & libgit2 (Windows)
|
||||||
|
shell: cmd
|
||||||
|
run: .\build.bat all
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
|
||||||
|
- name: Install libgit2 (Linux/macOS)
|
||||||
|
run: make libgit2
|
||||||
|
if: matrix.os != 'windows-latest'
|
||||||
|
|
||||||
|
- name: Test core pkg
|
||||||
|
run: go test -tags=static -v ./...
|
||||||
|
|
||||||
|
- name: Test httphandler pkg
|
||||||
|
run: cd httphandler && go test -tags=static -v ./...
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
RELEASE: ${{ inputs.release }}
|
||||||
|
CLIENT: test
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
run: python3 --version && python3 build.py
|
||||||
|
|
||||||
|
- name: Smoke Testing
|
||||||
|
env:
|
||||||
|
RELEASE: ${{ inputs.release }}
|
||||||
|
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||||
|
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
||||||
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,7 +1,7 @@
|
|||||||
*.vs*
|
*.vs*
|
||||||
*kubescape*
|
*kubescape*
|
||||||
*debug*
|
*debug*
|
||||||
*vender*
|
*vendor*
|
||||||
*.pyc*
|
*.pyc*
|
||||||
.idea
|
.idea
|
||||||
.history
|
.history
|
||||||
|
|||||||
88
README.md
88
README.md
@@ -11,7 +11,7 @@
|
|||||||
:sunglasses: [Want to contribute?](#being-a-part-of-the-team) :innocent:
|
:sunglasses: [Want to contribute?](#being-a-part-of-the-team) :innocent:
|
||||||
|
|
||||||
|
|
||||||
Kubescape is a K8s open-source tool providing a Kubernetes single pane of glass, including risk analysis, security compliance, RBAC visualizer, and image vulnerabilities scanning.
|
Kubescape is a K8s open-source tool providing a Kubernetes single pane of glass, including risk analysis, security compliance, RBAC visualizer, and image vulnerability scanning.
|
||||||
Kubescape scans K8s clusters, YAML files, and HELM charts, detecting misconfigurations according to multiple frameworks (such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo/?utm_source=github&utm_medium=repository), [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/)), software vulnerabilities, and RBAC (role-based-access-control) violations at early stages of the CI/CD pipeline, calculates risk score instantly and shows risk trends over time.
|
Kubescape scans K8s clusters, YAML files, and HELM charts, detecting misconfigurations according to multiple frameworks (such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo/?utm_source=github&utm_medium=repository), [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/)), software vulnerabilities, and RBAC (role-based-access-control) violations at early stages of the CI/CD pipeline, calculates risk score instantly and shows risk trends over time.
|
||||||
|
|
||||||
It has become one of the fastest-growing Kubernetes tools among developers due to its easy-to-use CLI interface, flexible output formats, and automated scanning capabilities, saving Kubernetes users and admins precious time, effort, and resources.
|
It has become one of the fastest-growing Kubernetes tools among developers due to its easy-to-use CLI interface, flexible output formats, and automated scanning capabilities, saving Kubernetes users and admins precious time, effort, and resources.
|
||||||
@@ -30,17 +30,17 @@ Kubescape integrates natively with other DevOps tools, including Jenkins, Circle
|
|||||||
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
|
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
|
||||||
```
|
```
|
||||||
|
|
||||||
|
*OR:*
|
||||||
|
|
||||||
[Install on windows](#install-on-windows)
|
[Install on windows](#install-on-windows)
|
||||||
|
|
||||||
[Install on macOS](#install-on-macos)
|
[Install on macOS](#install-on-macos)
|
||||||
|
|
||||||
[Install on NixOS or Linux/macOS via nix](#install-on-nixos-or-with-nix-community)
|
[Install on NixOS or Linux/macOS via nix](#install-on-nixos-or-with-nix-community)
|
||||||
|
|
||||||
[Install using Go](#install-using-go)
|
|
||||||
|
|
||||||
## Run:
|
## Run:
|
||||||
```sh
|
```sh
|
||||||
kubescape scan --submit --enable-host-scan --verbose
|
kubescape scan --enable-host-scan --verbose
|
||||||
```
|
```
|
||||||
|
|
||||||
<img src="docs/summary.png">
|
<img src="docs/summary.png">
|
||||||
@@ -51,14 +51,29 @@ kubescape scan --submit --enable-host-scan --verbose
|
|||||||
|
|
||||||
</br>
|
</br>
|
||||||
|
|
||||||
|
## Architecture in short
|
||||||
|
### [CLI](#kubescape-cli)
|
||||||
|
<div align="center">
|
||||||
|
<img src="docs/ks-cli-arch.png" width="300" alt="cli-diagram">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### [Operator](https://github.com/kubescape/helm-charts#readme)
|
||||||
|
<div align="center">
|
||||||
|
<img src="docs/ks-operator-arch.png" width="300" alt="operator-diagram">
|
||||||
|
</div>
|
||||||
|
|
||||||
### Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape 😀
|
### Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape 😀
|
||||||
|
|
||||||
</br>
|
</br>
|
||||||
|
|
||||||
# Being a part of the team
|
# Being a part of the team
|
||||||
|
|
||||||
We invite you to our team! We are excited about this project and want to return the love we get.
|
## Community
|
||||||
|
We invite you to our community! We are excited about this project and want to return the love we get.
|
||||||
|
|
||||||
|
We hold community meetings in [Zoom](https://us02web.zoom.us/j/84020231442) on the first Tuesday of every month at 14:00 GMT! :sunglasses:
|
||||||
|
|
||||||
|
## Contributions
|
||||||
[Want to contribute?](https://github.com/kubescape/kubescape/blob/master/CONTRIBUTING.md) Want to discuss something? Have an issue? Please make sure that you follow our [Code Of Conduct](https://github.com/kubescape/kubescape/blob/master/CODE_OF_CONDUCT.md) .
|
[Want to contribute?](https://github.com/kubescape/kubescape/blob/master/CONTRIBUTING.md) Want to discuss something? Have an issue? Please make sure that you follow our [Code Of Conduct](https://github.com/kubescape/kubescape/blob/master/CODE_OF_CONDUCT.md) .
|
||||||
|
|
||||||
* Feel free to pick a task from the [issues](https://github.com/kubescape/kubescape/issues?q=is%3Aissue+is%3Aopen+label%3A%22open+for+contribution%22), [roadmap](docs/roadmap.md) or suggest a feature of your own. [Contact us](MAINTAINERS.md) directly for more information :)
|
* Feel free to pick a task from the [issues](https://github.com/kubescape/kubescape/issues?q=is%3Aissue+is%3Aopen+label%3A%22open+for+contribution%22), [roadmap](docs/roadmap.md) or suggest a feature of your own. [Contact us](MAINTAINERS.md) directly for more information :)
|
||||||
@@ -81,6 +96,7 @@ We invite you to our team! We are excited about this project and want to return
|
|||||||
* [Overview](https://youtu.be/wdBkt_0Qhbg)
|
* [Overview](https://youtu.be/wdBkt_0Qhbg)
|
||||||
* [How To Secure Kubernetes Clusters With Kubescape And Armo](https://youtu.be/ZATGiDIDBQk)
|
* [How To Secure Kubernetes Clusters With Kubescape And Armo](https://youtu.be/ZATGiDIDBQk)
|
||||||
* [Scan Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
|
* [Scan Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
|
||||||
|
* [Scan container image registry](https://youtu.be/iQ_k8EnK-3s)
|
||||||
* [Scan Kubescape on an air-gapped environment (offline support)](https://youtu.be/IGXL9s37smM)
|
* [Scan Kubescape on an air-gapped environment (offline support)](https://youtu.be/IGXL9s37smM)
|
||||||
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
|
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
|
||||||
* [Configure and run customized frameworks](https://youtu.be/12Sanq_rEhs)
|
* [Configure and run customized frameworks](https://youtu.be/12Sanq_rEhs)
|
||||||
@@ -159,22 +175,22 @@ Or to your profile (not preferred): `nix-env --install -A nixpkgs.kubescape`
|
|||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
#### Scan a running Kubernetes cluster and submit results to the [Kubescape SaaS version](https://cloud.armosec.io?utm_source=github&utm_medium=repository)
|
#### Scan a running Kubernetes cluster
|
||||||
```
|
```
|
||||||
kubescape scan --submit --enable-host-scan --verbose
|
kubescape scan --enable-host-scan --verbose
|
||||||
```
|
```
|
||||||
|
|
||||||
> Read [here](https://hub.armosec.io/docs/host-sensor?utm_source=github&utm_medium=repository) more about the `enable-host-scan` flag
|
> Read [here](https://hub.armosec.io/docs/host-sensor?utm_source=github&utm_medium=repository) more about the `enable-host-scan` flag
|
||||||
|
|
||||||
#### Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to the [Kubescape SaaS version](https://cloud.armosec.io?utm_source=github&utm_medium=repository)
|
#### Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework
|
||||||
```
|
```
|
||||||
kubescape scan framework nsa --submit
|
kubescape scan framework nsa
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework and submit results to the [Kubescape SaaS version](https://cloud.armosec.io?utm_source=github&utm_medium=repository)
|
#### Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework
|
||||||
```
|
```
|
||||||
kubescape scan framework mitre --submit
|
kubescape scan framework mitre
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -183,6 +199,11 @@ kubescape scan framework mitre --submit
|
|||||||
kubescape scan control "Privileged container"
|
kubescape scan control "Privileged container"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Scan using an alternative kubeconfig file
|
||||||
|
```
|
||||||
|
kubescape scan --kubeconfig cluster.conf
|
||||||
|
```
|
||||||
|
|
||||||
#### Scan specific namespaces
|
#### Scan specific namespaces
|
||||||
```
|
```
|
||||||
kubescape scan --include-namespaces development,staging,production
|
kubescape scan --include-namespaces development,staging,production
|
||||||
@@ -193,14 +214,13 @@ kubescape scan --include-namespaces development,staging,production
|
|||||||
kubescape scan --exclude-namespaces kube-system,kube-public
|
kubescape scan --exclude-namespaces kube-system,kube-public
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI). Submit the results in case the directory is a git repo. [docs](https://hub.armosec.io/docs/repository-scanning?utm_source=github&utm_medium=repository)
|
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI).
|
||||||
```
|
```
|
||||||
kubescape scan *.yaml --submit
|
kubescape scan *.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Scan Kubernetes manifest files from a git repository [and submit the results](https://hub.armosec.io/docs/repository-scanning?utm_source=github&utm_medium=repository)
|
#### Scan Kubernetes manifest files from a git repository
|
||||||
```
|
kubescape scan https://github.com/kubescape/kubescape
|
||||||
kubescape scan https://github.com/kubescape/kubescape --submit
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Display all scanned resources (including the resources which passed)
|
#### Display all scanned resources (including the resources which passed)
|
||||||
@@ -233,6 +253,12 @@ kubescape scan --format pdf --output results.pdf
|
|||||||
kubescape scan --format prometheus
|
kubescape scan --format prometheus
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Output in `html` format
|
||||||
|
|
||||||
|
```
|
||||||
|
kubescape scan --format html --output results.html
|
||||||
|
```
|
||||||
|
|
||||||
#### Scan with exceptions, objects with exceptions will be presented as `exclude` and not `fail`
|
#### Scan with exceptions, objects with exceptions will be presented as `exclude` and not `fail`
|
||||||
[Full documentation](examples/exceptions/README.md)
|
[Full documentation](examples/exceptions/README.md)
|
||||||
```
|
```
|
||||||
@@ -241,10 +267,16 @@ kubescape scan --exceptions examples/exceptions/exclude-kube-namespaces.json
|
|||||||
|
|
||||||
#### Scan Helm charts
|
#### Scan Helm charts
|
||||||
```
|
```
|
||||||
kubescape scan </path/to/directory> --submit
|
kubescape scan </path/to/directory>
|
||||||
```
|
```
|
||||||
> Kubescape will load the default value file
|
> Kubescape will load the default value file
|
||||||
|
|
||||||
|
#### Scan Kustomize Directory
|
||||||
|
```
|
||||||
|
kubescape scan </path/to/directory>
|
||||||
|
```
|
||||||
|
> Kubescape will generate Kubernetes Yaml Objects using 'Kustomize' file and scans them for security.
|
||||||
|
|
||||||
### Offline/Air-gaped Environment Support
|
### Offline/Air-gaped Environment Support
|
||||||
|
|
||||||
[Video tutorial](https://youtu.be/IGXL9s37smM)
|
[Video tutorial](https://youtu.be/IGXL9s37smM)
|
||||||
@@ -355,6 +387,28 @@ View Kubescape scan results directly in [Lens IDE](https://k8slens.dev/) using k
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## Build on pre-configured killercoda's ubuntu playground
|
||||||
|
|
||||||
|
* [Pre-configured Killercoda's Ubuntu Playground](https://killercoda.com/suhas-gumma/scenario/kubescape-build-for-development)
|
||||||
|
|
||||||
|
<details><summary> Pre-programmed actions executed by the playground </summary>
|
||||||
|
|
||||||
|
|
||||||
|
* Clone the official GitHub repository of `Kubescape`.
|
||||||
|
* [Automate the build process on Linux](https://github.com/kubescape/kubescape#build-on-linuxmacos)
|
||||||
|
* The entire process involves executing multiple commands in order and it takes around 5-6 minutes to execute them all.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Instructions to use the playground</summary>
|
||||||
|
|
||||||
|
* Apply changes you wish to make to the kubescape directory using text editors like `Vim`.
|
||||||
|
* [Build on Linux](https://github.com/kubescape/kubescape#build-on-linuxmacos)
|
||||||
|
* Now, you can use Kubescape just like a normal user. Instead of using `kubescape`, use `./kubescape`. (Make sure you are inside kubescape directory because the command will execute the binary named `kubescape` in `kubescape directory`)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## VS code configuration samples
|
## VS code configuration samples
|
||||||
|
|
||||||
You can use the sample files below to setup your VS code environment for building and debugging purposes.
|
You can use the sample files below to setup your VS code environment for building and debugging purposes.
|
||||||
|
|||||||
11
build.py
11
build.py
@@ -14,14 +14,15 @@ def check_status(status, msg):
|
|||||||
|
|
||||||
def get_build_dir():
|
def get_build_dir():
|
||||||
current_platform = platform.system()
|
current_platform = platform.system()
|
||||||
build_dir = "./build/"
|
build_dir = ""
|
||||||
|
|
||||||
if current_platform == "Windows": build_dir += "windows-latest"
|
if current_platform == "Windows": build_dir = "windows-latest"
|
||||||
elif current_platform == "Linux": build_dir += "ubuntu-latest"
|
elif current_platform == "Linux": build_dir = "ubuntu-latest"
|
||||||
elif current_platform == "Darwin": build_dir += "macos-latest"
|
elif current_platform == "Darwin": build_dir = "macos-latest"
|
||||||
else: raise OSError("Platform %s is not supported!" % (current_platform))
|
else: raise OSError("Platform %s is not supported!" % (current_platform))
|
||||||
|
|
||||||
return build_dir
|
return os.path.join("build", build_dir)
|
||||||
|
|
||||||
|
|
||||||
def get_package_name():
|
def get_package_name():
|
||||||
package_name = "kubescape"
|
package_name = "kubescape"
|
||||||
|
|||||||
@@ -35,15 +35,15 @@ RUN /work/build/ubuntu-latest/kubescape download artifacts -o /work/artifacts
|
|||||||
|
|
||||||
FROM alpine:3.16.2
|
FROM alpine:3.16.2
|
||||||
|
|
||||||
RUN addgroup -S armo && adduser -S armo -G armo
|
RUN addgroup -S ks && adduser -S ks -G ks
|
||||||
|
|
||||||
COPY --from=builder /work/artifacts/ /home/armo/.kubescape
|
COPY --from=builder /work/artifacts/ /home/ks/.kubescape
|
||||||
|
|
||||||
RUN chown -R armo:armo /home/armo/.kubescape
|
RUN chown -R ks:ks /home/ks/.kubescape
|
||||||
|
|
||||||
USER armo
|
USER ks
|
||||||
|
|
||||||
WORKDIR /home/armo
|
WORKDIR /home/ks
|
||||||
|
|
||||||
COPY --from=builder /work/httphandler/build/ubuntu-latest/kubescape /usr/bin/ksserver
|
COPY --from=builder /work/httphandler/build/ubuntu-latest/kubescape /usr/bin/ksserver
|
||||||
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
|
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ var (
|
|||||||
|
|
||||||
# Set access key
|
# Set access key
|
||||||
kubescape config set secretKey <access key>
|
kubescape config set secretKey <access key>
|
||||||
|
|
||||||
|
# Set cloudAPIURL
|
||||||
|
kubescape config set cloudAPIURL <cloud API URL>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,13 @@ func getSetCmd(ks meta.IKubescape) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var supportConfigSet = map[string]func(*metav1.SetConfig, string){
|
var supportConfigSet = map[string]func(*metav1.SetConfig, string){
|
||||||
"accountID": func(s *metav1.SetConfig, account string) { s.Account = account },
|
"accountID": func(s *metav1.SetConfig, account string) { s.Account = account },
|
||||||
"clientID": func(s *metav1.SetConfig, clientID string) { s.ClientID = clientID },
|
"clientID": func(s *metav1.SetConfig, clientID string) { s.ClientID = clientID },
|
||||||
"secretKey": func(s *metav1.SetConfig, secretKey string) { s.SecretKey = secretKey },
|
"secretKey": func(s *metav1.SetConfig, secretKey string) { s.SecretKey = secretKey },
|
||||||
|
"cloudAPIURL": func(s *metav1.SetConfig, cloudAPIURL string) { s.CloudAPIURL = cloudAPIURL },
|
||||||
|
"cloudAuthURL": func(s *metav1.SetConfig, cloudAuthURL string) { s.CloudAuthURL = cloudAuthURL },
|
||||||
|
"cloudReportURL": func(s *metav1.SetConfig, cloudReportURL string) { s.CloudReportURL = cloudReportURL },
|
||||||
|
"cloudUIURL": func(s *metav1.SetConfig, cloudUIURL string) { s.CloudUIURL = cloudUIURL },
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringKeysToSlice(m map[string]func(*metav1.SetConfig, string)) []string {
|
func stringKeysToSlice(m map[string]func(*metav1.SetConfig, string)) []string {
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ func getExceptionsCmd(ks meta.IKubescape, deleteInfo *v1.Delete) *cobra.Command
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
|
if err := flagValidationDelete(deleteInfo); err != nil {
|
||||||
|
logger.L().Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
exceptionsNames := strings.Split(args[0], ";")
|
exceptionsNames := strings.Split(args[0], ";")
|
||||||
if len(exceptionsNames) == 0 {
|
if len(exceptionsNames) == 0 {
|
||||||
logger.L().Fatal("missing exceptions names")
|
logger.L().Fatal("missing exceptions names")
|
||||||
@@ -32,3 +37,10 @@ func getExceptionsCmd(ks meta.IKubescape, deleteInfo *v1.Delete) *cobra.Command
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the flag entered are valid
|
||||||
|
func flagValidationDelete(deleteInfo *v1.Delete) error {
|
||||||
|
|
||||||
|
// Validate the user's credentials
|
||||||
|
return deleteInfo.Credentials.Validate()
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ var (
|
|||||||
downloadExample = `
|
downloadExample = `
|
||||||
# Download all artifacts and save them in the default path (~/.kubescape)
|
# Download all artifacts and save them in the default path (~/.kubescape)
|
||||||
kubescape download artifacts
|
kubescape download artifacts
|
||||||
download
|
|
||||||
# Download all artifacts and save them in /tmp path
|
# Download all artifacts and save them in /tmp path
|
||||||
kubescape download artifacts --output /tmp
|
kubescape download artifacts --output /tmp
|
||||||
|
|
||||||
@@ -59,6 +59,10 @@ func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
|||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
if err := flagValidationDownload(&downloadInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if filepath.Ext(downloadInfo.Path) == ".json" {
|
if filepath.Ext(downloadInfo.Path) == ".json" {
|
||||||
downloadInfo.Path, downloadInfo.FileName = filepath.Split(downloadInfo.Path)
|
downloadInfo.Path, downloadInfo.FileName = filepath.Split(downloadInfo.Path)
|
||||||
}
|
}
|
||||||
@@ -80,3 +84,10 @@ func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
|||||||
|
|
||||||
return downloadCmd
|
return downloadCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the flag entered are valid
|
||||||
|
func flagValidationDownload(downloadInfo *v1.DownloadInfo) error {
|
||||||
|
|
||||||
|
// Validate the user's credentials
|
||||||
|
return downloadInfo.Credentials.Validate()
|
||||||
|
}
|
||||||
|
|||||||
45
cmd/fix/fix.go
Normal file
45
cmd/fix/fix.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -51,6 +51,11 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
if err := flagValidationList(&listPolicies); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
listPolicies.Target = args[0]
|
listPolicies.Target = args[0]
|
||||||
|
|
||||||
if err := ks.List(&listPolicies); err != nil {
|
if err := ks.List(&listPolicies); err != nil {
|
||||||
@@ -67,3 +72,10 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
|||||||
|
|
||||||
return listCmd
|
return listCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the flag entered are valid
|
||||||
|
func flagValidationList(listPolicies *v1.ListPolicies) error {
|
||||||
|
|
||||||
|
// Validate the user's credentials
|
||||||
|
return listPolicies.Credentials.Validate()
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import (
|
|||||||
"github.com/kubescape/kubescape/v2/cmd/config"
|
"github.com/kubescape/kubescape/v2/cmd/config"
|
||||||
"github.com/kubescape/kubescape/v2/cmd/delete"
|
"github.com/kubescape/kubescape/v2/cmd/delete"
|
||||||
"github.com/kubescape/kubescape/v2/cmd/download"
|
"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/list"
|
||||||
"github.com/kubescape/kubescape/v2/cmd/scan"
|
"github.com/kubescape/kubescape/v2/cmd/scan"
|
||||||
"github.com/kubescape/kubescape/v2/cmd/submit"
|
"github.com/kubescape/kubescape/v2/cmd/submit"
|
||||||
|
"github.com/kubescape/kubescape/v2/cmd/update"
|
||||||
"github.com/kubescape/kubescape/v2/cmd/version"
|
"github.com/kubescape/kubescape/v2/cmd/version"
|
||||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||||
@@ -76,6 +78,8 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
|||||||
rootCmd.AddCommand(completion.GetCompletionCmd())
|
rootCmd.AddCommand(completion.GetCompletionCmd())
|
||||||
rootCmd.AddCommand(version.GetVersionCmd())
|
rootCmd.AddCommand(version.GetVersionCmd())
|
||||||
rootCmd.AddCommand(config.GetConfigCmd(ks))
|
rootCmd.AddCommand(config.GetConfigCmd(ks))
|
||||||
|
rootCmd.AddCommand(update.GetUpdateCmd())
|
||||||
|
rootCmd.AddCommand(fix.GetFixCmd(ks))
|
||||||
|
|
||||||
return rootCmd
|
return rootCmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
|||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
if err := validateFrameworkScanInfo(scanInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// flagValidationControl(scanInfo)
|
// flagValidationControl(scanInfo)
|
||||||
scanInfo.PolicyIdentifier = []cautils.PolicyIdentifier{}
|
scanInfo.PolicyIdentifier = []cautils.PolicyIdentifier{}
|
||||||
|
|
||||||
@@ -88,6 +92,10 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
|||||||
|
|
||||||
scanInfo.FrameworkScan = false
|
scanInfo.FrameworkScan = false
|
||||||
|
|
||||||
|
if err := validateControlScanInfo(scanInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
results, err := ks.Scan(scanInfo)
|
results, err := ks.Scan(scanInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.L().Fatal(err.Error())
|
logger.L().Fatal(err.Error())
|
||||||
@@ -101,7 +109,19 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
|||||||
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
|
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)))
|
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)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateControlScanInfo validates the ScanInfo struct for the `control` command
|
||||||
|
func validateControlScanInfo(scanInfo *cautils.ScanInfo) error {
|
||||||
|
severity := scanInfo.FailThresholdSeverity
|
||||||
|
|
||||||
|
if err := validateSeverity(severity); severity != "" && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package scan
|
package scan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||||
|
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
|
||||||
|
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||||
|
|
||||||
logger "github.com/kubescape/go-logger"
|
logger "github.com/kubescape/go-logger"
|
||||||
"github.com/kubescape/go-logger/helpers"
|
"github.com/kubescape/go-logger/helpers"
|
||||||
@@ -36,6 +39,8 @@ var (
|
|||||||
|
|
||||||
Run 'kubescape list frameworks' for the list of supported frameworks
|
Run 'kubescape list frameworks' for the list of supported frameworks
|
||||||
`
|
`
|
||||||
|
|
||||||
|
ErrUnknownSeverity = errors.New("unknown severity")
|
||||||
)
|
)
|
||||||
|
|
||||||
func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
|
func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
|
||||||
@@ -62,7 +67,7 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
|||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
if err := flagValidationFramework(scanInfo); err != nil {
|
if err := validateFrameworkScanInfo(scanInfo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
scanInfo.FrameworkScan = true
|
scanInfo.FrameworkScan = true
|
||||||
@@ -113,17 +118,95 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
|||||||
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
|
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)))
|
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)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func flagValidationFramework(scanInfo *cautils.ScanInfo) error {
|
// countersExceedSeverityThreshold returns true if severity of failed controls exceed the set severity threshold, else returns false
|
||||||
|
func countersExceedSeverityThreshold(severityCounters reportsummary.ISeverityCounters, scanInfo *cautils.ScanInfo) (bool, error) {
|
||||||
|
targetSeverity := scanInfo.FailThresholdSeverity
|
||||||
|
if err := validateSeverity(targetSeverity); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
getFailedResourcesFuncsBySeverity := []struct {
|
||||||
|
SeverityName string
|
||||||
|
GetFailedResources func() int
|
||||||
|
}{
|
||||||
|
{reporthandlingapis.SeverityLowString, severityCounters.NumberOfResourcesWithLowSeverity},
|
||||||
|
{reporthandlingapis.SeverityMediumString, severityCounters.NumberOfResourcesWithMediumSeverity},
|
||||||
|
{reporthandlingapis.SeverityHighString, severityCounters.NumberOfResourcesWithHighSeverity},
|
||||||
|
{reporthandlingapis.SeverityCriticalString, severityCounters.NumberOfResourcesWithCriticalSeverity},
|
||||||
|
}
|
||||||
|
|
||||||
|
targetSeverityIdx := 0
|
||||||
|
for idx, description := range getFailedResourcesFuncsBySeverity {
|
||||||
|
if strings.EqualFold(description.SeverityName, targetSeverity) {
|
||||||
|
targetSeverityIdx = idx
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, description := range getFailedResourcesFuncsBySeverity[targetSeverityIdx:] {
|
||||||
|
failedResourcesCount := description.GetFailedResources()
|
||||||
|
if failedResourcesCount > 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminateOnExceedingSeverity terminates the application on exceeding severity
|
||||||
|
func terminateOnExceedingSeverity(scanInfo *cautils.ScanInfo, l logger.ILogger) {
|
||||||
|
l.Fatal("result exceeds severity threshold", helpers.String("set severity threshold", scanInfo.FailThresholdSeverity))
|
||||||
|
}
|
||||||
|
|
||||||
|
// enforceSeverityThresholds ensures that the scan results are below the defined severity threshold
|
||||||
|
//
|
||||||
|
// The function forces the application to terminate with an exit code 1 if at least one control failed control that exceeds the set severity threshold
|
||||||
|
func enforceSeverityThresholds(severityCounters reportsummary.ISeverityCounters, scanInfo *cautils.ScanInfo, onExceed func(*cautils.ScanInfo, logger.ILogger)) {
|
||||||
|
// If a severity threshold is not set, we don’t need to enforce it
|
||||||
|
if scanInfo.FailThresholdSeverity == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, err := countersExceedSeverityThreshold(severityCounters, scanInfo); val && err == nil {
|
||||||
|
onExceed(scanInfo, logger.L())
|
||||||
|
} else if err != nil {
|
||||||
|
logger.L().Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSeverity returns an error if a given severity is not known, nil otherwise
|
||||||
|
func validateSeverity(severity string) error {
|
||||||
|
for _, val := range reporthandlingapis.GetSupportedSeverities() {
|
||||||
|
if strings.EqualFold(severity, val) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ErrUnknownSeverity
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateFrameworkScanInfo validates the scan info struct for the `scan framework` command
|
||||||
|
func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
|
||||||
if scanInfo.Submit && scanInfo.Local {
|
if scanInfo.Submit && scanInfo.Local {
|
||||||
return fmt.Errorf("you can use `keep-local` or `submit`, but not both")
|
return fmt.Errorf("you can use `keep-local` or `submit`, but not both")
|
||||||
}
|
}
|
||||||
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
|
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
|
||||||
return fmt.Errorf("bad argument: out of range threshold")
|
return fmt.Errorf("bad argument: out of range threshold")
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
severity := scanInfo.FailThresholdSeverity
|
||||||
|
if err := validateSeverity(severity); severity != "" && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the user's credentials
|
||||||
|
return scanInfo.Credentials.Validate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package scan
|
package scan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||||
@@ -10,10 +11,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var scanCmdExamples = `
|
var scanCmdExamples = `
|
||||||
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defind frameworks
|
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
|
||||||
|
|
||||||
# Scan current cluster with all frameworks
|
# Scan current cluster with all frameworks
|
||||||
kubescape scan --submit --enable-host-scan --verbose
|
kubescape scan --enable-host-scan --verbose
|
||||||
|
|
||||||
# Scan kubernetes YAML manifest files
|
# Scan kubernetes YAML manifest files
|
||||||
kubescape scan *.yaml
|
kubescape scan *.yaml
|
||||||
@@ -70,30 +71,33 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
|||||||
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
|
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
|
||||||
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
|
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
|
||||||
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
|
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
|
||||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public")
|
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Notice, when running with `exclude-namespace` kubescape does not scan cluster-scoped objects.")
|
||||||
|
|
||||||
scanCmd.PersistentFlags().Float32VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1")
|
scanCmd.PersistentFlags().Float32VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1")
|
||||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html"`)
|
|
||||||
|
scanCmd.PersistentFlags().StringVar(&scanInfo.FailThresholdSeverity, "severity-threshold", "", "Severity threshold is the severity of failed controls at which the command fails and returns exit code 1")
|
||||||
|
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif"`)
|
||||||
scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
|
scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
|
||||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to ARMO backend. Use this flag if you ran with the '--submit' flag in the past and you do not want to submit your current scan results")
|
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to configured backend.")
|
||||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
|
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
|
||||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display all of the input resources and not only failed resources")
|
scanCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display all of the input resources and not only failed resources")
|
||||||
scanCmd.PersistentFlags().StringVar(&scanInfo.View, "view", string(cautils.ResourceViewType), fmt.Sprintf("View results based on the %s/%s. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, cautils.ResourceViewType))
|
scanCmd.PersistentFlags().StringVar(&scanInfo.View, "view", string(cautils.ResourceViewType), fmt.Sprintf("View results based on the %s/%s. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, cautils.ResourceViewType))
|
||||||
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
|
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
|
||||||
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
|
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
|
||||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Send the scan results to ARMO management portal 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().StringVar(&scanInfo.HostSensorYamlPath, "host-scan-yaml", "", "Override default host scanner DaemonSet. Use this flag cautiously")
|
scanCmd.PersistentFlags().StringVar(&scanInfo.HostSensorYamlPath, "host-scan-yaml", "", "Override default host scanner DaemonSet. Use this flag cautiously")
|
||||||
scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v1", "Output object can be differnet between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
|
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")
|
||||||
|
|
||||||
// Deprecated flags - remove 1.May.2022
|
|
||||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
|
|
||||||
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
|
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
|
||||||
|
|
||||||
// hidden flags
|
// 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("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("silent") // this flag should be deprecated since we added the --logger support
|
|
||||||
// scanCmd.PersistentFlags().MarkHidden("format-version") // meant for testing different output approaches and not for common use
|
|
||||||
|
|
||||||
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valuable data from cluster nodes for certain controls. Yaml file: https://github.com/kubescape/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml")
|
// Retrieve --kubeconfig flag from https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/cmd.go
|
||||||
|
scanCmd.PersistentFlags().AddGoFlag(flag.Lookup("kubeconfig"))
|
||||||
|
|
||||||
|
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "Deploy Kubescape host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valuable data from cluster nodes for certain controls. Yaml file: https://github.com/kubescape/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml")
|
||||||
hostF.NoOptDefVal = "true"
|
hostF.NoOptDefVal = "true"
|
||||||
hostF.DefValue = "false, for no TTY in stdin"
|
hostF.DefValue = "false, for no TTY in stdin"
|
||||||
|
|
||||||
|
|||||||
254
cmd/scan/scan_test.go
Normal file
254
cmd/scan/scan_test.go
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
package scan
|
||||||
|
|
||||||
|
import (
|
||||||
|
logger "github.com/kubescape/go-logger"
|
||||||
|
"github.com/kubescape/go-logger/helpers"
|
||||||
|
|
||||||
|
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||||
|
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||||
|
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||||
|
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExceedsSeverity(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
Description string
|
||||||
|
ScanInfo *cautils.ScanInfo
|
||||||
|
SeverityCounters reportsummary.ISeverityCounters
|
||||||
|
Want bool
|
||||||
|
Error error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Description: "Critical failed resource should exceed Critical threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "critical"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 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},
|
||||||
|
Want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "High failed resource should not exceed Critical threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "critical"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
|
||||||
|
Want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Critical failed resource exceeds High threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
|
||||||
|
Want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "High failed resource exceeds High threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
|
||||||
|
Want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Medium failed resource does not exceed High threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithMediumSeverityCounter: 1},
|
||||||
|
Want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Critical failed resource exceeds Medium threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
|
||||||
|
Want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "High failed resource exceeds Medium threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
|
||||||
|
Want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Medium failed resource exceeds Medium threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithMediumSeverityCounter: 1},
|
||||||
|
Want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Low failed resource does not exceed Medium threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithLowSeverityCounter: 1},
|
||||||
|
Want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Critical failed resource exceeds Low threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
|
||||||
|
Want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "High failed resource exceeds Low threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithHighSeverityCounter: 1},
|
||||||
|
Want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Medium failed resource exceeds Low threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithMediumSeverityCounter: 1},
|
||||||
|
Want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Low failed resource exceeds Low threshold",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithLowSeverityCounter: 1},
|
||||||
|
Want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "Unknown severity returns an error",
|
||||||
|
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "unknown"},
|
||||||
|
SeverityCounters: &reportsummary.SeverityCounters{ResourcesWithLowSeverityCounter: 1},
|
||||||
|
Want: false,
|
||||||
|
Error: ErrUnknownSeverity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.Description, func(t *testing.T) {
|
||||||
|
got, err := countersExceedSeverityThreshold(testCase.SeverityCounters, testCase.ScanInfo)
|
||||||
|
want := testCase.Want
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got: %v, want: %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != testCase.Error {
|
||||||
|
t.Errorf(`got error "%v", want "%v"`, err, testCase.Error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_enforceSeverityThresholds(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
Description string
|
||||||
|
SeverityCounters *reportsummary.SeverityCounters
|
||||||
|
ScanInfo *cautils.ScanInfo
|
||||||
|
Want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Exceeding Critical severity counter should call the terminating function",
|
||||||
|
&reportsummary.SeverityCounters{ResourcesWithCriticalSeverityCounter: 1},
|
||||||
|
&cautils.ScanInfo{FailThresholdSeverity: apis.SeverityCriticalString},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Non-exceeding severity counter should call not the terminating function",
|
||||||
|
&reportsummary.SeverityCounters{},
|
||||||
|
&cautils.ScanInfo{FailThresholdSeverity: apis.SeverityCriticalString},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(
|
||||||
|
tc.Description,
|
||||||
|
func(t *testing.T) {
|
||||||
|
severityCounters := tc.SeverityCounters
|
||||||
|
scanInfo := tc.ScanInfo
|
||||||
|
want := tc.Want
|
||||||
|
|
||||||
|
got := false
|
||||||
|
onExceed := func(*cautils.ScanInfo, logger.ILogger) {
|
||||||
|
got = true
|
||||||
|
}
|
||||||
|
|
||||||
|
enforceSeverityThresholds(severityCounters, scanInfo, onExceed)
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got: %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type spyLogMessage struct {
|
||||||
|
Message string
|
||||||
|
Details map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type spyLogger struct {
|
||||||
|
setItems []spyLogMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *spyLogger) Error(msg string, details ...helpers.IDetails) {}
|
||||||
|
func (l *spyLogger) Success(msg string, details ...helpers.IDetails) {}
|
||||||
|
func (l *spyLogger) Warning(msg string, details ...helpers.IDetails) {}
|
||||||
|
func (l *spyLogger) Info(msg string, details ...helpers.IDetails) {}
|
||||||
|
func (l *spyLogger) Debug(msg string, details ...helpers.IDetails) {}
|
||||||
|
func (l *spyLogger) SetLevel(level string) error { return nil }
|
||||||
|
func (l *spyLogger) GetLevel() string { return "" }
|
||||||
|
func (l *spyLogger) SetWriter(w *os.File) {}
|
||||||
|
func (l *spyLogger) GetWriter() *os.File { return &os.File{} }
|
||||||
|
func (l *spyLogger) LoggerName() string { return "" }
|
||||||
|
|
||||||
|
func (l *spyLogger) Fatal(msg string, details ...helpers.IDetails) {
|
||||||
|
firstDetail := details[0]
|
||||||
|
detailsMap := map[string]string{firstDetail.Key(): firstDetail.Value().(string)}
|
||||||
|
|
||||||
|
newMsg := spyLogMessage{msg, detailsMap}
|
||||||
|
l.setItems = append(l.setItems, newMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *spyLogger) GetSpiedItems() []spyLogMessage {
|
||||||
|
return l.setItems
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_terminateOnExceedingSeverity(t *testing.T) {
|
||||||
|
expectedMessage := "result exceeds severity threshold"
|
||||||
|
expectedKey := "set severity threshold"
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
Description string
|
||||||
|
ExpectedMessage string
|
||||||
|
ExpectedKey string
|
||||||
|
ExpectedValue string
|
||||||
|
Logger *spyLogger
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Should log the Critical threshold that was set in scan info",
|
||||||
|
expectedMessage,
|
||||||
|
expectedKey,
|
||||||
|
apis.SeverityCriticalString,
|
||||||
|
&spyLogger{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Should log the High threshold that was set in scan info",
|
||||||
|
expectedMessage,
|
||||||
|
expectedKey,
|
||||||
|
apis.SeverityHighString,
|
||||||
|
&spyLogger{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(
|
||||||
|
tc.Description,
|
||||||
|
func(t *testing.T) {
|
||||||
|
want := []spyLogMessage{
|
||||||
|
{tc.ExpectedMessage, map[string]string{tc.ExpectedKey: tc.ExpectedValue}},
|
||||||
|
}
|
||||||
|
scanInfo := &cautils.ScanInfo{FailThresholdSeverity: tc.ExpectedValue}
|
||||||
|
|
||||||
|
terminateOnExceedingSeverity(scanInfo, tc.Logger)
|
||||||
|
|
||||||
|
got := tc.Logger.GetSpiedItems()
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("got: %v, want: %v", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
115
cmd/scan/validators_test.go
Normal file
115
cmd/scan/validators_test.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package scan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test_validateControlScanInfo tests how scan info is validated for the `scan control` command
|
||||||
|
func Test_validateControlScanInfo(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
Description string
|
||||||
|
ScanInfo *cautils.ScanInfo
|
||||||
|
Want error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Empty severity should be valid for scan info",
|
||||||
|
&cautils.ScanInfo{FailThresholdSeverity: ""},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"High severity should be valid for scan info",
|
||||||
|
&cautils.ScanInfo{FailThresholdSeverity: "High"},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Unknown severity should be invalid for scan info",
|
||||||
|
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
|
||||||
|
ErrUnknownSeverity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(
|
||||||
|
tc.Description,
|
||||||
|
func(t *testing.T) {
|
||||||
|
var want error = tc.Want
|
||||||
|
|
||||||
|
got := validateControlScanInfo(tc.ScanInfo)
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got: %v, want: %v", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test_validateFrameworkScanInfo tests how scan info is validated for the `scan framework` command
|
||||||
|
func Test_validateFrameworkScanInfo(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
Description string
|
||||||
|
ScanInfo *cautils.ScanInfo
|
||||||
|
Want error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Empty severity should be valid for scan info",
|
||||||
|
&cautils.ScanInfo{FailThresholdSeverity: ""},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"High severity should be valid for scan info",
|
||||||
|
&cautils.ScanInfo{FailThresholdSeverity: "High"},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Unknown severity should be invalid for scan info",
|
||||||
|
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
|
||||||
|
ErrUnknownSeverity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(
|
||||||
|
tc.Description,
|
||||||
|
func(t *testing.T) {
|
||||||
|
var want error = tc.Want
|
||||||
|
|
||||||
|
got := validateFrameworkScanInfo(tc.ScanInfo)
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got: %v, want: %v", got, want)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateSeverity(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
Description string
|
||||||
|
Input string
|
||||||
|
Want error
|
||||||
|
}{
|
||||||
|
{"low should be a valid severity", "low", nil},
|
||||||
|
{"Low should be a valid severity", "Low", nil},
|
||||||
|
{"medium should be a valid severity", "medium", nil},
|
||||||
|
{"Medium should be a valid severity", "Medium", nil},
|
||||||
|
{"high should be a valid severity", "high", nil},
|
||||||
|
{"Critical should be a valid severity", "Critical", nil},
|
||||||
|
{"critical should be a valid severity", "critical", nil},
|
||||||
|
{"Unknown should be an invalid severity", "Unknown", ErrUnknownSeverity},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.Description, func(t *testing.T) {
|
||||||
|
input := testCase.Input
|
||||||
|
want := testCase.Want
|
||||||
|
got := validateSeverity(input)
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got: %v, want: %v", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,11 @@ func getExceptionsCmd(ks meta.IKubescape, submitInfo *metav1.Submit) *cobra.Comm
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
|
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||||
|
logger.L().Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
if err := ks.SubmitExceptions(&submitInfo.Credentials, args[0]); err != nil {
|
if err := ks.SubmitExceptions(&submitInfo.Credentials, args[0]); err != nil {
|
||||||
logger.L().Fatal(err.Error())
|
logger.L().Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,10 +37,14 @@ func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
|||||||
Long: ``,
|
Long: ``,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
k8s := k8sinterface.NewKubernetesApi()
|
k8s := k8sinterface.NewKubernetesApi()
|
||||||
|
|
||||||
// get config
|
// get config
|
||||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", k8s)
|
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
|
||||||
if err := clusterConfig.SetTenant(); err != nil {
|
if err := clusterConfig.SetTenant(); err != nil {
|
||||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||||
}
|
}
|
||||||
@@ -77,9 +81,16 @@ func getKubernetesApi() *k8sinterface.KubernetesApi {
|
|||||||
}
|
}
|
||||||
return k8sinterface.NewKubernetesApi()
|
return k8sinterface.NewKubernetesApi()
|
||||||
}
|
}
|
||||||
func getTenantConfig(credentials *cautils.Credentials, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
func getTenantConfig(credentials *cautils.Credentials, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
||||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||||
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName)
|
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||||
}
|
}
|
||||||
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName)
|
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the flag entered are valid
|
||||||
|
func flagValidationSubmit(submitInfo *v1.Submit) error {
|
||||||
|
|
||||||
|
// Validate the user's credentials
|
||||||
|
return submitInfo.Credentials.Validate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
|||||||
Short: "Submit a pre scanned results file. The file must be in json format",
|
Short: "Submit a pre scanned results file. The file must be in json format",
|
||||||
Long: ``,
|
Long: ``,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("missing results file")
|
return fmt.Errorf("missing results file")
|
||||||
}
|
}
|
||||||
@@ -61,7 +66,7 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
|||||||
k8s := getKubernetesApi()
|
k8s := getKubernetesApi()
|
||||||
|
|
||||||
// get config
|
// get config
|
||||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", k8s)
|
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
|
||||||
if err := clusterConfig.SetTenant(); err != nil {
|
if err := clusterConfig.SetTenant(); err != nil {
|
||||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||||
}
|
}
|
||||||
|
|||||||
59
cmd/update/update.go
Normal file
59
cmd/update/update.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package update
|
||||||
|
|
||||||
|
//This update command updates to the latest kubescape release.
|
||||||
|
//Example:-
|
||||||
|
// kubescape update
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
logger "github.com/kubescape/go-logger"
|
||||||
|
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetUpdateCmd() *cobra.Command {
|
||||||
|
updateCmd := &cobra.Command{
|
||||||
|
Use: "update",
|
||||||
|
Short: "Update your version",
|
||||||
|
Long: ``,
|
||||||
|
RunE: func(_ *cobra.Command, args []string) error {
|
||||||
|
//Checking the user's version of kubescape to the latest release
|
||||||
|
if cautils.BuildNumber == cautils.LatestReleaseVersion {
|
||||||
|
//your version == latest version
|
||||||
|
logger.L().Info(("You are in the latest version"))
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const OSTYPE string = runtime.GOOS
|
||||||
|
var ShellToUse string
|
||||||
|
switch OSTYPE {
|
||||||
|
|
||||||
|
case "windows":
|
||||||
|
cautils.StartSpinner()
|
||||||
|
//run the installation command for windows
|
||||||
|
ShellToUse = "powershell"
|
||||||
|
_, err := exec.Command(ShellToUse, "-c", "iwr -useb https://raw.githubusercontent.com/kubescape/kubescape/master/install.ps1 | iex").Output()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.L().Fatal(err.Error())
|
||||||
|
}
|
||||||
|
cautils.StopSpinner()
|
||||||
|
|
||||||
|
default:
|
||||||
|
ShellToUse = "bash"
|
||||||
|
cautils.StartSpinner()
|
||||||
|
//run the installation command for linux and macOS
|
||||||
|
_, err := exec.Command(ShellToUse, "-c", "curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash").Output()
|
||||||
|
if err != nil {
|
||||||
|
logger.L().Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
cautils.StopSpinner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return updateCmd
|
||||||
|
}
|
||||||
@@ -5,11 +5,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
logger "github.com/kubescape/go-logger"
|
logger "github.com/kubescape/go-logger"
|
||||||
|
"github.com/kubescape/go-logger/helpers"
|
||||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -31,6 +33,10 @@ type ConfigObj struct {
|
|||||||
Token string `json:"invitationParam,omitempty"`
|
Token string `json:"invitationParam,omitempty"`
|
||||||
CustomerAdminEMail string `json:"adminMail,omitempty"`
|
CustomerAdminEMail string `json:"adminMail,omitempty"`
|
||||||
ClusterName string `json:"clusterName,omitempty"`
|
ClusterName string `json:"clusterName,omitempty"`
|
||||||
|
CloudReportURL string `json:"cloudReportURL,omitempty"`
|
||||||
|
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
|
||||||
|
CloudUIURL string `json:"cloudUIURL,omitempty"`
|
||||||
|
CloudAuthURL string `json:"cloudAuthURL,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config - convert ConfigObj to config file
|
// Config - convert ConfigObj to config file
|
||||||
@@ -74,6 +80,10 @@ type ITenantConfig interface {
|
|||||||
GetClientID() string
|
GetClientID() string
|
||||||
GetSecretKey() string
|
GetSecretKey() string
|
||||||
GetConfigObj() *ConfigObj
|
GetConfigObj() *ConfigObj
|
||||||
|
GetCloudReportURL() string
|
||||||
|
GetCloudAPIURL() string
|
||||||
|
GetCloudUIURL() string
|
||||||
|
GetCloudAuthURL() string
|
||||||
// GetBackendAPI() getter.IBackend
|
// GetBackendAPI() getter.IBackend
|
||||||
// GenerateURL()
|
// GenerateURL()
|
||||||
|
|
||||||
@@ -90,7 +100,7 @@ type LocalConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalConfig(
|
func NewLocalConfig(
|
||||||
backendAPI getter.IBackend, credentials *Credentials, clusterName string) *LocalConfig {
|
backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *LocalConfig {
|
||||||
|
|
||||||
lc := &LocalConfig{
|
lc := &LocalConfig{
|
||||||
backendAPI: backendAPI,
|
backendAPI: backendAPI,
|
||||||
@@ -102,26 +112,55 @@ func NewLocalConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateCredentials(lc.configObj, credentials)
|
updateCredentials(lc.configObj, credentials)
|
||||||
|
updateCloudURLs(lc.configObj)
|
||||||
|
|
||||||
if clusterName != "" {
|
// If a custom cluster name is provided then set that name, else use the cluster's original name
|
||||||
|
if customClusterName != "" {
|
||||||
|
lc.configObj.ClusterName = AdoptClusterName(customClusterName)
|
||||||
|
} else if clusterName != "" {
|
||||||
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
||||||
}
|
}
|
||||||
|
|
||||||
lc.backendAPI.SetAccountID(lc.configObj.AccountID)
|
lc.backendAPI.SetAccountID(lc.configObj.AccountID)
|
||||||
lc.backendAPI.SetClientID(lc.configObj.ClientID)
|
lc.backendAPI.SetClientID(lc.configObj.ClientID)
|
||||||
lc.backendAPI.SetSecretKey(lc.configObj.SecretKey)
|
lc.backendAPI.SetSecretKey(lc.configObj.SecretKey)
|
||||||
|
if lc.configObj.CloudAPIURL != "" {
|
||||||
|
lc.backendAPI.SetCloudAPIURL(lc.configObj.CloudAPIURL)
|
||||||
|
} else {
|
||||||
|
lc.configObj.CloudAPIURL = lc.backendAPI.GetCloudAPIURL()
|
||||||
|
}
|
||||||
|
if lc.configObj.CloudAuthURL != "" {
|
||||||
|
lc.backendAPI.SetCloudAuthURL(lc.configObj.CloudAuthURL)
|
||||||
|
} else {
|
||||||
|
lc.configObj.CloudAuthURL = lc.backendAPI.GetCloudAuthURL()
|
||||||
|
}
|
||||||
|
if lc.configObj.CloudReportURL != "" {
|
||||||
|
lc.backendAPI.SetCloudReportURL(lc.configObj.CloudReportURL)
|
||||||
|
} else {
|
||||||
|
lc.configObj.CloudReportURL = lc.backendAPI.GetCloudReportURL()
|
||||||
|
}
|
||||||
|
if lc.configObj.CloudUIURL != "" {
|
||||||
|
lc.backendAPI.SetCloudUIURL(lc.configObj.CloudUIURL)
|
||||||
|
} else {
|
||||||
|
lc.configObj.CloudUIURL = lc.backendAPI.GetCloudUIURL()
|
||||||
|
}
|
||||||
|
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", lc.backendAPI.GetCloudAPIURL()), helpers.String("auth", lc.backendAPI.GetCloudAuthURL()), helpers.String("report", lc.backendAPI.GetCloudReportURL()), helpers.String("UI", lc.backendAPI.GetCloudUIURL()))
|
||||||
|
|
||||||
return lc
|
return lc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
|
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
|
||||||
func (lc *LocalConfig) GetTenantEmail() string { return lc.configObj.CustomerAdminEMail }
|
func (lc *LocalConfig) GetTenantEmail() string { return lc.configObj.CustomerAdminEMail }
|
||||||
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
|
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
|
||||||
func (lc *LocalConfig) GetClientID() string { return lc.configObj.ClientID }
|
func (lc *LocalConfig) GetClientID() string { return lc.configObj.ClientID }
|
||||||
func (lc *LocalConfig) GetSecretKey() string { return lc.configObj.SecretKey }
|
func (lc *LocalConfig) GetSecretKey() string { return lc.configObj.SecretKey }
|
||||||
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
|
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
|
||||||
func (lc *LocalConfig) GetToken() string { return lc.configObj.Token }
|
func (lc *LocalConfig) GetToken() string { return lc.configObj.Token }
|
||||||
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
|
func (lc *LocalConfig) GetCloudReportURL() string { return lc.configObj.CloudReportURL }
|
||||||
|
func (lc *LocalConfig) GetCloudAPIURL() string { return lc.configObj.CloudAPIURL }
|
||||||
|
func (lc *LocalConfig) GetCloudUIURL() string { return lc.configObj.CloudUIURL }
|
||||||
|
func (lc *LocalConfig) GetCloudAuthURL() string { return lc.configObj.CloudAuthURL }
|
||||||
|
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
|
||||||
func (lc *LocalConfig) SetTenant() error {
|
func (lc *LocalConfig) SetTenant() error {
|
||||||
|
|
||||||
// Kubescape Cloud tenant GUID
|
// Kubescape Cloud tenant GUID
|
||||||
@@ -178,7 +217,7 @@ KS_ACCOUNT_ID
|
|||||||
KS_CLIENT_ID
|
KS_CLIENT_ID
|
||||||
KS_SECRET_KEY
|
KS_SECRET_KEY
|
||||||
|
|
||||||
TODO - supprot:
|
TODO - support:
|
||||||
KS_CACHE // path to cached files
|
KS_CACHE // path to cached files
|
||||||
*/
|
*/
|
||||||
type ClusterConfig struct {
|
type ClusterConfig struct {
|
||||||
@@ -189,7 +228,7 @@ type ClusterConfig struct {
|
|||||||
configMapNamespace string
|
configMapNamespace string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, credentials *Credentials, clusterName string) *ClusterConfig {
|
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *ClusterConfig {
|
||||||
// var configObj *ConfigObj
|
// var configObj *ConfigObj
|
||||||
c := &ClusterConfig{
|
c := &ClusterConfig{
|
||||||
k8s: k8s,
|
k8s: k8s,
|
||||||
@@ -209,8 +248,12 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
|
|||||||
loadConfigFromFile(c.configObj)
|
loadConfigFromFile(c.configObj)
|
||||||
}
|
}
|
||||||
updateCredentials(c.configObj, credentials)
|
updateCredentials(c.configObj, credentials)
|
||||||
|
updateCloudURLs(c.configObj)
|
||||||
|
|
||||||
if clusterName != "" {
|
// If a custom cluster name is provided then set that name, else use the cluster's original name
|
||||||
|
if customClusterName != "" {
|
||||||
|
c.configObj.ClusterName = AdoptClusterName(customClusterName)
|
||||||
|
} else if clusterName != "" {
|
||||||
c.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
c.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,18 +266,44 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
|
|||||||
c.backendAPI.SetAccountID(c.configObj.AccountID)
|
c.backendAPI.SetAccountID(c.configObj.AccountID)
|
||||||
c.backendAPI.SetClientID(c.configObj.ClientID)
|
c.backendAPI.SetClientID(c.configObj.ClientID)
|
||||||
c.backendAPI.SetSecretKey(c.configObj.SecretKey)
|
c.backendAPI.SetSecretKey(c.configObj.SecretKey)
|
||||||
|
if c.configObj.CloudAPIURL != "" {
|
||||||
|
c.backendAPI.SetCloudAPIURL(c.configObj.CloudAPIURL)
|
||||||
|
} else {
|
||||||
|
c.configObj.CloudAPIURL = c.backendAPI.GetCloudAPIURL()
|
||||||
|
}
|
||||||
|
if c.configObj.CloudAuthURL != "" {
|
||||||
|
c.backendAPI.SetCloudAuthURL(c.configObj.CloudAuthURL)
|
||||||
|
} else {
|
||||||
|
c.configObj.CloudAuthURL = c.backendAPI.GetCloudAuthURL()
|
||||||
|
}
|
||||||
|
if c.configObj.CloudReportURL != "" {
|
||||||
|
c.backendAPI.SetCloudReportURL(c.configObj.CloudReportURL)
|
||||||
|
} else {
|
||||||
|
c.configObj.CloudReportURL = c.backendAPI.GetCloudReportURL()
|
||||||
|
}
|
||||||
|
if c.configObj.CloudUIURL != "" {
|
||||||
|
c.backendAPI.SetCloudUIURL(c.configObj.CloudUIURL)
|
||||||
|
} else {
|
||||||
|
c.configObj.CloudUIURL = c.backendAPI.GetCloudUIURL()
|
||||||
|
}
|
||||||
|
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", c.backendAPI.GetCloudAPIURL()), helpers.String("auth", c.backendAPI.GetCloudAuthURL()), helpers.String("report", c.backendAPI.GetCloudReportURL()), helpers.String("UI", c.backendAPI.GetCloudUIURL()))
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
|
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
|
||||||
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
|
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
|
||||||
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
|
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
|
||||||
func (c *ClusterConfig) GetClientID() string { return c.configObj.ClientID }
|
func (c *ClusterConfig) GetClientID() string { return c.configObj.ClientID }
|
||||||
func (c *ClusterConfig) GetSecretKey() string { return c.configObj.SecretKey }
|
func (c *ClusterConfig) GetSecretKey() string { return c.configObj.SecretKey }
|
||||||
func (c *ClusterConfig) GetTenantEmail() string { return c.configObj.CustomerAdminEMail }
|
func (c *ClusterConfig) GetTenantEmail() string { return c.configObj.CustomerAdminEMail }
|
||||||
func (c *ClusterConfig) GetToken() string { return c.configObj.Token }
|
func (c *ClusterConfig) GetToken() string { return c.configObj.Token }
|
||||||
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
|
func (c *ClusterConfig) GetCloudReportURL() string { return c.configObj.CloudReportURL }
|
||||||
|
func (c *ClusterConfig) GetCloudAPIURL() string { return c.configObj.CloudAPIURL }
|
||||||
|
func (c *ClusterConfig) GetCloudUIURL() string { return c.configObj.CloudUIURL }
|
||||||
|
func (c *ClusterConfig) GetCloudAuthURL() string { return c.configObj.CloudAuthURL }
|
||||||
|
|
||||||
|
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
|
||||||
|
|
||||||
func (c *ClusterConfig) SetTenant() error {
|
func (c *ClusterConfig) SetTenant() error {
|
||||||
|
|
||||||
@@ -468,7 +537,11 @@ func DeleteConfigFile() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AdoptClusterName(clusterName string) string {
|
func AdoptClusterName(clusterName string) string {
|
||||||
return strings.ReplaceAll(clusterName, "/", "-")
|
re, err := regexp.Compile(`[^\w]+`)
|
||||||
|
if err != nil {
|
||||||
|
return clusterName
|
||||||
|
}
|
||||||
|
return re.ReplaceAllString(clusterName, "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfigMapName() string {
|
func getConfigMapName() string {
|
||||||
@@ -516,3 +589,39 @@ func updateCredentials(configObj *ConfigObj, credentials *Credentials) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCloudURLsFromEnv(cloudURLs *CloudURLs) {
|
||||||
|
// load from env
|
||||||
|
if cloudAPIURL := os.Getenv("KS_CLOUD_API_URL"); cloudAPIURL != "" {
|
||||||
|
cloudURLs.CloudAPIURL = cloudAPIURL
|
||||||
|
}
|
||||||
|
if cloudAuthURL := os.Getenv("KS_CLOUD_AUTH_URL"); cloudAuthURL != "" {
|
||||||
|
cloudURLs.CloudAuthURL = cloudAuthURL
|
||||||
|
}
|
||||||
|
if cloudReportURL := os.Getenv("KS_CLOUD_REPORT_URL"); cloudReportURL != "" {
|
||||||
|
cloudURLs.CloudReportURL = cloudReportURL
|
||||||
|
}
|
||||||
|
if cloudUIURL := os.Getenv("KS_CLOUD_UI_URL"); cloudUIURL != "" {
|
||||||
|
cloudURLs.CloudUIURL = cloudUIURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCloudURLs(configObj *ConfigObj) {
|
||||||
|
cloudURLs := &CloudURLs{}
|
||||||
|
|
||||||
|
getCloudURLsFromEnv(cloudURLs)
|
||||||
|
|
||||||
|
if cloudURLs.CloudAPIURL != "" {
|
||||||
|
configObj.CloudAPIURL = cloudURLs.CloudAPIURL // override config CloudAPIURL
|
||||||
|
}
|
||||||
|
if cloudURLs.CloudAuthURL != "" {
|
||||||
|
configObj.CloudAuthURL = cloudURLs.CloudAuthURL // override config CloudAuthURL
|
||||||
|
}
|
||||||
|
if cloudURLs.CloudReportURL != "" {
|
||||||
|
configObj.CloudReportURL = cloudURLs.CloudReportURL // override config CloudReportURL
|
||||||
|
}
|
||||||
|
if cloudURLs.CloudUIURL != "" {
|
||||||
|
configObj.CloudUIURL = cloudURLs.CloudUIURL // override config CloudUIURL
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cautils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -16,6 +17,10 @@ func mockConfigObj() *ConfigObj {
|
|||||||
ClusterName: "ddd",
|
ClusterName: "ddd",
|
||||||
CustomerAdminEMail: "ab@cd",
|
CustomerAdminEMail: "ab@cd",
|
||||||
Token: "eee",
|
Token: "eee",
|
||||||
|
CloudReportURL: "report.armo.cloud",
|
||||||
|
CloudAPIURL: "api.armosec.io",
|
||||||
|
CloudUIURL: "cloud.armosec.io",
|
||||||
|
CloudAuthURL: "auth.armosec.io",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func mockLocalConfig() *LocalConfig {
|
func mockLocalConfig() *LocalConfig {
|
||||||
@@ -39,6 +44,10 @@ func TestConfig(t *testing.T) {
|
|||||||
assert.Equal(t, co.AccountID, cop.AccountID)
|
assert.Equal(t, co.AccountID, cop.AccountID)
|
||||||
assert.Equal(t, co.ClientID, cop.ClientID)
|
assert.Equal(t, co.ClientID, cop.ClientID)
|
||||||
assert.Equal(t, co.SecretKey, cop.SecretKey)
|
assert.Equal(t, co.SecretKey, cop.SecretKey)
|
||||||
|
assert.Equal(t, co.CloudReportURL, cop.CloudReportURL)
|
||||||
|
assert.Equal(t, co.CloudAPIURL, cop.CloudAPIURL)
|
||||||
|
assert.Equal(t, co.CloudUIURL, cop.CloudUIURL)
|
||||||
|
assert.Equal(t, co.CloudAuthURL, cop.CloudAuthURL)
|
||||||
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
|
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
|
||||||
assert.Equal(t, "", cop.CustomerAdminEMail) // Not copied to bytes
|
assert.Equal(t, "", cop.CustomerAdminEMail) // Not copied to bytes
|
||||||
assert.Equal(t, "", cop.Token) // Not copied to bytes
|
assert.Equal(t, "", cop.Token) // Not copied to bytes
|
||||||
@@ -60,6 +69,10 @@ func TestITenantConfig(t *testing.T) {
|
|||||||
assert.Equal(t, co.ClusterName, lc.GetContextName())
|
assert.Equal(t, co.ClusterName, lc.GetContextName())
|
||||||
assert.Equal(t, co.CustomerAdminEMail, lc.GetTenantEmail())
|
assert.Equal(t, co.CustomerAdminEMail, lc.GetTenantEmail())
|
||||||
assert.Equal(t, co.Token, lc.GetToken())
|
assert.Equal(t, co.Token, lc.GetToken())
|
||||||
|
assert.Equal(t, co.CloudReportURL, lc.GetCloudReportURL())
|
||||||
|
assert.Equal(t, co.CloudAPIURL, lc.GetCloudAPIURL())
|
||||||
|
assert.Equal(t, co.CloudUIURL, lc.GetCloudUIURL())
|
||||||
|
assert.Equal(t, co.CloudAuthURL, lc.GetCloudAuthURL())
|
||||||
|
|
||||||
// test ClusterConfig methods
|
// test ClusterConfig methods
|
||||||
assert.Equal(t, co.AccountID, c.GetAccountID())
|
assert.Equal(t, co.AccountID, c.GetAccountID())
|
||||||
@@ -68,6 +81,10 @@ func TestITenantConfig(t *testing.T) {
|
|||||||
assert.Equal(t, co.ClusterName, c.GetContextName())
|
assert.Equal(t, co.ClusterName, c.GetContextName())
|
||||||
assert.Equal(t, co.CustomerAdminEMail, c.GetTenantEmail())
|
assert.Equal(t, co.CustomerAdminEMail, c.GetTenantEmail())
|
||||||
assert.Equal(t, co.Token, c.GetToken())
|
assert.Equal(t, co.Token, c.GetToken())
|
||||||
|
assert.Equal(t, co.CloudReportURL, c.GetCloudReportURL())
|
||||||
|
assert.Equal(t, co.CloudAPIURL, c.GetCloudAPIURL())
|
||||||
|
assert.Equal(t, co.CloudUIURL, c.GetCloudUIURL())
|
||||||
|
assert.Equal(t, co.CloudAuthURL, c.GetCloudAuthURL())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateConfigData(t *testing.T) {
|
func TestUpdateConfigData(t *testing.T) {
|
||||||
@@ -80,6 +97,10 @@ func TestUpdateConfigData(t *testing.T) {
|
|||||||
assert.Equal(t, c.GetAccountID(), configMap.Data["accountID"])
|
assert.Equal(t, c.GetAccountID(), configMap.Data["accountID"])
|
||||||
assert.Equal(t, c.GetClientID(), configMap.Data["clientID"])
|
assert.Equal(t, c.GetClientID(), configMap.Data["clientID"])
|
||||||
assert.Equal(t, c.GetSecretKey(), configMap.Data["secretKey"])
|
assert.Equal(t, c.GetSecretKey(), configMap.Data["secretKey"])
|
||||||
|
assert.Equal(t, c.GetCloudReportURL(), configMap.Data["cloudReportURL"])
|
||||||
|
assert.Equal(t, c.GetCloudAPIURL(), configMap.Data["cloudAPIURL"])
|
||||||
|
assert.Equal(t, c.GetCloudUIURL(), configMap.Data["cloudUIURL"])
|
||||||
|
assert.Equal(t, c.GetCloudAuthURL(), configMap.Data["cloudAuthURL"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadConfig(t *testing.T) {
|
func TestReadConfig(t *testing.T) {
|
||||||
@@ -97,6 +118,10 @@ func TestReadConfig(t *testing.T) {
|
|||||||
assert.Equal(t, com.ClusterName, co.ClusterName)
|
assert.Equal(t, com.ClusterName, co.ClusterName)
|
||||||
assert.Equal(t, com.CustomerAdminEMail, co.CustomerAdminEMail)
|
assert.Equal(t, com.CustomerAdminEMail, co.CustomerAdminEMail)
|
||||||
assert.Equal(t, com.Token, co.Token)
|
assert.Equal(t, com.Token, co.Token)
|
||||||
|
assert.Equal(t, com.CloudReportURL, co.CloudReportURL)
|
||||||
|
assert.Equal(t, com.CloudAPIURL, co.CloudAPIURL)
|
||||||
|
assert.Equal(t, com.CloudUIURL, co.CloudUIURL)
|
||||||
|
assert.Equal(t, com.CloudAuthURL, co.CloudAuthURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadConfigFromData(t *testing.T) {
|
func TestLoadConfigFromData(t *testing.T) {
|
||||||
@@ -120,6 +145,10 @@ func TestLoadConfigFromData(t *testing.T) {
|
|||||||
assert.Equal(t, c.GetContextName(), co.ClusterName)
|
assert.Equal(t, c.GetContextName(), co.ClusterName)
|
||||||
assert.Equal(t, c.GetTenantEmail(), co.CustomerAdminEMail)
|
assert.Equal(t, c.GetTenantEmail(), co.CustomerAdminEMail)
|
||||||
assert.Equal(t, c.GetToken(), co.Token)
|
assert.Equal(t, c.GetToken(), co.Token)
|
||||||
|
assert.Equal(t, c.GetCloudReportURL(), co.CloudReportURL)
|
||||||
|
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
|
||||||
|
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
|
||||||
|
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use case: all data is in config.json
|
// use case: all data is in config.json
|
||||||
@@ -139,6 +168,10 @@ func TestLoadConfigFromData(t *testing.T) {
|
|||||||
assert.Equal(t, c.GetAccountID(), co.AccountID)
|
assert.Equal(t, c.GetAccountID(), co.AccountID)
|
||||||
assert.Equal(t, c.GetClientID(), co.ClientID)
|
assert.Equal(t, c.GetClientID(), co.ClientID)
|
||||||
assert.Equal(t, c.GetSecretKey(), co.SecretKey)
|
assert.Equal(t, c.GetSecretKey(), co.SecretKey)
|
||||||
|
assert.Equal(t, c.GetCloudReportURL(), co.CloudReportURL)
|
||||||
|
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
|
||||||
|
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
|
||||||
|
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use case: some data is in config.json
|
// use case: some data is in config.json
|
||||||
@@ -151,10 +184,12 @@ func TestLoadConfigFromData(t *testing.T) {
|
|||||||
// add to map
|
// add to map
|
||||||
configMap.Data["clientID"] = c.configObj.ClientID
|
configMap.Data["clientID"] = c.configObj.ClientID
|
||||||
configMap.Data["secretKey"] = c.configObj.SecretKey
|
configMap.Data["secretKey"] = c.configObj.SecretKey
|
||||||
|
configMap.Data["cloudReportURL"] = c.configObj.CloudReportURL
|
||||||
|
|
||||||
// delete the content
|
// delete the content
|
||||||
c.configObj.ClientID = ""
|
c.configObj.ClientID = ""
|
||||||
c.configObj.SecretKey = ""
|
c.configObj.SecretKey = ""
|
||||||
|
c.configObj.CloudReportURL = ""
|
||||||
|
|
||||||
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
|
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
|
||||||
loadConfigFromData(c.configObj, configMap.Data)
|
loadConfigFromData(c.configObj, configMap.Data)
|
||||||
@@ -162,6 +197,7 @@ func TestLoadConfigFromData(t *testing.T) {
|
|||||||
assert.NotEmpty(t, c.GetAccountID())
|
assert.NotEmpty(t, c.GetAccountID())
|
||||||
assert.NotEmpty(t, c.GetClientID())
|
assert.NotEmpty(t, c.GetClientID())
|
||||||
assert.NotEmpty(t, c.GetSecretKey())
|
assert.NotEmpty(t, c.GetSecretKey())
|
||||||
|
assert.NotEmpty(t, c.GetCloudReportURL())
|
||||||
}
|
}
|
||||||
|
|
||||||
// use case: some data is in config.json
|
// use case: some data is in config.json
|
||||||
@@ -191,3 +227,44 @@ func TestLoadConfigFromData(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdoptClusterName(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
clusterName string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "replace 1",
|
||||||
|
clusterName: "my-name__is--ks",
|
||||||
|
want: "my-name__is-ks",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replace 2",
|
||||||
|
clusterName: "my-name1",
|
||||||
|
want: "my-name1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replace 3",
|
||||||
|
clusterName: "my:name",
|
||||||
|
want: "my-name",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := AdoptClusterName(tt.clusterName); got != tt.want {
|
||||||
|
t.Errorf("AdoptClusterName() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateCloudURLs(t *testing.T) {
|
||||||
|
co := mockConfigObj()
|
||||||
|
mockCloudAPIURL := "1-2-3-4.com"
|
||||||
|
os.Setenv("KS_CLOUD_API_URL", mockCloudAPIURL)
|
||||||
|
|
||||||
|
assert.NotEqual(t, co.CloudAPIURL, mockCloudAPIURL)
|
||||||
|
updateCloudURLs(co)
|
||||||
|
assert.Equal(t, co.CloudAPIURL, mockCloudAPIURL)
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type KSResources map[string][]string
|
|||||||
type OPASessionObj struct {
|
type OPASessionObj struct {
|
||||||
K8SResources *K8SResources // input k8s objects
|
K8SResources *K8SResources // input k8s objects
|
||||||
ArmoResource *KSResources // input ARMO objects
|
ArmoResource *KSResources // input ARMO objects
|
||||||
|
AllPolicies *Policies // list of all frameworks
|
||||||
Policies []reporthandling.Framework // list of frameworks to scan
|
Policies []reporthandling.Framework // list of frameworks to scan
|
||||||
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<resource ID>]<resource>
|
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>
|
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<resource ID>]<resource result>
|
||||||
@@ -24,7 +25,7 @@ type OPASessionObj struct {
|
|||||||
ResourcesPrioritized map[string]prioritization.PrioritizedResource // resources prioritization information, map[<resource ID>]<prioritized resource>
|
ResourcesPrioritized map[string]prioritization.PrioritizedResource // resources prioritization information, map[<resource ID>]<prioritized resource>
|
||||||
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
|
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
|
||||||
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
|
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
|
||||||
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
|
RegoInputData RegoInputData // input passed to rego for scanning. map[<control name>][<input arguments>]
|
||||||
Metadata *reporthandlingv2.Metadata
|
Metadata *reporthandlingv2.Metadata
|
||||||
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
|
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
|
||||||
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
|
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -30,7 +30,7 @@ const (
|
|||||||
JSON_FILE_FORMAT FileFormat = "json"
|
JSON_FILE_FORMAT FileFormat = "json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadResourcesFromHelmCharts scans a given path (recuresively) for helm charts, renders the templates and returns a map of workloads and a map of chart names
|
// LoadResourcesFromHelmCharts scans a given path (recursively) for helm charts, renders the templates and returns a map of workloads and a map of chart names
|
||||||
func LoadResourcesFromHelmCharts(basePath string) (map[string][]workloadinterface.IMetadata, map[string]string) {
|
func LoadResourcesFromHelmCharts(basePath string) (map[string][]workloadinterface.IMetadata, map[string]string) {
|
||||||
directories, _ := listDirs(basePath)
|
directories, _ := listDirs(basePath)
|
||||||
helmDirectories := make([]string, 0)
|
helmDirectories := make([]string, 0)
|
||||||
@@ -61,6 +61,41 @@ func LoadResourcesFromHelmCharts(basePath string) (map[string][]workloadinterfac
|
|||||||
return sourceToWorkloads, sourceToChartName
|
return sourceToWorkloads, sourceToChartName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the contents at given path is a Kustomize Directory, LoadResourcesFromKustomizeDirectory will
|
||||||
|
// generate yaml files using "Kustomize" & renders a map of workloads from those yaml files
|
||||||
|
func LoadResourcesFromKustomizeDirectory(basePath string) (map[string][]workloadinterface.IMetadata, string) {
|
||||||
|
isKustomizeDirectory := IsKustomizeDirectory(basePath)
|
||||||
|
isKustomizeFile := IsKustomizeFile(basePath)
|
||||||
|
if ok := isKustomizeDirectory || isKustomizeFile; !ok {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceToWorkloads := map[string][]workloadinterface.IMetadata{}
|
||||||
|
kustomizeDirectory := NewKustomizeDirectory(basePath)
|
||||||
|
|
||||||
|
var newBasePath string
|
||||||
|
|
||||||
|
if isKustomizeFile {
|
||||||
|
newBasePath = filepath.Dir(basePath)
|
||||||
|
logger.L().Info("Kustomize File Detected, Scanning the rendered Kubernetes Objects...")
|
||||||
|
} else {
|
||||||
|
newBasePath = basePath
|
||||||
|
logger.L().Info("Kustomize Directory Detected, Scanning the rendered Kubernetes Objects...")
|
||||||
|
}
|
||||||
|
|
||||||
|
wls, errs := kustomizeDirectory.GetWorkloads(newBasePath)
|
||||||
|
kustomizeDirectoryName := GetKustomizeDirectoryName(newBasePath)
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
logger.L().Error(fmt.Sprintf("Rendering yaml from Kustomize failed: %v", errs))
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range wls {
|
||||||
|
sourceToWorkloads[k] = v
|
||||||
|
}
|
||||||
|
return sourceToWorkloads, kustomizeDirectoryName
|
||||||
|
}
|
||||||
|
|
||||||
func LoadResourcesFromFiles(input, rootPath string) map[string][]workloadinterface.IMetadata {
|
func LoadResourcesFromFiles(input, rootPath string) map[string][]workloadinterface.IMetadata {
|
||||||
files, errs := listFiles(input)
|
files, errs := listFiles(input)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
@@ -104,9 +139,9 @@ func loadFiles(rootPath string, filePaths []string) (map[string][]workloadinterf
|
|||||||
for j := range w {
|
for j := range w {
|
||||||
lw := localworkload.NewLocalWorkload(w[j].GetObject())
|
lw := localworkload.NewLocalWorkload(w[j].GetObject())
|
||||||
if relPath, err := filepath.Rel(rootPath, path); err == nil {
|
if relPath, err := filepath.Rel(rootPath, path); err == nil {
|
||||||
lw.SetPath(relPath)
|
lw.SetPath(fmt.Sprintf("%s:%d", relPath, j))
|
||||||
} else {
|
} else {
|
||||||
lw.SetPath(path)
|
lw.SetPath(fmt.Sprintf("%s:%d", path, j))
|
||||||
}
|
}
|
||||||
wSlice = append(wSlice, lw)
|
wSlice = append(wSlice, lw)
|
||||||
}
|
}
|
||||||
@@ -262,7 +297,7 @@ func glob(root, pattern string, onlyDirectories bool) ([]string, error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// listing only directotries
|
// listing only directories
|
||||||
if onlyDirectories {
|
if onlyDirectories {
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
|
if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package getter
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/armosec/armoapi-go/armotypes"
|
||||||
"github.com/kubescape/opa-utils/gitregostore"
|
"github.com/kubescape/opa-utils/gitregostore"
|
||||||
"github.com/kubescape/opa-utils/reporthandling"
|
"github.com/kubescape/opa-utils/reporthandling"
|
||||||
|
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// =======================================================================================================================
|
// =======================================================================================================================
|
||||||
@@ -70,6 +72,14 @@ func (drp *DownloadReleasedPolicy) GetControlsInputs(clusterName string) (map[st
|
|||||||
return defaultConfigInputs.Settings.PostureControlInputs, err
|
return defaultConfigInputs.Settings.PostureControlInputs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (drp *DownloadReleasedPolicy) GetAttackTracks() ([]v1alpha1.AttackTrack, error) {
|
||||||
|
attackTracks, err := drp.gs.GetAttackTracks()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return attackTracks, err
|
||||||
|
}
|
||||||
|
|
||||||
func (drp *DownloadReleasedPolicy) SetRegoObjects() error {
|
func (drp *DownloadReleasedPolicy) SetRegoObjects() error {
|
||||||
fwNames, err := drp.gs.GetOPAFrameworksNamesList()
|
fwNames, err := drp.gs.GetOPAFrameworksNamesList()
|
||||||
if len(fwNames) != 0 && err == nil {
|
if len(fwNames) != 0 && err == nil {
|
||||||
@@ -90,3 +100,11 @@ func contains(s []string, str string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (drp *DownloadReleasedPolicy) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||||
|
exceptions, err := drp.gs.GetSystemPostureExceptionPolicies()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return exceptions, nil
|
||||||
|
}
|
||||||
|
|||||||
42
core/cautils/getter/gcpcloudapi.go
Normal file
42
core/cautils/getter/gcpcloudapi.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
containeranalysis "cloud.google.com/go/containeranalysis/apiv1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GCPCloudAPI struct {
|
||||||
|
credentialsPath string
|
||||||
|
context context.Context
|
||||||
|
client *containeranalysis.Client
|
||||||
|
projectID string
|
||||||
|
credentialsCheck bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGlobalGCPCloudAPIConnector() *GCPCloudAPI {
|
||||||
|
|
||||||
|
if os.Getenv("KS_GCP_CREDENTIALS_PATH") == "" || os.Getenv("KS_GCP_PROJECT_ID") == "" {
|
||||||
|
return &GCPCloudAPI{
|
||||||
|
credentialsCheck: false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return &GCPCloudAPI{
|
||||||
|
context: context.Background(),
|
||||||
|
credentialsPath: os.Getenv("KS_GCP_CREDENTIALS_PATH"),
|
||||||
|
projectID: os.Getenv("KS_GCP_PROJECT_ID"),
|
||||||
|
credentialsCheck: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *GCPCloudAPI) SetClient(client *containeranalysis.Client) {
|
||||||
|
api.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *GCPCloudAPI) GetCredentialsPath() string { return api.credentialsPath }
|
||||||
|
func (api *GCPCloudAPI) GetClient() *containeranalysis.Client { return api.client }
|
||||||
|
func (api *GCPCloudAPI) GetProjectID() string { return api.projectID }
|
||||||
|
func (api *GCPCloudAPI) GetCredentialsCheck() bool { return api.credentialsCheck }
|
||||||
|
func (api *GCPCloudAPI) GetContext() context.Context { return api.context }
|
||||||
@@ -3,6 +3,7 @@ package getter
|
|||||||
import (
|
import (
|
||||||
"github.com/armosec/armoapi-go/armotypes"
|
"github.com/armosec/armoapi-go/armotypes"
|
||||||
"github.com/kubescape/opa-utils/reporthandling"
|
"github.com/kubescape/opa-utils/reporthandling"
|
||||||
|
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// supported listing
|
// supported listing
|
||||||
@@ -27,10 +28,18 @@ type IBackend interface {
|
|||||||
GetAccountID() string
|
GetAccountID() string
|
||||||
GetClientID() string
|
GetClientID() string
|
||||||
GetSecretKey() string
|
GetSecretKey() string
|
||||||
|
GetCloudReportURL() string
|
||||||
|
GetCloudAPIURL() string
|
||||||
|
GetCloudUIURL() string
|
||||||
|
GetCloudAuthURL() string
|
||||||
|
|
||||||
SetAccountID(accountID string)
|
SetAccountID(accountID string)
|
||||||
SetClientID(clientID string)
|
SetClientID(clientID string)
|
||||||
SetSecretKey(secretKey string)
|
SetSecretKey(secretKey string)
|
||||||
|
SetCloudReportURL(cloudReportURL string)
|
||||||
|
SetCloudAPIURL(cloudAPIURL string)
|
||||||
|
SetCloudUIURL(cloudUIURL string)
|
||||||
|
SetCloudAuthURL(cloudAuthURL string)
|
||||||
|
|
||||||
GetTenant() (*TenantResponse, error)
|
GetTenant() (*TenantResponse, error)
|
||||||
}
|
}
|
||||||
@@ -38,3 +47,7 @@ type IBackend interface {
|
|||||||
type IControlsInputsGetter interface {
|
type IControlsInputsGetter interface {
|
||||||
GetControlsInputs(clusterName string) (map[string][]string, error)
|
GetControlsInputs(clusterName string) (map[string][]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IAttackTracksGetter interface {
|
||||||
|
GetAttackTracks() ([]v1alpha1.AttackTrack, error)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/armosec/armoapi-go/armotypes"
|
"github.com/armosec/armoapi-go/armotypes"
|
||||||
logger "github.com/kubescape/go-logger"
|
|
||||||
"github.com/kubescape/go-logger/helpers"
|
|
||||||
"github.com/kubescape/opa-utils/reporthandling"
|
"github.com/kubescape/opa-utils/reporthandling"
|
||||||
|
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -34,23 +33,22 @@ var (
|
|||||||
|
|
||||||
// KSCloudAPI allows accessing the API of the Kubescape Cloud offering
|
// KSCloudAPI allows accessing the API of the Kubescape Cloud offering
|
||||||
type KSCloudAPI struct {
|
type KSCloudAPI struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
apiURL string
|
cloudAPIURL string
|
||||||
authURL string
|
cloudAuthURL string
|
||||||
erURL string
|
cloudReportURL string
|
||||||
feURL string
|
cloudUIURL string
|
||||||
accountID string
|
accountID string
|
||||||
clientID string
|
clientID string
|
||||||
secretKey string
|
secretKey string
|
||||||
authCookie string
|
authCookie string
|
||||||
feToken FeLoginResponse
|
feToken FeLoginResponse
|
||||||
loggedIn bool
|
loggedIn bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalKSCloudAPIConnector *KSCloudAPI
|
var globalKSCloudAPIConnector *KSCloudAPI
|
||||||
|
|
||||||
func SetKSCloudAPIConnector(ksCloudAPI *KSCloudAPI) {
|
func SetKSCloudAPIConnector(ksCloudAPI *KSCloudAPI) {
|
||||||
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", ksCloudAPI.apiURL), helpers.String("auth", ksCloudAPI.authURL), helpers.String("report", ksCloudAPI.erURL), helpers.String("UI", ksCloudAPI.feURL))
|
|
||||||
globalKSCloudAPIConnector = ksCloudAPI
|
globalKSCloudAPIConnector = ksCloudAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,10 +62,10 @@ func GetKSCloudAPIConnector() *KSCloudAPI {
|
|||||||
func NewKSCloudAPIDev() *KSCloudAPI {
|
func NewKSCloudAPIDev() *KSCloudAPI {
|
||||||
apiObj := newKSCloudAPI()
|
apiObj := newKSCloudAPI()
|
||||||
|
|
||||||
apiObj.apiURL = ksCloudDevBEURL
|
apiObj.cloudAPIURL = ksCloudDevBEURL
|
||||||
apiObj.authURL = ksCloudDevAUTHURL
|
apiObj.cloudAuthURL = ksCloudDevAUTHURL
|
||||||
apiObj.erURL = ksCloudDevERURL
|
apiObj.cloudReportURL = ksCloudDevERURL
|
||||||
apiObj.feURL = ksCloudDevFEURL
|
apiObj.cloudUIURL = ksCloudDevFEURL
|
||||||
|
|
||||||
return apiObj
|
return apiObj
|
||||||
}
|
}
|
||||||
@@ -75,10 +73,10 @@ func NewKSCloudAPIDev() *KSCloudAPI {
|
|||||||
func NewKSCloudAPIProd() *KSCloudAPI {
|
func NewKSCloudAPIProd() *KSCloudAPI {
|
||||||
apiObj := newKSCloudAPI()
|
apiObj := newKSCloudAPI()
|
||||||
|
|
||||||
apiObj.apiURL = ksCloudBEURL
|
apiObj.cloudAPIURL = ksCloudBEURL
|
||||||
apiObj.erURL = ksCloudERURL
|
apiObj.cloudReportURL = ksCloudERURL
|
||||||
apiObj.feURL = ksCloudFEURL
|
apiObj.cloudUIURL = ksCloudFEURL
|
||||||
apiObj.authURL = ksCloudAUTHURL
|
apiObj.cloudAuthURL = ksCloudAUTHURL
|
||||||
|
|
||||||
return apiObj
|
return apiObj
|
||||||
}
|
}
|
||||||
@@ -86,10 +84,10 @@ func NewKSCloudAPIProd() *KSCloudAPI {
|
|||||||
func NewKSCloudAPIStaging() *KSCloudAPI {
|
func NewKSCloudAPIStaging() *KSCloudAPI {
|
||||||
apiObj := newKSCloudAPI()
|
apiObj := newKSCloudAPI()
|
||||||
|
|
||||||
apiObj.apiURL = ksCloudStageBEURL
|
apiObj.cloudAPIURL = ksCloudStageBEURL
|
||||||
apiObj.erURL = ksCloudStageERURL
|
apiObj.cloudReportURL = ksCloudStageERURL
|
||||||
apiObj.feURL = ksCloudStageFEURL
|
apiObj.cloudUIURL = ksCloudStageFEURL
|
||||||
apiObj.authURL = ksCloudStageAUTHURL
|
apiObj.cloudAuthURL = ksCloudStageAUTHURL
|
||||||
|
|
||||||
return apiObj
|
return apiObj
|
||||||
}
|
}
|
||||||
@@ -97,10 +95,10 @@ func NewKSCloudAPIStaging() *KSCloudAPI {
|
|||||||
func NewKSCloudAPICustomized(ksCloudERURL, ksCloudBEURL, ksCloudFEURL, ksCloudAUTHURL string) *KSCloudAPI {
|
func NewKSCloudAPICustomized(ksCloudERURL, ksCloudBEURL, ksCloudFEURL, ksCloudAUTHURL string) *KSCloudAPI {
|
||||||
apiObj := newKSCloudAPI()
|
apiObj := newKSCloudAPI()
|
||||||
|
|
||||||
apiObj.erURL = ksCloudERURL
|
apiObj.cloudReportURL = ksCloudERURL
|
||||||
apiObj.apiURL = ksCloudBEURL
|
apiObj.cloudAPIURL = ksCloudBEURL
|
||||||
apiObj.feURL = ksCloudFEURL
|
apiObj.cloudUIURL = ksCloudFEURL
|
||||||
apiObj.authURL = ksCloudAUTHURL
|
apiObj.cloudAuthURL = ksCloudAUTHURL
|
||||||
|
|
||||||
return apiObj
|
return apiObj
|
||||||
}
|
}
|
||||||
@@ -135,17 +133,36 @@ func (api *KSCloudAPI) Get(fullURL string, headers map[string]string) (string, e
|
|||||||
return HttpGetter(api.httpClient, fullURL, headers)
|
return HttpGetter(api.httpClient, fullURL, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *KSCloudAPI) GetAccountID() string { return api.accountID }
|
func (api *KSCloudAPI) GetAccountID() string { return api.accountID }
|
||||||
func (api *KSCloudAPI) IsLoggedIn() bool { return api.loggedIn }
|
func (api *KSCloudAPI) IsLoggedIn() bool { return api.loggedIn }
|
||||||
func (api *KSCloudAPI) GetClientID() string { return api.clientID }
|
func (api *KSCloudAPI) GetClientID() string { return api.clientID }
|
||||||
func (api *KSCloudAPI) GetSecretKey() string { return api.secretKey }
|
func (api *KSCloudAPI) GetSecretKey() string { return api.secretKey }
|
||||||
func (api *KSCloudAPI) GetFrontendURL() string { return api.feURL }
|
func (api *KSCloudAPI) GetCloudReportURL() string { return api.cloudReportURL }
|
||||||
func (api *KSCloudAPI) GetApiURL() string { return api.apiURL }
|
func (api *KSCloudAPI) GetCloudAPIURL() string { return api.cloudAPIURL }
|
||||||
func (api *KSCloudAPI) GetAuthURL() string { return api.authURL }
|
func (api *KSCloudAPI) GetCloudUIURL() string { return api.cloudUIURL }
|
||||||
func (api *KSCloudAPI) GetReportReceiverURL() string { return api.erURL }
|
func (api *KSCloudAPI) GetCloudAuthURL() string { return api.cloudAuthURL }
|
||||||
func (api *KSCloudAPI) SetAccountID(accountID string) { api.accountID = accountID }
|
|
||||||
func (api *KSCloudAPI) SetClientID(clientID string) { api.clientID = clientID }
|
func (api *KSCloudAPI) SetAccountID(accountID string) { api.accountID = accountID }
|
||||||
func (api *KSCloudAPI) SetSecretKey(secretKey string) { api.secretKey = secretKey }
|
func (api *KSCloudAPI) SetClientID(clientID string) { api.clientID = clientID }
|
||||||
|
func (api *KSCloudAPI) SetSecretKey(secretKey string) { api.secretKey = secretKey }
|
||||||
|
func (api *KSCloudAPI) SetCloudReportURL(cloudReportURL string) { api.cloudReportURL = cloudReportURL }
|
||||||
|
func (api *KSCloudAPI) SetCloudAPIURL(cloudAPIURL string) { api.cloudAPIURL = cloudAPIURL }
|
||||||
|
func (api *KSCloudAPI) SetCloudUIURL(cloudUIURL string) { api.cloudUIURL = cloudUIURL }
|
||||||
|
func (api *KSCloudAPI) SetCloudAuthURL(cloudAuthURL string) { api.cloudAuthURL = cloudAuthURL }
|
||||||
|
|
||||||
|
func (api *KSCloudAPI) GetAttackTracks() ([]v1alpha1.AttackTrack, error) {
|
||||||
|
respStr, err := api.Get(api.getAttackTracksURL(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
attackTracks := []v1alpha1.AttackTrack{}
|
||||||
|
if err = JSONDecoder(respStr).Decode(&attackTracks); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return attackTracks, err
|
||||||
|
}
|
||||||
|
|
||||||
func (api *KSCloudAPI) GetFramework(name string) (*reporthandling.Framework, error) {
|
func (api *KSCloudAPI) GetFramework(name string) (*reporthandling.Framework, error) {
|
||||||
respStr, err := api.Get(api.getFrameworkURL(name), nil)
|
respStr, err := api.Get(api.getFrameworkURL(name), nil)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ var NativeFrameworks = []string{"nsa", "mitre", "armobest", "devopsbest"}
|
|||||||
|
|
||||||
func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string {
|
func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string {
|
||||||
u := url.URL{}
|
u := url.URL{}
|
||||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||||
u.Path = "api/v1/armoFrameworks"
|
u.Path = "api/v1/armoFrameworks"
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
||||||
@@ -28,9 +28,20 @@ func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string {
|
|||||||
return u.String()
|
return u.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *KSCloudAPI) getAttackTracksURL() string {
|
||||||
|
u := url.URL{}
|
||||||
|
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||||
|
u.Path = "api/v1/attackTracks"
|
||||||
|
q := u.Query()
|
||||||
|
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (api *KSCloudAPI) getListFrameworkURL() string {
|
func (api *KSCloudAPI) getListFrameworkURL() string {
|
||||||
u := url.URL{}
|
u := url.URL{}
|
||||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||||
u.Path = "api/v1/armoFrameworks"
|
u.Path = "api/v1/armoFrameworks"
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
||||||
@@ -40,7 +51,7 @@ func (api *KSCloudAPI) getListFrameworkURL() string {
|
|||||||
}
|
}
|
||||||
func (api *KSCloudAPI) getExceptionsURL(clusterName string) string {
|
func (api *KSCloudAPI) getExceptionsURL(clusterName string) string {
|
||||||
u := url.URL{}
|
u := url.URL{}
|
||||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||||
u.Path = "api/v1/armoPostureExceptions"
|
u.Path = "api/v1/armoPostureExceptions"
|
||||||
|
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
@@ -55,7 +66,7 @@ func (api *KSCloudAPI) getExceptionsURL(clusterName string) string {
|
|||||||
|
|
||||||
func (api *KSCloudAPI) exceptionsURL(exceptionsPolicyName string) string {
|
func (api *KSCloudAPI) exceptionsURL(exceptionsPolicyName string) string {
|
||||||
u := url.URL{}
|
u := url.URL{}
|
||||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||||
u.Path = "api/v1/postureExceptionPolicy"
|
u.Path = "api/v1/postureExceptionPolicy"
|
||||||
|
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
@@ -77,7 +88,7 @@ func (api *KSCloudAPI) getAccountConfigDefault(clusterName string) string {
|
|||||||
|
|
||||||
func (api *KSCloudAPI) getAccountConfig(clusterName string) string {
|
func (api *KSCloudAPI) getAccountConfig(clusterName string) string {
|
||||||
u := url.URL{}
|
u := url.URL{}
|
||||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||||
u.Path = "api/v1/armoCustomerConfiguration"
|
u.Path = "api/v1/armoCustomerConfiguration"
|
||||||
|
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
@@ -92,21 +103,21 @@ func (api *KSCloudAPI) getAccountConfig(clusterName string) string {
|
|||||||
|
|
||||||
func (api *KSCloudAPI) getAccountURL() string {
|
func (api *KSCloudAPI) getAccountURL() string {
|
||||||
u := url.URL{}
|
u := url.URL{}
|
||||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||||
u.Path = "api/v1/createTenant"
|
u.Path = "api/v1/createTenant"
|
||||||
return u.String()
|
return u.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *KSCloudAPI) getApiToken() string {
|
func (api *KSCloudAPI) getApiToken() string {
|
||||||
u := url.URL{}
|
u := url.URL{}
|
||||||
u.Scheme, u.Host = parseHost(api.GetAuthURL())
|
u.Scheme, u.Host = parseHost(api.GetCloudAuthURL())
|
||||||
u.Path = "identity/resources/auth/v1/api-token"
|
u.Path = "identity/resources/auth/v1/api-token"
|
||||||
return u.String()
|
return u.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *KSCloudAPI) getOpenidCustomers() string {
|
func (api *KSCloudAPI) getOpenidCustomers() string {
|
||||||
u := url.URL{}
|
u := url.URL{}
|
||||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||||
u.Path = "api/v1/openid_customers"
|
u.Path = "api/v1/openid_customers"
|
||||||
return u.String()
|
return u.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,16 +65,16 @@ func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
|
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
|
||||||
framework := &reporthandling.Framework{}
|
var framework reporthandling.Framework
|
||||||
var err error
|
var err error
|
||||||
for _, filePath := range lp.filePaths {
|
for _, filePath := range lp.filePaths {
|
||||||
|
framework = reporthandling.Framework{}
|
||||||
f, err := os.ReadFile(filePath)
|
f, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err = json.Unmarshal(f, &framework); err != nil {
|
||||||
if err = json.Unmarshal(f, framework); err != nil {
|
return nil, err
|
||||||
return framework, err
|
|
||||||
}
|
}
|
||||||
if strings.EqualFold(frameworkName, framework.Name) {
|
if strings.EqualFold(frameworkName, framework.Name) {
|
||||||
break
|
break
|
||||||
@@ -84,7 +84,7 @@ func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framew
|
|||||||
|
|
||||||
return nil, fmt.Errorf("framework from file not matching")
|
return nil, fmt.Errorf("framework from file not matching")
|
||||||
}
|
}
|
||||||
return framework, err
|
return &framework, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
|
func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||||
@@ -130,14 +130,19 @@ func (lp *LoadPolicy) GetControlsInputs(clusterName string) (map[string][]string
|
|||||||
filePath := lp.filePath()
|
filePath := lp.filePath()
|
||||||
accountConfig := &armotypes.CustomerConfig{}
|
accountConfig := &armotypes.CustomerConfig{}
|
||||||
f, err := os.ReadFile(filePath)
|
f, err := os.ReadFile(filePath)
|
||||||
|
fileName := filepath.Base(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
formattedError := fmt.Errorf("Error opening %s file, \"controls-config\" will be downloaded from ARMO management portal", fileName)
|
||||||
|
return nil, formattedError
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = json.Unmarshal(f, &accountConfig.Settings.PostureControlInputs); err == nil {
|
if err = json.Unmarshal(f, &accountConfig.Settings.PostureControlInputs); err == nil {
|
||||||
return accountConfig.Settings.PostureControlInputs, nil
|
return accountConfig.Settings.PostureControlInputs, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
|
||||||
|
formattedError := fmt.Errorf("Error reading %s file, %s, \"controls-config\" will be downloaded from ARMO management portal", fileName, err.Error())
|
||||||
|
|
||||||
|
return nil, formattedError
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporary support for a list of files
|
// temporary support for a list of files
|
||||||
|
|||||||
115
core/cautils/kustomizedirectory.go
Normal file
115
core/cautils/kustomizedirectory.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package cautils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
logger "github.com/kubescape/go-logger"
|
||||||
|
"github.com/kubescape/go-logger/helpers"
|
||||||
|
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||||
|
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/api/krusty"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/filesys"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KustomizeDirectory struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for checking if there is "Kustomization" file in the given Directory
|
||||||
|
var kustomizationFileMatchers = [3]string{"kustomization.yml", "kustomization.yaml", "Kustomization"}
|
||||||
|
|
||||||
|
func IsKustomizeDirectory(path string) bool {
|
||||||
|
if isDir := IsDir(path); !isDir {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastChar := path[len(path)-1:]; lastChar != "/" {
|
||||||
|
path += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := 0
|
||||||
|
for _, kustomizationFileMatcher := range kustomizationFileMatchers {
|
||||||
|
checkPath := path + kustomizationFileMatcher
|
||||||
|
if _, err := os.Stat(checkPath); err == nil {
|
||||||
|
matches++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch matches {
|
||||||
|
case 0:
|
||||||
|
return false
|
||||||
|
case 1:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
logger.L().Info("Multiple kustomize files found while checking Kustomize Directory")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for checking if the path is Kustomization file.
|
||||||
|
func IsKustomizeFile(path string) bool {
|
||||||
|
fileName := filepath.Base(path)
|
||||||
|
|
||||||
|
for _, kustomizationFileMatcher := range kustomizationFileMatchers {
|
||||||
|
if fileName == kustomizationFileMatcher {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKustomizeDirectory(path string) *KustomizeDirectory {
|
||||||
|
return &KustomizeDirectory{
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetKustomizeDirectoryName(path string) string {
|
||||||
|
if isKustomizeDirectory := IsKustomizeDirectory(path); !isKustomizeDirectory {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Workloads, creates the yaml files(K8s resources) using Kustomize and
|
||||||
|
// renders the workloads from the yaml files (k8s resources)
|
||||||
|
func (kd *KustomizeDirectory) GetWorkloads(kustomizeDirectoryPath string) (map[string][]workloadinterface.IMetadata, []error) {
|
||||||
|
|
||||||
|
fSys := filesys.MakeFsOnDisk()
|
||||||
|
kustomizer := krusty.MakeKustomizer(krusty.MakeDefaultOptions())
|
||||||
|
resmap, err := kustomizer.Run(fSys, kustomizeDirectoryPath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
yml, err := resmap.AsYaml()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
workloads := make(map[string][]workloadinterface.IMetadata, 0)
|
||||||
|
errs := []error{}
|
||||||
|
|
||||||
|
wls, e := ReadFile(yml, YAML_FILE_FORMAT)
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
logger.L().Debug("failed to read rendered yaml file", helpers.String("file", kustomizeDirectoryPath), helpers.Error(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(wls) != 0 {
|
||||||
|
workloads[kustomizeDirectoryPath] = []workloadinterface.IMetadata{}
|
||||||
|
for i := range wls {
|
||||||
|
lw := localworkload.NewLocalWorkload(wls[i].GetObject())
|
||||||
|
lw.SetPath(kustomizeDirectoryPath)
|
||||||
|
workloads[kustomizeDirectoryPath] = append(workloads[kustomizeDirectoryPath], lw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return workloads, errs
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
package cautils
|
package cautils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
type RootInfo struct {
|
type RootInfo struct {
|
||||||
Logger string // logger level
|
Logger string // logger level
|
||||||
LoggerName string // logger name ("pretty"/"zap"/"none")
|
LoggerName string // logger name ("pretty"/"zap"/"none")
|
||||||
@@ -9,7 +15,12 @@ type RootInfo struct {
|
|||||||
|
|
||||||
KSCloudBEURLs string // Kubescape Cloud URL
|
KSCloudBEURLs string // Kubescape Cloud URL
|
||||||
KSCloudBEURLsDep string // Kubescape Cloud URL
|
KSCloudBEURLsDep string // Kubescape Cloud URL
|
||||||
|
}
|
||||||
|
type CloudURLs struct {
|
||||||
|
CloudReportURL string
|
||||||
|
CloudAPIURL string
|
||||||
|
CloudUIURL string
|
||||||
|
CloudAuthURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Credentials struct {
|
type Credentials struct {
|
||||||
@@ -17,3 +28,23 @@ type Credentials struct {
|
|||||||
ClientID string
|
ClientID string
|
||||||
SecretKey string
|
SecretKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To check if the user's credentials: accountID / clientID / secretKey are valid.
|
||||||
|
func (credentials *Credentials) Validate() error {
|
||||||
|
|
||||||
|
// Check if the Account-ID is valid
|
||||||
|
if _, err := uuid.Parse(credentials.Account); credentials.Account != "" && err != nil {
|
||||||
|
return fmt.Errorf("bad argument: account must be a valid UUID")
|
||||||
|
}
|
||||||
|
// Check if the Client-ID is valid
|
||||||
|
if _, err := uuid.Parse(credentials.ClientID); credentials.ClientID != "" && err != nil {
|
||||||
|
return fmt.Errorf("bad argument: account must be a valid UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the Secret-Key is valid
|
||||||
|
if _, err := uuid.Parse(credentials.SecretKey); credentials.SecretKey != "" && err != nil {
|
||||||
|
return fmt.Errorf("bad argument: account must be a valid UUID")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
71
core/cautils/rootinfo_test.go
Normal file
71
core/cautils/rootinfo_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package cautils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCredentials_Validate(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Account string
|
||||||
|
ClientID string
|
||||||
|
SecretKey string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid account ID",
|
||||||
|
fields: fields{
|
||||||
|
Account: "22019933-feac-4012-a8eb-e81461ba6655",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid account ID",
|
||||||
|
fields: fields{
|
||||||
|
Account: "22019933-feac-4012-a8eb-e81461ba665",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid client ID",
|
||||||
|
fields: fields{
|
||||||
|
ClientID: "22019933-feac-4012-a8eb-e81461ba6655",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid client ID",
|
||||||
|
fields: fields{
|
||||||
|
ClientID: "22019933-feac-4012-a8eb-e81461ba665",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid secret key",
|
||||||
|
fields: fields{
|
||||||
|
SecretKey: "22019933-feac-4012-a8eb-e81461ba6655",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid secret key",
|
||||||
|
fields: fields{
|
||||||
|
SecretKey: "22019933-feac-4012-a8eb-e81461ba665",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
credentials := &Credentials{
|
||||||
|
Account: tt.fields.Account,
|
||||||
|
ClientID: tt.fields.ClientID,
|
||||||
|
SecretKey: tt.fields.SecretKey,
|
||||||
|
}
|
||||||
|
if err := credentials.Validate(); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Credentials.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,38 +100,41 @@ type PolicyIdentifier struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ScanInfo struct {
|
type ScanInfo struct {
|
||||||
Getters // TODO - remove from object
|
Getters // TODO - remove from object
|
||||||
PolicyIdentifier []PolicyIdentifier // TODO - remove from object
|
PolicyIdentifier []PolicyIdentifier // TODO - remove from object
|
||||||
UseExceptions string // Load file with exceptions configuration
|
UseExceptions string // Load file with exceptions configuration
|
||||||
ControlsInputs string // Load file with inputs for controls
|
ControlsInputs string // Load file with inputs for controls
|
||||||
UseFrom []string // Load framework from local file (instead of download). Use when running offline
|
UseFrom []string // Load framework from local file (instead of download). Use when running offline
|
||||||
UseDefault bool // Load framework from cached file (instead of download). Use when running offline
|
UseDefault bool // Load framework from cached file (instead of download). Use when running offline
|
||||||
UseArtifactsFrom string // Load artifacts from local path. Use when running offline
|
UseArtifactsFrom string // Load artifacts from local path. Use when running offline
|
||||||
VerboseMode bool // Display all of the input resources and not only failed resources
|
VerboseMode bool // Display all of the input resources and not only failed resources
|
||||||
View string // Display all of the input resources and not only failed resources
|
View string // Display all of the input resources and not only failed resources
|
||||||
Format string // Format results (table, json, junit ...)
|
Format string // Format results (table, json, junit ...)
|
||||||
Output string // Store results in an output file, Output file name
|
Output string // Store results in an output file, Output file name
|
||||||
FormatVersion string // Output object can be differnet between versions, this is for testing and backward compatibility
|
FormatVersion string // Output object can be differnet between versions, this is for testing and backward compatibility
|
||||||
ExcludedNamespaces string // used for host scanner namespace
|
CustomClusterName string // Set the custom name of the cluster
|
||||||
IncludeNamespaces string //
|
ExcludedNamespaces string // used for host scanner namespace
|
||||||
InputPatterns []string // Yaml files input patterns
|
IncludeNamespaces string //
|
||||||
Silent bool // Silent mode - Do not print progress logs
|
InputPatterns []string // Yaml files input patterns
|
||||||
FailThreshold float32 // Failure score threshold
|
Silent bool // Silent mode - Do not print progress logs
|
||||||
Submit bool // Submit results to Kubescape Cloud BE
|
FailThreshold float32 // Failure score threshold
|
||||||
ScanID string // Report id of the current scan
|
FailThresholdSeverity string // Severity at and above which the command should fail
|
||||||
HostSensorEnabled BoolPtrFlag // Deploy Kubescape K8s host scanner to collect data from certain controls
|
Submit bool // Submit results to Kubescape Cloud BE
|
||||||
HostSensorYamlPath string // Path to hostsensor file
|
ScanID string // Report id of the current scan
|
||||||
Local bool // Do not submit results
|
HostSensorEnabled BoolPtrFlag // Deploy Kubescape K8s host scanner to collect data from certain controls
|
||||||
Credentials Credentials // account ID
|
HostSensorYamlPath string // Path to hostsensor file
|
||||||
KubeContext string // context name
|
Local bool // Do not submit results
|
||||||
FrameworkScan bool // false if scanning control
|
Credentials Credentials // account ID
|
||||||
ScanAll bool // true if scan all frameworks
|
KubeContext string // context name
|
||||||
|
FrameworkScan bool // false if scanning control
|
||||||
|
ScanAll bool // true if scan all frameworks
|
||||||
}
|
}
|
||||||
|
|
||||||
type Getters struct {
|
type Getters struct {
|
||||||
ExceptionsGetter getter.IExceptionsGetter
|
ExceptionsGetter getter.IExceptionsGetter
|
||||||
ControlsInputsGetter getter.IControlsInputsGetter
|
ControlsInputsGetter getter.IControlsInputsGetter
|
||||||
PolicyGetter getter.IPolicyGetter
|
PolicyGetter getter.IPolicyGetter
|
||||||
|
AttackTracksGetter getter.IAttackTracksGetter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (scanInfo *ScanInfo) Init() {
|
func (scanInfo *ScanInfo) Init() {
|
||||||
@@ -205,13 +208,6 @@ func (scanInfo *ScanInfo) setOutputFile() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (scanInfo *ScanInfo) GetScanningEnvironment() string {
|
|
||||||
// if len(scanInfo.InputPatterns) != 0 {
|
|
||||||
// return ScanLocalFiles
|
|
||||||
// }
|
|
||||||
// return ScanCluster
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind apisv1.NotificationPolicyKind) {
|
func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind apisv1.NotificationPolicyKind) {
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
if !scanInfo.contains(policy) {
|
if !scanInfo.contains(policy) {
|
||||||
@@ -423,6 +419,7 @@ func metadataGitLocal(input string) (*reporthandlingv2.RepoContextMetadata, erro
|
|||||||
Date: commit.Committer.Date,
|
Date: commit.Committer.Date,
|
||||||
CommitterName: commit.Committer.Name,
|
CommitterName: commit.Committer.Name,
|
||||||
}
|
}
|
||||||
|
context.LocalRootPath, _ = gitParser.GetRootDir()
|
||||||
|
|
||||||
return context, nil
|
return context, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ func TestSetContextMetadata(t *testing.T) {
|
|||||||
assert.Nil(t, ctx.HelmContextMetadata)
|
assert.Nil(t, ctx.HelmContextMetadata)
|
||||||
assert.Nil(t, ctx.RepoContextMetadata)
|
assert.Nil(t, ctx.RepoContextMetadata)
|
||||||
}
|
}
|
||||||
{
|
// TODO: tests were commented out due to actual http calls ; http calls should be mocked.
|
||||||
|
/*{
|
||||||
ctx := reporthandlingv2.ContextMetadata{}
|
ctx := reporthandlingv2.ContextMetadata{}
|
||||||
setContextMetadata(&ctx, "https://github.com/kubescape/kubescape")
|
setContextMetadata(&ctx, "https://github.com/kubescape/kubescape")
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ func TestSetContextMetadata(t *testing.T) {
|
|||||||
assert.Equal(t, "kubescape", ctx.RepoContextMetadata.Repo)
|
assert.Equal(t, "kubescape", ctx.RepoContextMetadata.Repo)
|
||||||
assert.Equal(t, "kubescape", ctx.RepoContextMetadata.Owner)
|
assert.Equal(t, "kubescape", ctx.RepoContextMetadata.Owner)
|
||||||
assert.Equal(t, "master", ctx.RepoContextMetadata.Branch)
|
assert.Equal(t, "master", ctx.RepoContextMetadata.Branch)
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetHostname(t *testing.T) {
|
func TestGetHostname(t *testing.T) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const SKIP_VERSION_CHECK = "KS_SKIP_UPDATE_CHECK"
|
|||||||
|
|
||||||
var BuildNumber string
|
var BuildNumber string
|
||||||
var Client string
|
var Client string
|
||||||
|
var LatestReleaseVersion string
|
||||||
|
|
||||||
const UnknownBuildNumber = "unknown"
|
const UnknownBuildNumber = "unknown"
|
||||||
|
|
||||||
@@ -108,9 +109,11 @@ func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckReques
|
|||||||
return fmt.Errorf("failed to get latest version")
|
return fmt.Errorf("failed to get latest version")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LatestReleaseVersion := latestVersion.ClientUpdate
|
||||||
|
|
||||||
if latestVersion.ClientUpdate != "" {
|
if latestVersion.ClientUpdate != "" {
|
||||||
if BuildNumber != "" && semver.Compare(BuildNumber, latestVersion.ClientUpdate) == -1 {
|
if BuildNumber != "" && semver.Compare(BuildNumber, LatestReleaseVersion) == -1 {
|
||||||
logger.L().Warning(warningMessage(latestVersion.ClientUpdate))
|
logger.L().Warning(warningMessage(LatestReleaseVersion))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package cautils
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kubescape/k8s-interface/cloudsupport"
|
||||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,8 +18,12 @@ var (
|
|||||||
"LinuxKernelVariables",
|
"LinuxKernelVariables",
|
||||||
"KubeletInfo",
|
"KubeletInfo",
|
||||||
"KubeProxyInfo",
|
"KubeProxyInfo",
|
||||||
|
"ControlPlaneInfo",
|
||||||
|
}
|
||||||
|
CloudResources = []string{
|
||||||
|
"ClusterDescribe",
|
||||||
|
string(cloudsupport.TypeApiServerInfo),
|
||||||
}
|
}
|
||||||
CloudResources = []string{"ClusterDescribe"}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func MapKSResource(ksResourceMap *KSResources, resources []string) []string {
|
func MapKSResource(ksResourceMap *KSResources, resources []string) []string {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
|
func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
|
||||||
|
|
||||||
tenant := getTenantConfig(nil, "", getKubernetesApi())
|
tenant := getTenantConfig(nil, "", "", getKubernetesApi())
|
||||||
|
|
||||||
if setConfig.Account != "" {
|
if setConfig.Account != "" {
|
||||||
tenant.GetConfigObj().AccountID = setConfig.Account
|
tenant.GetConfigObj().AccountID = setConfig.Account
|
||||||
@@ -19,19 +19,31 @@ func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
|
|||||||
if setConfig.ClientID != "" {
|
if setConfig.ClientID != "" {
|
||||||
tenant.GetConfigObj().ClientID = setConfig.ClientID
|
tenant.GetConfigObj().ClientID = setConfig.ClientID
|
||||||
}
|
}
|
||||||
|
if setConfig.CloudAPIURL != "" {
|
||||||
|
tenant.GetConfigObj().CloudAPIURL = setConfig.CloudAPIURL
|
||||||
|
}
|
||||||
|
if setConfig.CloudAuthURL != "" {
|
||||||
|
tenant.GetConfigObj().CloudAuthURL = setConfig.CloudAuthURL
|
||||||
|
}
|
||||||
|
if setConfig.CloudReportURL != "" {
|
||||||
|
tenant.GetConfigObj().CloudReportURL = setConfig.CloudReportURL
|
||||||
|
}
|
||||||
|
if setConfig.CloudUIURL != "" {
|
||||||
|
tenant.GetConfigObj().CloudUIURL = setConfig.CloudUIURL
|
||||||
|
}
|
||||||
|
|
||||||
return tenant.UpdateCachedConfig()
|
return tenant.UpdateCachedConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// View cached configurations
|
// View cached configurations
|
||||||
func (ks *Kubescape) ViewCachedConfig(viewConfig *metav1.ViewConfig) error {
|
func (ks *Kubescape) ViewCachedConfig(viewConfig *metav1.ViewConfig) error {
|
||||||
tenant := getTenantConfig(nil, "", getKubernetesApi()) // change k8sinterface
|
tenant := getTenantConfig(nil, "", "", getKubernetesApi()) // change k8sinterface
|
||||||
fmt.Fprintf(viewConfig.Writer, "%s\n", tenant.GetConfigObj().Config())
|
fmt.Fprintf(viewConfig.Writer, "%s\n", tenant.GetConfigObj().Config())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks *Kubescape) DeleteCachedConfig(deleteConfig *metav1.DeleteConfig) error {
|
func (ks *Kubescape) DeleteCachedConfig(deleteConfig *metav1.DeleteConfig) error {
|
||||||
|
|
||||||
tenant := getTenantConfig(nil, "", getKubernetesApi()) // change k8sinterface
|
tenant := getTenantConfig(nil, "", "", getKubernetesApi()) // change k8sinterface
|
||||||
return tenant.DeleteCachedConfig()
|
return tenant.DeleteCachedConfig()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
func (ks *Kubescape) DeleteExceptions(delExceptions *v1.DeleteExceptions) error {
|
func (ks *Kubescape) DeleteExceptions(delExceptions *v1.DeleteExceptions) error {
|
||||||
|
|
||||||
// load cached config
|
// load cached config
|
||||||
getTenantConfig(&delExceptions.Credentials, "", getKubernetesApi())
|
getTenantConfig(&delExceptions.Credentials, "", "", getKubernetesApi())
|
||||||
|
|
||||||
// login kubescape SaaS
|
// login kubescape SaaS
|
||||||
ksCloudAPI := getter.GetKSCloudAPIConnector()
|
ksCloudAPI := getter.GetKSCloudAPIConnector()
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func downloadArtifacts(downloadInfo *metav1.DownloadInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func downloadConfigInputs(downloadInfo *metav1.DownloadInfo) error {
|
func downloadConfigInputs(downloadInfo *metav1.DownloadInfo) error {
|
||||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi())
|
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||||
|
|
||||||
controlsInputsGetter := getConfigInputsGetter(downloadInfo.Name, tenant.GetAccountID(), nil)
|
controlsInputsGetter := getConfigInputsGetter(downloadInfo.Name, tenant.GetAccountID(), nil)
|
||||||
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetContextName())
|
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetContextName())
|
||||||
@@ -104,9 +104,9 @@ func downloadConfigInputs(downloadInfo *metav1.DownloadInfo) error {
|
|||||||
|
|
||||||
func downloadExceptions(downloadInfo *metav1.DownloadInfo) error {
|
func downloadExceptions(downloadInfo *metav1.DownloadInfo) error {
|
||||||
var err error
|
var err error
|
||||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi())
|
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||||
|
|
||||||
exceptionsGetter := getExceptionsGetter("")
|
exceptionsGetter := getExceptionsGetter("", tenant.GetAccountID(), nil)
|
||||||
exceptions := []armotypes.PostureExceptionPolicy{}
|
exceptions := []armotypes.PostureExceptionPolicy{}
|
||||||
if tenant.GetAccountID() != "" {
|
if tenant.GetAccountID() != "" {
|
||||||
exceptions, err = exceptionsGetter.GetExceptions(tenant.GetContextName())
|
exceptions, err = exceptionsGetter.GetExceptions(tenant.GetContextName())
|
||||||
@@ -128,7 +128,7 @@ func downloadExceptions(downloadInfo *metav1.DownloadInfo) error {
|
|||||||
|
|
||||||
func downloadFramework(downloadInfo *metav1.DownloadInfo) error {
|
func downloadFramework(downloadInfo *metav1.DownloadInfo) error {
|
||||||
|
|
||||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi())
|
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||||
|
|
||||||
g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
|
g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ func downloadFramework(downloadInfo *metav1.DownloadInfo) error {
|
|||||||
|
|
||||||
func downloadControl(downloadInfo *metav1.DownloadInfo) error {
|
func downloadControl(downloadInfo *metav1.DownloadInfo) error {
|
||||||
|
|
||||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi())
|
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||||
|
|
||||||
g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
|
g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
|
||||||
|
|
||||||
|
|||||||
72
core/core/fix.go
Normal file
72
core/core/fix.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,20 +25,31 @@ func getKubernetesApi() *k8sinterface.KubernetesApi {
|
|||||||
}
|
}
|
||||||
return k8sinterface.NewKubernetesApi()
|
return k8sinterface.NewKubernetesApi()
|
||||||
}
|
}
|
||||||
func getTenantConfig(credentials *cautils.Credentials, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
func getTenantConfig(credentials *cautils.Credentials, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
||||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||||
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName)
|
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||||
}
|
}
|
||||||
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName)
|
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExceptionsGetter(useExceptions string) getter.IExceptionsGetter {
|
func getExceptionsGetter(useExceptions string, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IExceptionsGetter {
|
||||||
if useExceptions != "" {
|
if useExceptions != "" {
|
||||||
// load exceptions from file
|
// load exceptions from file
|
||||||
return getter.NewLoadPolicy([]string{useExceptions})
|
return getter.NewLoadPolicy([]string{useExceptions})
|
||||||
} else {
|
}
|
||||||
|
if accountID != "" {
|
||||||
|
// download exceptions from Kubescape Cloud backend
|
||||||
return getter.GetKSCloudAPIConnector()
|
return getter.GetKSCloudAPIConnector()
|
||||||
}
|
}
|
||||||
|
// download exceptions from GitHub
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
return downloadReleasedPolicy
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, submit bool) *cautils.RBACObjects {
|
func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, submit bool) *cautils.RBACObjects {
|
||||||
@@ -58,7 +69,7 @@ func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fw
|
|||||||
}
|
}
|
||||||
if tenantConfig.GetAccountID() == "" {
|
if tenantConfig.GetAccountID() == "" {
|
||||||
// Add link only when scanning a cluster using a framework
|
// Add link only when scanning a cluster using a framework
|
||||||
return reporterv2.NewReportMock(reporterv2.NO_SUBMIT_QUERY, "run kubescape with the '--submit' flag")
|
return reporterv2.NewReportMock("https://hub.armosec.io/docs/installing-kubescape", "run kubescape with the '--account' flag")
|
||||||
}
|
}
|
||||||
var message string
|
var message string
|
||||||
if !fwScan {
|
if !fwScan {
|
||||||
@@ -128,16 +139,21 @@ func policyIdentifierNames(pi []cautils.PolicyIdentifier) string {
|
|||||||
func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig) {
|
func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If "First run (local config not found)" -
|
If CloudReportURL not set - Do not send report
|
||||||
Default/keep-local - Do not send report
|
|
||||||
Submit - Create tenant & Submit report
|
|
||||||
|
|
||||||
If "Submitted" -
|
If There is no account - Do not send report
|
||||||
|
|
||||||
|
If There is account -
|
||||||
keep-local - Do not send report
|
keep-local - Do not send report
|
||||||
Default/Submit - Submit report
|
Default - Submit report
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
if getter.GetKSCloudAPIConnector().GetCloudAPIURL() == "" {
|
||||||
|
scanInfo.Submit = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// do not submit control scanning
|
// do not submit control scanning
|
||||||
if !scanInfo.FrameworkScan {
|
if !scanInfo.FrameworkScan {
|
||||||
scanInfo.Submit = false
|
scanInfo.Submit = false
|
||||||
@@ -150,27 +166,26 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if tenantConfig.IsConfigFound() { // config found in cache (submitted)
|
if scanInfo.Local {
|
||||||
if !scanInfo.Local {
|
scanInfo.Submit = false
|
||||||
if tenantConfig.GetAccountID() != "" {
|
return
|
||||||
if _, err := uuid.Parse(tenantConfig.GetAccountID()); err != nil {
|
}
|
||||||
scanInfo.Submit = false
|
|
||||||
return
|
// If There is no account, or if the account is not legal, do not submit
|
||||||
}
|
if _, err := uuid.Parse(tenantConfig.GetAccountID()); err != nil {
|
||||||
}
|
scanInfo.Submit = false
|
||||||
// Submit report
|
} else {
|
||||||
scanInfo.Submit = true
|
scanInfo.Submit = true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setPolicyGetter set the policy getter - local file/github release/Kubescape Cloud API
|
// setPolicyGetter set the policy getter - local file/github release/Kubescape Cloud API
|
||||||
func getPolicyGetter(loadPoliciesFromFile []string, tennatEmail string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
|
func getPolicyGetter(loadPoliciesFromFile []string, tenantEmail string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
|
||||||
if len(loadPoliciesFromFile) > 0 {
|
if len(loadPoliciesFromFile) > 0 {
|
||||||
return getter.NewLoadPolicy(loadPoliciesFromFile)
|
return getter.NewLoadPolicy(loadPoliciesFromFile)
|
||||||
}
|
}
|
||||||
if tennatEmail != "" && frameworkScope {
|
if tenantEmail != "" && getter.GetKSCloudAPIConnector().GetCloudAPIURL() != "" && frameworkScope {
|
||||||
g := getter.GetKSCloudAPIConnector() // download policy from Kubescape Cloud backend
|
g := getter.GetKSCloudAPIConnector() // download policy from Kubescape Cloud backend
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
@@ -223,3 +238,17 @@ func listFrameworksNames(policyGetter getter.IPolicyGetter) []string {
|
|||||||
}
|
}
|
||||||
return getter.NativeFrameworks
|
return getter.NativeFrameworks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAttackTracksGetter(accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IAttackTracksGetter {
|
||||||
|
if accountID != "" {
|
||||||
|
g := getter.GetKSCloudAPIConnector() // download attack tracks from Kubescape Cloud backend
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
return downloadReleasedPolicy
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,14 +44,14 @@ func (ks *Kubescape) List(listPolicies *metav1.ListPolicies) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func listFrameworks(listPolicies *metav1.ListPolicies) ([]string, error) {
|
func listFrameworks(listPolicies *metav1.ListPolicies) ([]string, error) {
|
||||||
tenant := getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi()) // change k8sinterface
|
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
|
||||||
g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
|
g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
|
||||||
|
|
||||||
return listFrameworksNames(g), nil
|
return listFrameworksNames(g), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func listControls(listPolicies *metav1.ListPolicies) ([]string, error) {
|
func listControls(listPolicies *metav1.ListPolicies) ([]string, error) {
|
||||||
tenant := getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi()) // change k8sinterface
|
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
|
||||||
|
|
||||||
g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
|
g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
|
||||||
l := getter.ListName
|
l := getter.ListName
|
||||||
@@ -63,10 +63,10 @@ func listControls(listPolicies *metav1.ListPolicies) ([]string, error) {
|
|||||||
|
|
||||||
func listExceptions(listPolicies *metav1.ListPolicies) ([]string, error) {
|
func listExceptions(listPolicies *metav1.ListPolicies) ([]string, error) {
|
||||||
// load tenant metav1
|
// load tenant metav1
|
||||||
getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi())
|
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi())
|
||||||
|
|
||||||
var exceptionsNames []string
|
var exceptionsNames []string
|
||||||
ksCloudAPI := getExceptionsGetter("")
|
ksCloudAPI := getExceptionsGetter("", tenant.GetAccountID(), nil)
|
||||||
exceptions, err := ksCloudAPI.GetExceptions("")
|
exceptions, err := ksCloudAPI.GetExceptions("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return exceptionsNames, err
|
return exceptionsNames, err
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/kubescape/kubescape/v2/core/pkg/opaprocessor"
|
"github.com/kubescape/kubescape/v2/core/pkg/opaprocessor"
|
||||||
"github.com/kubescape/kubescape/v2/core/pkg/policyhandler"
|
"github.com/kubescape/kubescape/v2/core/pkg/policyhandler"
|
||||||
"github.com/kubescape/kubescape/v2/core/pkg/resourcehandler"
|
"github.com/kubescape/kubescape/v2/core/pkg/resourcehandler"
|
||||||
|
"github.com/kubescape/kubescape/v2/core/pkg/resourcesprioritization"
|
||||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
|
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
|
||||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer"
|
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer"
|
||||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter"
|
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter"
|
||||||
@@ -43,7 +44,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
|||||||
|
|
||||||
// ================== setup tenant object ======================================
|
// ================== setup tenant object ======================================
|
||||||
|
|
||||||
tenantConfig := getTenantConfig(&scanInfo.Credentials, scanInfo.KubeContext, k8s)
|
tenantConfig := getTenantConfig(&scanInfo.Credentials, scanInfo.KubeContext, scanInfo.CustomClusterName, k8s)
|
||||||
|
|
||||||
// Set submit behavior AFTER loading tenant config
|
// Set submit behavior AFTER loading tenant config
|
||||||
setSubmitBehavior(scanInfo, tenantConfig)
|
setSubmitBehavior(scanInfo, tenantConfig)
|
||||||
@@ -121,7 +122,8 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
|
|||||||
// set policy getter only after setting the customerGUID
|
// set policy getter only after setting the customerGUID
|
||||||
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetTenantEmail(), scanInfo.FrameworkScan, downloadReleasedPolicy)
|
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetTenantEmail(), scanInfo.FrameworkScan, downloadReleasedPolicy)
|
||||||
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
|
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
|
||||||
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(scanInfo.UseExceptions)
|
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(scanInfo.UseExceptions, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
|
||||||
|
scanInfo.Getters.AttackTracksGetter = getAttackTracksGetter(interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
|
||||||
|
|
||||||
// TODO - list supported frameworks/controls
|
// TODO - list supported frameworks/controls
|
||||||
if scanInfo.ScanAll {
|
if scanInfo.ScanAll {
|
||||||
@@ -152,15 +154,13 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
|
|||||||
return resultsHandling, fmt.Errorf("%w", err)
|
return resultsHandling, fmt.Errorf("%w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// ======================== prioritization ===================
|
||||||
|
|
||||||
// ======================== prioritization ===================
|
if priotizationHandler, err := resourcesprioritization.NewResourcesPrioritizationHandler(scanInfo.Getters.AttackTracksGetter); err != nil {
|
||||||
priotizationHandler := resourcesprioritization.NewResourcesPrioritizationHandler(true)
|
logger.L().Warning("failed to get attack tracks, this may affect the scanning results", helpers.Error(err))
|
||||||
if err := priotizationHandler.PrioritizeResources(scanData); err != nil {
|
} else if err := priotizationHandler.PrioritizeResources(scanData); err != nil {
|
||||||
return resultsHandling, fmt.Errorf("%w", err)
|
return resultsHandling, fmt.Errorf("%w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ========================= results handling =====================
|
// ========================= results handling =====================
|
||||||
resultsHandling.SetData(scanData)
|
resultsHandling.SetData(scanData)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (ks *Kubescape) SubmitExceptions(credentials *cautils.Credentials, excPath
|
|||||||
logger.L().Info("submitting exceptions", helpers.String("path", excPath))
|
logger.L().Info("submitting exceptions", helpers.String("path", excPath))
|
||||||
|
|
||||||
// load cached config
|
// load cached config
|
||||||
tenantConfig := getTenantConfig(credentials, "", getKubernetesApi())
|
tenantConfig := getTenantConfig(credentials, "", "", getKubernetesApi())
|
||||||
if err := tenantConfig.SetTenant(); err != nil {
|
if err := tenantConfig.SetTenant(); err != nil {
|
||||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ package v1
|
|||||||
import "io"
|
import "io"
|
||||||
|
|
||||||
type SetConfig struct {
|
type SetConfig struct {
|
||||||
Account string
|
Account string
|
||||||
ClientID string
|
ClientID string
|
||||||
SecretKey string
|
SecretKey string
|
||||||
|
CloudReportURL string
|
||||||
|
CloudAPIURL string
|
||||||
|
CloudUIURL string
|
||||||
|
CloudAuthURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ViewConfig struct {
|
type ViewConfig struct {
|
||||||
|
|||||||
8
core/meta/datastructures/v1/fix.go
Normal file
8
core/meta/datastructures/v1/fix.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -25,4 +25,7 @@ type IKubescape interface {
|
|||||||
|
|
||||||
// delete
|
// delete
|
||||||
DeleteExceptions(deleteexceptions *metav1.DeleteExceptions) error
|
DeleteExceptions(deleteexceptions *metav1.DeleteExceptions) error
|
||||||
|
|
||||||
|
// fix
|
||||||
|
Fix(fixInfo *metav1.FixInfo) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type ElasticContainerVulnerabilityResult struct {
|
|||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
IsFixed int `json:"isFixed"`
|
IsFixed int `json:"isFixed"`
|
||||||
IntroducedInLayer string `json:"layerHash"`
|
IntroducedInLayer string `json:"layerHash"`
|
||||||
RelevantLinks []string `json:"links"` // shitty SE practice
|
RelevantLinks []string `json:"links"` // Bad SE practice
|
||||||
|
|
||||||
Vulnerability `json:",inline"`
|
Vulnerability `json:",inline"`
|
||||||
}
|
}
|
||||||
|
|||||||
63
core/pkg/fixhandler/datastructures.go
Normal file
63
core/pkg/fixhandler/datastructures.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
346
core/pkg/fixhandler/fixhandler.go
Normal file
346
core/pkg/fixhandler/fixhandler.go
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
248
core/pkg/fixhandler/fixhandler_test.go
Normal file
248
core/pkg/fixhandler/fixhandler_test.go
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
19
core/pkg/fixhandler/testdata/hybrids/tc-01-00-input.yaml
vendored
Normal file
19
core/pkg/fixhandler/testdata/hybrids/tc-01-00-input.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 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
|
||||||
19
core/pkg/fixhandler/testdata/hybrids/tc-01-01-expected.yaml
vendored
Normal file
19
core/pkg/fixhandler/testdata/hybrids/tc-01-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 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
|
||||||
19
core/pkg/fixhandler/testdata/hybrids/tc-02-00-input-indented-list.yaml
vendored
Normal file
19
core/pkg/fixhandler/testdata/hybrids/tc-02-00-input-indented-list.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 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
|
||||||
19
core/pkg/fixhandler/testdata/hybrids/tc-02-01-expected.yaml
vendored
Normal file
19
core/pkg/fixhandler/testdata/hybrids/tc-02-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 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
|
||||||
21
core/pkg/fixhandler/testdata/hybrids/tc-03-00-input-comments.yaml
vendored
Normal file
21
core/pkg/fixhandler/testdata/hybrids/tc-03-00-input-comments.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 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
|
||||||
21
core/pkg/fixhandler/testdata/hybrids/tc-03-01-expected.yaml
vendored
Normal file
21
core/pkg/fixhandler/testdata/hybrids/tc-03-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 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
|
||||||
21
core/pkg/fixhandler/testdata/hybrids/tc-04-00-input-separated-keys.yaml
vendored
Normal file
21
core/pkg/fixhandler/testdata/hybrids/tc-04-00-input-separated-keys.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 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
|
||||||
21
core/pkg/fixhandler/testdata/hybrids/tc-04-01-expected.yaml
vendored
Normal file
21
core/pkg/fixhandler/testdata/hybrids/tc-04-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
12
core/pkg/fixhandler/testdata/inserts/tc-01-00-input-mapping-insert-mapping.yaml
vendored
Normal file
12
core/pkg/fixhandler/testdata/inserts/tc-01-00-input-mapping-insert-mapping.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 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
|
||||||
14
core/pkg/fixhandler/testdata/inserts/tc-01-01-expected.yaml
vendored
Normal file
14
core/pkg/fixhandler/testdata/inserts/tc-01-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 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
|
||||||
11
core/pkg/fixhandler/testdata/inserts/tc-02-00-input-mapping-insert-mapping-with-list.yaml
vendored
Normal file
11
core/pkg/fixhandler/testdata/inserts/tc-02-00-input-mapping-insert-mapping-with-list.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# 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
|
||||||
15
core/pkg/fixhandler/testdata/inserts/tc-02-01-expected.yaml
vendored
Normal file
15
core/pkg/fixhandler/testdata/inserts/tc-02-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# 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
|
||||||
15
core/pkg/fixhandler/testdata/inserts/tc-03-00-input-list-append-scalar.yaml
vendored
Normal file
15
core/pkg/fixhandler/testdata/inserts/tc-03-00-input-list-append-scalar.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# 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
|
||||||
16
core/pkg/fixhandler/testdata/inserts/tc-03-01-expected.yaml
vendored
Normal file
16
core/pkg/fixhandler/testdata/inserts/tc-03-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# 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
|
||||||
47
core/pkg/fixhandler/testdata/inserts/tc-04-00-input-multiple-inserts.yaml
vendored
Normal file
47
core/pkg/fixhandler/testdata/inserts/tc-04-00-input-multiple-inserts.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# 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"]
|
||||||
57
core/pkg/fixhandler/testdata/inserts/tc-04-01-expected.yaml
vendored
Normal file
57
core/pkg/fixhandler/testdata/inserts/tc-04-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# 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
|
||||||
16
core/pkg/fixhandler/testdata/inserts/tc-05-00-input-comment-blank-line-single-insert.yaml
vendored
Normal file
16
core/pkg/fixhandler/testdata/inserts/tc-05-00-input-comment-blank-line-single-insert.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# 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
|
||||||
18
core/pkg/fixhandler/testdata/inserts/tc-05-01-expected.yaml
vendored
Normal file
18
core/pkg/fixhandler/testdata/inserts/tc-05-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 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
|
||||||
14
core/pkg/fixhandler/testdata/inserts/tc-06-00-input-list-append-scalar-oneline.yaml
vendored
Normal file
14
core/pkg/fixhandler/testdata/inserts/tc-06-00-input-list-append-scalar-oneline.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 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]
|
||||||
14
core/pkg/fixhandler/testdata/inserts/tc-06-01-expected.yaml
vendored
Normal file
14
core/pkg/fixhandler/testdata/inserts/tc-06-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 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]
|
||||||
27
core/pkg/fixhandler/testdata/inserts/tc-07-00-input-multiple-documents.yaml
vendored
Normal file
27
core/pkg/fixhandler/testdata/inserts/tc-07-00-input-multiple-documents.yaml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 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
|
||||||
31
core/pkg/fixhandler/testdata/inserts/tc-07-01-expected.yaml
vendored
Normal file
31
core/pkg/fixhandler/testdata/inserts/tc-07-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 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
|
||||||
11
core/pkg/fixhandler/testdata/inserts/tc-08-00-input-mapping-insert-mapping-indented.yaml
vendored
Normal file
11
core/pkg/fixhandler/testdata/inserts/tc-08-00-input-mapping-insert-mapping-indented.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# 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
|
||||||
15
core/pkg/fixhandler/testdata/inserts/tc-08-01-expected.yaml
vendored
Normal file
15
core/pkg/fixhandler/testdata/inserts/tc-08-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# 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
|
||||||
11
core/pkg/fixhandler/testdata/inserts/tc-09-00-input-list-insert-new-mapping-indented.yaml
vendored
Normal file
11
core/pkg/fixhandler/testdata/inserts/tc-09-00-input-list-insert-new-mapping-indented.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# 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
|
||||||
13
core/pkg/fixhandler/testdata/inserts/tc-09-01-expected.yaml
vendored
Normal file
13
core/pkg/fixhandler/testdata/inserts/tc-09-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 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
|
||||||
11
core/pkg/fixhandler/testdata/inserts/tc-10-00-input-list-insert-new-mapping.yaml
vendored
Normal file
11
core/pkg/fixhandler/testdata/inserts/tc-10-00-input-list-insert-new-mapping.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# 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
|
||||||
13
core/pkg/fixhandler/testdata/inserts/tc-10-01-expected.yaml
vendored
Normal file
13
core/pkg/fixhandler/testdata/inserts/tc-10-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 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
|
||||||
14
core/pkg/fixhandler/testdata/removals/tc-01-00-input.yaml
vendored
Normal file
14
core/pkg/fixhandler/testdata/removals/tc-01-00-input.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 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
|
||||||
12
core/pkg/fixhandler/testdata/removals/tc-01-01-expected.yaml
vendored
Normal file
12
core/pkg/fixhandler/testdata/removals/tc-01-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 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
|
||||||
15
core/pkg/fixhandler/testdata/removals/tc-02-00-input.yaml
vendored
Normal file
15
core/pkg/fixhandler/testdata/removals/tc-02-00-input.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# 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
|
||||||
12
core/pkg/fixhandler/testdata/removals/tc-02-01-expected.yaml
vendored
Normal file
12
core/pkg/fixhandler/testdata/removals/tc-02-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 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
|
||||||
14
core/pkg/fixhandler/testdata/removals/tc-03-00-input.yaml
vendored
Normal file
14
core/pkg/fixhandler/testdata/removals/tc-03-00-input.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 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"]
|
||||||
14
core/pkg/fixhandler/testdata/removals/tc-03-01-expected.yaml
vendored
Normal file
14
core/pkg/fixhandler/testdata/removals/tc-03-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 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"]
|
||||||
32
core/pkg/fixhandler/testdata/removals/tc-04-00-input.yaml
vendored
Normal file
32
core/pkg/fixhandler/testdata/removals/tc-04-00-input.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# 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
|
||||||
27
core/pkg/fixhandler/testdata/removals/tc-04-01-expected.yaml
vendored
Normal file
27
core/pkg/fixhandler/testdata/removals/tc-04-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 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
|
||||||
14
core/pkg/fixhandler/testdata/replaces/tc-01-00-input.yaml
vendored
Normal file
14
core/pkg/fixhandler/testdata/replaces/tc-01-00-input.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 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
|
||||||
14
core/pkg/fixhandler/testdata/replaces/tc-01-01-expected.yaml
vendored
Normal file
14
core/pkg/fixhandler/testdata/replaces/tc-01-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 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
|
||||||
18
core/pkg/fixhandler/testdata/replaces/tc-02-00-input.yaml
vendored
Normal file
18
core/pkg/fixhandler/testdata/replaces/tc-02-00-input.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 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"]
|
||||||
18
core/pkg/fixhandler/testdata/replaces/tc-02-01-expected.yaml
vendored
Normal file
18
core/pkg/fixhandler/testdata/replaces/tc-02-01-expected.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 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"]
|
||||||
286
core/pkg/fixhandler/yamlhandler.go
Normal file
286
core/pkg/fixhandler/yamlhandler.go
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
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(¤tDFSNode) {
|
||||||
|
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
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user