mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
Compare commits
388 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1514d6e76 | ||
|
|
3a038c9a0e | ||
|
|
b4bdf4d860 | ||
|
|
08e7108dc0 | ||
|
|
108a2d6dd8 | ||
|
|
2c28286bb1 | ||
|
|
79858b7ed7 | ||
|
|
bb2e83eb3b | ||
|
|
282a29b971 | ||
|
|
60b9edc463 | ||
|
|
0f9a5e3127 | ||
|
|
7c79c14363 | ||
|
|
fe84225252 | ||
|
|
56da8d8d92 | ||
|
|
f135e95d2c | ||
|
|
db34183fc1 | ||
|
|
8f3af71c84 | ||
|
|
116aee0c9c | ||
|
|
e5d44f741d | ||
|
|
f005cb7f80 | ||
|
|
9ae9d35ccb | ||
|
|
cb38a4e8a1 | ||
|
|
eb6d39be42 | ||
|
|
3160d74c42 | ||
|
|
5076c38482 | ||
|
|
73c55fe253 | ||
|
|
f48f81c0b5 | ||
|
|
81c1c29b7c | ||
|
|
874aa38f68 | ||
|
|
b9caaf5025 | ||
|
|
61c120de0e | ||
|
|
de3408bf57 | ||
|
|
8d32032ec1 | ||
|
|
42ed787f7b | ||
|
|
ccdba85b3c | ||
|
|
c59f7691dc | ||
|
|
cf87c2d30b | ||
|
|
b547814dec | ||
|
|
b476a72e04 | ||
|
|
4f6f85710a | ||
|
|
47c23de160 | ||
|
|
bc85844ec0 | ||
|
|
134d854722 | ||
|
|
e3522c19cc | ||
|
|
967fc3fe81 | ||
|
|
896a0699ec | ||
|
|
a53375204e | ||
|
|
b1392361f8 | ||
|
|
7b4fbffae2 | ||
|
|
34e7b9f2ad | ||
|
|
f0080bdeae | ||
|
|
0eb27389da | ||
|
|
2c5eed9ee2 | ||
|
|
2c1a5bd032 | ||
|
|
298f8346e9 | ||
|
|
1897c5a4ba | ||
|
|
57e435271e | ||
|
|
7e9b430347 | ||
|
|
ca5b3e626b | ||
|
|
3a404f29fa | ||
|
|
16073d6872 | ||
|
|
dce563d2f5 | ||
|
|
8d556a5b84 | ||
|
|
a61063e5b8 | ||
|
|
94973867db | ||
|
|
214c2dcae8 | ||
|
|
72b36bf012 | ||
|
|
4335e6ceac | ||
|
|
b5f92a7d54 | ||
|
|
41ec75d264 | ||
|
|
6d6ad1f487 | ||
|
|
3ac33d21ac | ||
|
|
04e4b37f6f | ||
|
|
3e5903de6a | ||
|
|
04ea0fe524 | ||
|
|
955d6751a9 | ||
|
|
30c43bff10 | ||
|
|
e009244566 | ||
|
|
3d3cd2c2d8 | ||
|
|
f5498371ec | ||
|
|
c3b95bed8c | ||
|
|
8ce7d6c0f6 | ||
|
|
e875f429a9 | ||
|
|
b6beff0488 | ||
|
|
60c69ac3f0 | ||
|
|
1fb9320421 | ||
|
|
9a176f6667 | ||
|
|
96ea9a9e42 | ||
|
|
e39fca0c11 | ||
|
|
2ec035005d | ||
|
|
b734b3aef0 | ||
|
|
0f5635f42d | ||
|
|
8557075b7c | ||
|
|
bc0f0e7087 | ||
|
|
8ce5f9aea3 | ||
|
|
050f9d3a4e | ||
|
|
a81bf0deb4 | ||
|
|
2059324c27 | ||
|
|
a09a0a1bca | ||
|
|
83712bb9f5 | ||
|
|
728ae47b9a | ||
|
|
2a9b272a14 | ||
|
|
8662deac43 | ||
|
|
e42644bbd8 | ||
|
|
07d30b6272 | ||
|
|
2a4f8543cc | ||
|
|
186b293cce | ||
|
|
2bfe72f39d | ||
|
|
f99f955223 | ||
|
|
ec56e69a3c | ||
|
|
3942583b1d | ||
|
|
a10b15ba4b | ||
|
|
5003cbd7a8 | ||
|
|
481a137c23 | ||
|
|
c3f7f0938d | ||
|
|
b1925fa38d | ||
|
|
d9f8a7a46f | ||
|
|
846a072bf9 | ||
|
|
5dd7bbd8a7 | ||
|
|
e1773acf24 | ||
|
|
03a0f97669 | ||
|
|
917a3f41e8 | ||
|
|
3c8da1b299 | ||
|
|
c61c7edbd0 | ||
|
|
53402d9a1c | ||
|
|
de9278b388 | ||
|
|
4fef6200f8 | ||
|
|
81771b7bd7 | ||
|
|
2fee77c42c | ||
|
|
968ecdb31d | ||
|
|
af7b36a88b | ||
|
|
6ad58d38e2 | ||
|
|
681b4ce155 | ||
|
|
9d21ac1b16 | ||
|
|
2b3fcca7e8 | ||
|
|
af8e786ab5 | ||
|
|
c8df1b8f1f | ||
|
|
4f921ddf6f | ||
|
|
4f5839870b | ||
|
|
c0d7f51d6c | ||
|
|
a81d770360 | ||
|
|
f64d5eab50 | ||
|
|
d773397fe9 | ||
|
|
2e30995bfc | ||
|
|
17a2547f18 | ||
|
|
87a5cd66c8 | ||
|
|
9436ace64f | ||
|
|
fde00f6bd8 | ||
|
|
04a72a069a | ||
|
|
e2dcb5bc15 | ||
|
|
c7040a257c | ||
|
|
602dc00c65 | ||
|
|
0339691571 | ||
|
|
9e1f3ec131 | ||
|
|
b8589819dc | ||
|
|
a3e87f4c01 | ||
|
|
21ab5a602e | ||
|
|
5d97d7b4b2 | ||
|
|
d8d7d0b372 | ||
|
|
b8323d41fc | ||
|
|
d0b5314201 | ||
|
|
547e36e73f | ||
|
|
e593a772cb | ||
|
|
4da09529b6 | ||
|
|
de375992e8 | ||
|
|
0bc4a29881 | ||
|
|
9575c92713 | ||
|
|
cf277874eb | ||
|
|
746e060402 | ||
|
|
dd3a7c816e | ||
|
|
814bc3ab2c | ||
|
|
dbaf6761df | ||
|
|
580e45827d | ||
|
|
f3b8de9d1f | ||
|
|
fb1c728b12 | ||
|
|
6964ca0d18 | ||
|
|
6e9a2f55fd | ||
|
|
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 | ||
|
|
dd7a8fd0c1 | ||
|
|
4277331ee2 | ||
|
|
53561a728f | ||
|
|
d0fd8c4fe4 | ||
|
|
398989510b | ||
|
|
f8e3ad5685 | ||
|
|
fbea7ef874 | ||
|
|
dc2c6f8a21 | ||
|
|
5ee08583b6 | ||
|
|
bfbd278e7c | ||
|
|
4c6e5903e3 | ||
|
|
a7cd5672c1 | ||
|
|
3373b728b7 | ||
|
|
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 |
54
.github/workflows/01-golang-lint.yaml
vendored
Normal file
54
.github/workflows/01-golang-lint.yaml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
pull_request:
|
||||
types: [ edited, opened, synchronize, reopened ]
|
||||
branches: [ master, dev ]
|
||||
paths-ignore:
|
||||
- '**.yaml'
|
||||
- '**.md'
|
||||
permissions:
|
||||
contents: read
|
||||
# Optional: allow read access to pull request. Use with `only-new-issues` option.
|
||||
pull-requests: read
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install libgit2
|
||||
run: make libgit2
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: latest
|
||||
|
||||
# Optional: working directory, useful for monorepos
|
||||
# working-directory: somedir
|
||||
|
||||
# Optional: golangci-lint command line arguments.
|
||||
# args: --issues-exit-code=0
|
||||
args: --timeout 10m --build-tags=static
|
||||
#--new-from-rev dev
|
||||
|
||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||
only-new-issues: true
|
||||
|
||||
# Optional: if set to true then the all caching functionality will be complete disabled,
|
||||
# takes precedence over all other caching options.
|
||||
# skip-cache: true
|
||||
|
||||
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
|
||||
# skip-pkg-cache: true
|
||||
|
||||
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
|
||||
# skip-build-cache: true
|
||||
89
.github/workflows/build-image.yaml
vendored
Normal file
89
.github/workflows/build-image.yaml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
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'
|
||||
|
||||
jobs:
|
||||
check-secret:
|
||||
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
|
||||
steps:
|
||||
- name: Check whether unity activation requests should be done
|
||||
id: check-secret-set
|
||||
env:
|
||||
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
|
||||
|
||||
build-image:
|
||||
needs: [check-secret]
|
||||
if: needs.check-secret.outputs.is-secret-set == 'true'
|
||||
name: Build image and upload to registry
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
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 }}
|
||||
|
||||
198
.github/workflows/build.yaml
vendored
198
.github/workflows/build.yaml
vendored
@@ -4,76 +4,40 @@ on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
# Do not run the pipeline if only Markdown files changed
|
||||
- '**.md'
|
||||
jobs:
|
||||
once:
|
||||
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: v2.0.${{ github.run_number }}
|
||||
release_name: Release v2.0.${{ github.run_number }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
build:
|
||||
name: Create cross-platform release build, tag and upload binaries
|
||||
needs: once
|
||||
test:
|
||||
uses: ./.github/workflows/test.yaml
|
||||
with:
|
||||
release: "v2.0.${{ github.run_number }}"
|
||||
client: test
|
||||
|
||||
create-release:
|
||||
uses: ./.github/workflows/release.yaml
|
||||
needs: test
|
||||
with:
|
||||
release_name: "Release v2.0.${{ github.run_number }}"
|
||||
tag_name: "v2.0.${{ github.run_number }}"
|
||||
secrets: inherit
|
||||
|
||||
publish-artifacts:
|
||||
name: Build and publish artifacts
|
||||
needs: create-release
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
os: [ubuntu-20.04, 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: Test cmd pkg
|
||||
# run: cd cmd && go test -v ./...
|
||||
go-version: 1.19
|
||||
|
||||
- name: Install MSYS2 & libgit2 (Windows)
|
||||
shell: cmd
|
||||
@@ -84,102 +48,68 @@ jobs:
|
||||
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 }}
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
CLIENT: release
|
||||
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 release binaries
|
||||
id: upload-release-asset
|
||||
|
||||
- name: Upload release binaries (Windows / MacOS)
|
||||
id: upload-release-asset-win-macos
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.once.outputs.upload_url }}
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: build/${{ matrix.os }}/kubescape
|
||||
asset_name: kubescape-${{ matrix.os }}
|
||||
asset_content_type: application/octet-stream
|
||||
if: matrix.os != 'ubuntu-20.04'
|
||||
|
||||
- name: Upload release hash
|
||||
id: upload-release-hash
|
||||
- name: Upload release binaries (Linux)
|
||||
id: upload-release-asset-linux
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.once.outputs.upload_url }}
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: build/ubuntu-latest/kubescape
|
||||
asset_name: kubescape-ubuntu-latest
|
||||
asset_content_type: application/octet-stream
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
|
||||
- name: Upload release hash (Windows / MacOS)
|
||||
id: upload-release-hash-win-macos
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: build/${{ matrix.os }}/kubescape.sha256
|
||||
asset_name: kubescape-${{ matrix.os }}-sha256
|
||||
asset_content_type: application/octet-stream
|
||||
build-docker:
|
||||
name: Build docker container, tag and upload to registry
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'kubescape/kubescape' }} # TODO
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
contents: read
|
||||
if: matrix.os != 'ubuntu-20.04'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- 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
|
||||
- name: Upload release hash (Linux)
|
||||
id: upload-release-hash-linux
|
||||
uses: actions/upload-release-asset@v1
|
||||
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 }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: build/ubuntu-latest/kubescape.sha256
|
||||
asset_name: kubescape-ubuntu-latest-sha256
|
||||
asset_content_type: application/octet-stream
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
|
||||
publish-image:
|
||||
uses: ./.github/workflows/build-image.yaml
|
||||
needs: create-release
|
||||
with:
|
||||
client: "image-release"
|
||||
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
|
||||
image_tag: "v2.0.${{ github.run_number }}"
|
||||
support_platforms: true
|
||||
cosign: true
|
||||
secrets: inherit
|
||||
|
||||
149
.github/workflows/build_dev.yaml
vendored
149
.github/workflows/build_dev.yaml
vendored
@@ -7,136 +7,19 @@ on:
|
||||
# Do not run the pipeline if only Markdown files changed
|
||||
- '**.md'
|
||||
jobs:
|
||||
build:
|
||||
name: Create cross-platform dev build
|
||||
runs-on: ${{ matrix.os }}
|
||||
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: 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
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
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
|
||||
test:
|
||||
uses: ./.github/workflows/test.yaml
|
||||
with:
|
||||
release: "v2.0.${{ github.run_number }}"
|
||||
client: test
|
||||
|
||||
# publish-dev-image:
|
||||
# uses: ./.github/workflows/build-image.yaml
|
||||
# needs: test
|
||||
# with:
|
||||
# client: "image-dev"
|
||||
# image_name: "quay.io/${{ github.repository_owner }}/kubescape"
|
||||
# image_tag: "dev-v2.0.${{ github.run_number }}"
|
||||
# support_platforms: true
|
||||
# cosign: true
|
||||
# secrets: inherit
|
||||
|
||||
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
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: '<h3>Hey, Welcome to this repo, Congratulations on opening your issue. Keep Contributing to Kubescape</h3>'
|
||||
pr-message: '<h3>Hey, Welcome to this repo, Congratulations on opening your Pull Request. Keep Contributing to Kubescape</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>'
|
||||
issue-message: '<h3>Hi! Welcome to Kubescape. Thank you for taking the time and reporting an issue</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 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 ]
|
||||
paths-ignore:
|
||||
# Do not run the pipeline if only Markdown files changed
|
||||
- '**.yaml'
|
||||
- '**.md'
|
||||
jobs:
|
||||
build:
|
||||
name: Create cross-platform build
|
||||
runs-on: ${{ matrix.os }}
|
||||
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 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
|
||||
|
||||
test:
|
||||
uses: ./.github/workflows/test.yaml
|
||||
with:
|
||||
release: "v2.0.${{ github.run_number }}"
|
||||
client: test
|
||||
|
||||
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
|
||||
|
||||
100
.github/workflows/test.yaml
vendored
Normal file
100
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
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-20.04, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Cache Go modules (Linux)
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
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.19
|
||||
|
||||
- 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 (Windows / MacOS)
|
||||
env:
|
||||
RELEASE: ${{ inputs.release }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
||||
if: matrix.os != 'ubuntu-20.04'
|
||||
|
||||
- name: Smoke Testing (Linux)
|
||||
env:
|
||||
RELEASE: ${{ inputs.release }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/ubuntu-latest/kubescape
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,8 +1,9 @@
|
||||
*.vs*
|
||||
*kubescape*
|
||||
*debug*
|
||||
*vender*
|
||||
*vendor*
|
||||
*.pyc*
|
||||
.idea
|
||||
.history
|
||||
ca.srl
|
||||
ca.srl
|
||||
*.out
|
||||
|
||||
58
.golangci.yml
Normal file
58
.golangci.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
dupl:
|
||||
threshold: 200
|
||||
goconst:
|
||||
min-len: 3
|
||||
min-occurrences: 2
|
||||
gocognit:
|
||||
min-complexity: 65
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gosec
|
||||
- staticcheck
|
||||
- nolintlint
|
||||
disable:
|
||||
# temporarily disabled
|
||||
- varcheck
|
||||
- ineffassign
|
||||
- unused
|
||||
- typecheck
|
||||
- errcheck
|
||||
- govet
|
||||
- gosimple
|
||||
- deadcode
|
||||
- gofmt
|
||||
- goimports
|
||||
- bodyclose
|
||||
- dupl
|
||||
- gocognit
|
||||
- gocritic
|
||||
- goimports
|
||||
- nakedret
|
||||
- revive
|
||||
- stylecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
#- forbidigo # <- see later
|
||||
# should remain disabled
|
||||
- maligned
|
||||
- lll
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
issues:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- revive
|
||||
text: "var-naming"
|
||||
- linters:
|
||||
- revive
|
||||
text: "type name will be used as (.+?) by other packages, and that stutters"
|
||||
- linters:
|
||||
- stylecheck
|
||||
text: "ST1003"
|
||||
run:
|
||||
skip-dirs:
|
||||
- git2go
|
||||
@@ -1,127 +1,3 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
## Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement [here](mailto:ben@armosec.io).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Maintainers
|
||||
|
||||
The following table lists Kubescape project maintainers
|
||||
The following table lists the Kubescape project maintainers:
|
||||
|
||||
| Name | GitHub | Email | Organization | Role | Added/Renewed On |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| [Ben Hirschberg](https://www.linkedin.com/in/benyamin-ben-hirschberg-66141890) | [@slashben](https://github.com/slashben) | ben@armosec.io | [ARMO](https://www.armosec.io/) | VP R&D | 2021-09-01 |
|
||||
| [Rotem Refael](https://www.linkedin.com/in/rotem-refael) | [@rotemamsa](https://github.com/rotemamsa) | rrefael@armosec.io | [ARMO](https://www.armosec.io/) | Team Leader | 2021-10-11 |
|
||||
| [David Wertenteil](https://www.linkedin.com/in/david-wertenteil-0ba277b9) | [@dwertent](https://github.com/dwertent) | dwertent@armosec.io | [ARMO](https://www.armosec.io/) | Kubescape CLI Developer | 2021-09-01 |
|
||||
| [Bezalel Brandwine](https://www.linkedin.com/in/bezalel-brandwine) | [@Bezbran](https://github.com/Bezbran) | bbrandwine@armosec.io | [ARMO](https://www.armosec.io/) | Kubescape SaaS Developer | 2021-09-01 |
|
||||
| Name | GitHub | Organization | Added/Renewed On |
|
||||
| --- | --- | --- | --- |
|
||||
| [Ben Hirschberg](https://www.linkedin.com/in/benyamin-ben-hirschberg-66141890) | [@slashben](https://github.com/slashben) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
|
||||
| [Rotem Refael](https://www.linkedin.com/in/rotem-refael) | [@rotemamsa](https://github.com/rotemamsa) | [ARMO](https://www.armosec.io/) | 2021-10-11 |
|
||||
| [David Wertenteil](https://www.linkedin.com/in/david-wertenteil-0ba277b9) | [@dwertent](https://github.com/dwertent) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
|
||||
| [Bezalel Brandwine](https://www.linkedin.com/in/bezalel-brandwine) | [@Bezbran](https://github.com/Bezbran) | [ARMO](https://www.armosec.io/) | 2021-09-01 |
|
||||
| [Craig Box](https://www.linkedin.com/in/crbnz/) | [@craigbox](https://github.com/craigbox) | [ARMO](https://www.armosec.io/) | 2022-10-31 |
|
||||
|
||||
117
README.md
117
README.md
@@ -11,11 +11,11 @@
|
||||
: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 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 is an open-source Kubernetes security platform. A single pane of glass access to view risk analysis, security compliance, RBAC visualization, and image vulnerability scanning.
|
||||
Kubescape scans Kubernetes clusters, YAML files, and Helm charts. It detects misconfigurations according to multiple frameworks (such as [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/) and [CIS Benchmark](https://www.armosec.io/blog/cis-kubernetes-benchmark-framework-scanning-tools-comparison/?utm_source=github&utm_medium=repository)). Kubescape also helps you find software vulnerabilities, and RBAC (role-based-access-control) violations at early stages of the CI/CD pipeline. It calculates your 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.
|
||||
Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI, Github workflows, Prometheus, and Slack, and supports multi-cloud K8s deployments like EKS, GKE, and AKS.
|
||||
Kubescape is one of the fastest-growing Kubernetes security tools among developers. It saves Kubernetes users and admins precious time, effort, and resources with its easy-to-use CLI interface, flexible output formats, and automated scanning capabilities.
|
||||
Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI, Github workflows, Prometheus, and Slack. It supports multi-cloud Kubernetes deployments like EKS, GKE, and AKS.
|
||||
|
||||
</br>
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
*OR:*
|
||||
|
||||
[Install on windows](#install-on-windows)
|
||||
|
||||
[Install on macOS](#install-on-macos)
|
||||
|
||||
[Install on NixOS or Linux/macOS via nix](#install-on-nixos-or-with-nix-community)
|
||||
|
||||
[Install using Go](#install-using-go)
|
||||
|
||||
## Run:
|
||||
```sh
|
||||
kubescape scan --submit --enable-host-scan --verbose
|
||||
kubescape scan --enable-host-scan --verbose
|
||||
```
|
||||
|
||||
<img src="docs/summary.png">
|
||||
@@ -51,15 +51,35 @@ kubescape scan --submit --enable-host-scan --verbose
|
||||
|
||||
</br>
|
||||
|
||||
## Architecture in short
|
||||
|
||||
[Component architecture](docs/architecture.drawio.svg)
|
||||
|
||||
### [CLI](#kubescape-cli)
|
||||
<div align="center">
|
||||
<img src="docs/ks-cli-arch.png" width="300" alt="cli-diagram">
|
||||
</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 😀
|
||||
|
||||
</br>
|
||||
|
||||
# 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
|
||||
You are in vited to our community! We are excited about this project and want to return the love we get.
|
||||
|
||||
[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) .
|
||||
We hold community meetings on [Zoom](https://us02web.zoom.us/j/84020231442) on the first Tuesday of every month at 14:00 GMT! :sunglasses:
|
||||
|
||||
Please make sure that you follow our [Code Of Conduct](https://github.com/kubescape/kubescape/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
## Contributions
|
||||
Want to discuss something? Have an issue? [Want to contribute?](https://github.com/kubescape/kubescape/blob/master/CONTRIBUTING.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 :)
|
||||
* [Open an issue](https://github.com/kubescape/kubescape/issues/new/choose) , we are trying to respond within 48 hours
|
||||
@@ -81,6 +101,7 @@ We invite you to our team! We are excited about this project and want to return
|
||||
* [Overview](https://youtu.be/wdBkt_0Qhbg)
|
||||
* [How To Secure Kubernetes Clusters With Kubescape And Armo](https://youtu.be/ZATGiDIDBQk)
|
||||
* [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)
|
||||
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
|
||||
* [Configure and run customized frameworks](https://youtu.be/12Sanq_rEhs)
|
||||
@@ -159,22 +180,22 @@ Or to your profile (not preferred): `nix-env --install -A nixpkgs.kubescape`
|
||||
### 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
|
||||
|
||||
#### 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 +204,11 @@ kubescape scan framework mitre --submit
|
||||
kubescape scan control "Privileged container"
|
||||
```
|
||||
|
||||
#### Scan using an alternative kubeconfig file
|
||||
```
|
||||
kubescape scan --kubeconfig cluster.conf
|
||||
```
|
||||
|
||||
#### Scan specific namespaces
|
||||
```
|
||||
kubescape scan --include-namespaces development,staging,production
|
||||
@@ -193,14 +219,15 @@ kubescape scan --include-namespaces development,staging,production
|
||||
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 --submit
|
||||
kubescape scan https://github.com/kubescape/kubescape
|
||||
```
|
||||
|
||||
#### Display all scanned resources (including the resources which passed)
|
||||
@@ -233,7 +260,13 @@ kubescape scan --format pdf --output results.pdf
|
||||
kubescape scan --format prometheus
|
||||
```
|
||||
|
||||
#### Scan with exceptions, objects with exceptions will be presented as `exclude` and not `fail`
|
||||
#### 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`
|
||||
[Full documentation](examples/exceptions/README.md)
|
||||
```
|
||||
kubescape scan --exceptions examples/exceptions/exclude-kube-namespaces.json
|
||||
@@ -241,11 +274,17 @@ kubescape scan --exceptions examples/exceptions/exclude-kube-namespaces.json
|
||||
|
||||
#### Scan Helm charts
|
||||
```
|
||||
kubescape scan </path/to/directory> --submit
|
||||
kubescape scan </path/to/directory>
|
||||
```
|
||||
> Kubescape will load the default value file
|
||||
|
||||
### Offline/Air-gaped Environment Support
|
||||
#### Scan a Kustomize Directory
|
||||
```
|
||||
kubescape scan </path/to/directory>
|
||||
```
|
||||
> Kubescape will generate Kubernetes YAML objects using a 'Kustomize' file and scan them for security.
|
||||
|
||||
### Offline/Air-gapped Environment Support
|
||||
|
||||
[Video tutorial](https://youtu.be/IGXL9s37smM)
|
||||
|
||||
@@ -289,7 +328,7 @@ kubescape scan framework nsa --use-from /path/nsa.json
|
||||
|
||||
 
|
||||
|
||||
Scan the YAML files while writing them using the [vs code extension](https://github.com/armosec/vscode-kubescape/blob/master/README.md)
|
||||
Scan the YAML files while writing them using the [VS Code extension](https://github.com/armosec/vscode-kubescape/blob/master/README.md)
|
||||
|
||||
## Lens Extension
|
||||
|
||||
@@ -355,9 +394,31 @@ View Kubescape scan results directly in [Lens IDE](https://k8slens.dev/) using k
|
||||
|
||||
</details>
|
||||
|
||||
## VS code configuration samples
|
||||
## Build on pre-configured killercoda's ubuntu playground
|
||||
|
||||
You can use the sample files below to setup your VS code environment for building and debugging purposes.
|
||||
* [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 like a regular user. Instead of using `kubescape`, use `./kubescape`. Make sure you are in the Kubescape directory because the command will execute the binary named `kubescape` in `kubescape directory`)
|
||||
|
||||
</details>
|
||||
|
||||
## VS Code configuration samples
|
||||
|
||||
You can use the sample files below to setup your VS Code environment for building and debugging purposes.
|
||||
|
||||
|
||||
<details><summary>.vscode/settings.json</summary>
|
||||
@@ -404,11 +465,11 @@ You can use the sample files below to setup your VS code environment for buildin
|
||||
## Technology
|
||||
Kubescape is based on the [OPA engine](https://github.com/open-policy-agent/opa) and ARMO's posture controls.
|
||||
|
||||
The tools retrieve Kubernetes objects from the API server and run a set of [rego's snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io?utm_source=github&utm_medium=repository).
|
||||
The tools retrieve Kubernetes objects from the API server and runs a set of [Rego snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io?utm_source=github&utm_medium=repository).
|
||||
|
||||
The results by default are printed in a pretty "console friendly" manner, but they can be retrieved in JSON format for further processing.
|
||||
The results by default are printed in a "console friendly" manner, but they can be retrieved in JSON format for further processing.
|
||||
|
||||
Kubescape is an open source project, we welcome your feedback and ideas for improvement. We’re also aiming to collaborate with the Kubernetes community to help make the tests more robust and complete as Kubernetes develops.
|
||||
Kubescape is an open source project, we welcome your feedback and ideas for improvement. We are part of the Kubernetes community and aim to make the tests more robust and complete as Kubernetes develops.
|
||||
|
||||
## Thanks to all the contributors ❤️
|
||||
<a href = "https://github.com/kubescape/kubescape/graphs/contributors">
|
||||
|
||||
13
build.py
13
build.py
@@ -14,14 +14,15 @@ def check_status(status, msg):
|
||||
|
||||
def get_build_dir():
|
||||
current_platform = platform.system()
|
||||
build_dir = "./build/"
|
||||
build_dir = ""
|
||||
|
||||
if current_platform == "Windows": build_dir += "windows-latest"
|
||||
elif current_platform == "Linux": build_dir += "ubuntu-latest"
|
||||
elif current_platform == "Darwin": build_dir += "macos-latest"
|
||||
if current_platform == "Windows": build_dir = "windows-latest"
|
||||
elif current_platform == "Linux": build_dir = "ubuntu-latest"
|
||||
elif current_platform == "Darwin": build_dir = "macos-latest"
|
||||
else: raise OSError("Platform %s is not supported!" % (current_platform))
|
||||
|
||||
return build_dir
|
||||
return os.path.join("build", build_dir)
|
||||
|
||||
|
||||
def get_package_name():
|
||||
package_name = "kubescape"
|
||||
@@ -56,7 +57,7 @@ def main():
|
||||
if client_name:
|
||||
ldflags += " -X {}={}".format(client_var, client_name)
|
||||
|
||||
build_command = ["go", "build", "-tags=static", "-o", ks_file, "-ldflags" ,ldflags]
|
||||
build_command = ["go", "build", "-buildmode=pie", "-tags=static", "-o", ks_file, "-ldflags" ,ldflags]
|
||||
|
||||
print("Building kubescape and saving here: {}".format(ks_file))
|
||||
print("Build command: {}".format(" ".join(build_command)))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.18-alpine as builder
|
||||
FROM golang:1.19-alpine as builder
|
||||
|
||||
ARG image_version
|
||||
ARG client
|
||||
@@ -12,7 +12,7 @@ ENV CGO_ENABLED=1
|
||||
|
||||
# Install required python/pip
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
RUN apk add --update --no-cache python3 git openssl-dev musl-dev gcc make cmake pkgconfig && ln -sf python3 /usr/bin/python
|
||||
RUN apk add --update --no-cache python3 gcc make git libc-dev binutils-gold cmake pkgconfig && ln -sf python3 /usr/bin/python
|
||||
RUN python3 -m ensurepip
|
||||
RUN pip3 install --no-cache --upgrade pip setuptools
|
||||
|
||||
@@ -35,15 +35,15 @@ RUN /work/build/ubuntu-latest/kubescape download artifacts -o /work/artifacts
|
||||
|
||||
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/build/ubuntu-latest/kubescape /usr/bin/kubescape
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
|
||||
var completionCmdExamples = `
|
||||
|
||||
# Enable BASH shell autocompletion
|
||||
$ source <(kubescape completion bash)
|
||||
# Enable BASH shell autocompletion
|
||||
$ source <(kubescape completion bash)
|
||||
$ echo 'source <(kubescape completion bash)' >> ~/.bashrc
|
||||
|
||||
# Enable ZSH shell autocompletion
|
||||
# Enable ZSH shell autocompletion
|
||||
$ source <(kubectl completion zsh)
|
||||
$ echo 'source <(kubectl completion zsh)' >> "${fpath[1]}/_kubectl"
|
||||
|
||||
@@ -27,7 +27,7 @@ func GetCompletionCmd() *cobra.Command {
|
||||
Example: completionCmdExamples,
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.ExactValidArgs(1),
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "bash":
|
||||
|
||||
@@ -25,6 +25,9 @@ var (
|
||||
|
||||
# Set 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){
|
||||
"accountID": func(s *metav1.SetConfig, account string) { s.Account = account },
|
||||
"clientID": func(s *metav1.SetConfig, clientID string) { s.ClientID = clientID },
|
||||
"secretKey": func(s *metav1.SetConfig, secretKey string) { s.SecretKey = secretKey },
|
||||
"accountID": func(s *metav1.SetConfig, account string) { s.Account = account },
|
||||
"clientID": func(s *metav1.SetConfig, clientID string) { s.ClientID = clientID },
|
||||
"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 {
|
||||
|
||||
@@ -22,6 +22,11 @@ func getExceptionsCmd(ks meta.IKubescape, deleteInfo *v1.Delete) *cobra.Command
|
||||
return nil
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
if err := flagValidationDelete(deleteInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
exceptionsNames := strings.Split(args[0], ";")
|
||||
if len(exceptionsNames) == 0 {
|
||||
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,15 +17,15 @@ var (
|
||||
downloadExample = `
|
||||
# Download all artifacts and save them in the default path (~/.kubescape)
|
||||
kubescape download artifacts
|
||||
download
|
||||
|
||||
# Download all artifacts and save them in /tmp path
|
||||
kubescape download artifacts --output /tmp
|
||||
|
||||
# Download the NSA framework. Run 'kubescape list frameworks' for all frameworks names
|
||||
kubescape download framework nsa
|
||||
|
||||
# Download the "Allowed hostPath" control. Run 'kubescape list controls' for all controls names
|
||||
kubescape download control "Allowed hostPath"
|
||||
# Download the "C-0001" control. Run 'kubescape list controls --id' for all controls ids
|
||||
kubescape download control "C-0001"
|
||||
|
||||
# Download the "C-0001" control. Run 'kubescape list controls --id' for all controls ids
|
||||
kubescape download control C-0001
|
||||
@@ -36,6 +36,8 @@ var (
|
||||
# Download the configured controls-inputs
|
||||
kubescape download controls-inputs
|
||||
|
||||
# Download the attack tracks
|
||||
kubescape download attack-tracks
|
||||
`
|
||||
)
|
||||
|
||||
@@ -59,12 +61,18 @@ func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationDownload(&downloadInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if filepath.Ext(downloadInfo.Path) == ".json" {
|
||||
downloadInfo.Path, downloadInfo.FileName = filepath.Split(downloadInfo.Path)
|
||||
}
|
||||
downloadInfo.Target = args[0]
|
||||
if len(args) >= 2 {
|
||||
downloadInfo.Name = args[1]
|
||||
|
||||
downloadInfo.Identifier = args[1]
|
||||
|
||||
}
|
||||
if err := ks.Download(&downloadInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
@@ -80,3 +88,10 @@ func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
return downloadCmd
|
||||
}
|
||||
|
||||
// Check if the flag entered are valid
|
||||
func flagValidationDownload(downloadInfo *v1.DownloadInfo) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return downloadInfo.Credentials.Validate()
|
||||
}
|
||||
|
||||
@@ -20,11 +20,8 @@ var (
|
||||
# List all supported frameworks names
|
||||
kubescape list frameworks --account <account id>
|
||||
|
||||
# List all supported controls names
|
||||
# List all supported controls names with ids
|
||||
kubescape list controls
|
||||
|
||||
# List all supported controls ids
|
||||
kubescape list controls --id
|
||||
|
||||
Control documentation:
|
||||
https://hub.armosec.io/docs/controls
|
||||
@@ -51,6 +48,11 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationList(&listPolicies); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listPolicies.Target = args[0]
|
||||
|
||||
if err := ks.List(&listPolicies); err != nil {
|
||||
@@ -62,8 +64,15 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-printer'/'json'")
|
||||
listCmd.PersistentFlags().BoolVarP(&listPolicies.ListIDs, "id", "", false, "List control ID's instead of controls names")
|
||||
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-print'/'json'")
|
||||
listCmd.PersistentFlags().MarkDeprecated("id", "Control ID's are included in list outpus")
|
||||
|
||||
return listCmd
|
||||
}
|
||||
|
||||
// Check if the flag entered are valid
|
||||
func flagValidationList(listPolicies *v1.ListPolicies) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return listPolicies.Credentials.Validate()
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/kubescape/kubescape/v2/cmd/list"
|
||||
"github.com/kubescape/kubescape/v2/cmd/scan"
|
||||
"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/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
@@ -26,7 +27,7 @@ var rootInfo cautils.RootInfo
|
||||
|
||||
var ksExamples = `
|
||||
# Scan command
|
||||
kubescape scan --submit
|
||||
kubescape scan
|
||||
|
||||
# List supported frameworks
|
||||
kubescape list frameworks
|
||||
@@ -76,6 +77,7 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
rootCmd.AddCommand(completion.GetCompletionCmd())
|
||||
rootCmd.AddCommand(version.GetVersionCmd())
|
||||
rootCmd.AddCommand(config.GetConfigCmd(ks))
|
||||
rootCmd.AddCommand(update.GetUpdateCmd())
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ var (
|
||||
kubescape scan control "privileged container"
|
||||
|
||||
# Scan list of controls separated with a comma
|
||||
kubescape scan control "privileged container","allowed hostpath"
|
||||
kubescape scan control "privileged container","HostPath mount"
|
||||
|
||||
# Scan list of controls using the control ID separated with a comma
|
||||
kubescape scan control C-0058,C-0057
|
||||
@@ -58,6 +58,10 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := validateFrameworkScanInfo(scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// flagValidationControl(scanInfo)
|
||||
scanInfo.PolicyIdentifier = []cautils.PolicyIdentifier{}
|
||||
|
||||
@@ -88,6 +92,10 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
||||
|
||||
scanInfo.FrameworkScan = false
|
||||
|
||||
if err := validateControlScanInfo(scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
results, err := ks.Scan(scanInfo)
|
||||
if err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
@@ -101,7 +109,23 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
||||
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
|
||||
logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold)))
|
||||
}
|
||||
enforceSeverityThresholds(results.GetResults().SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// validateControlScanInfo validates the ScanInfo struct for the `control` command
|
||||
func validateControlScanInfo(scanInfo *cautils.ScanInfo) error {
|
||||
severity := scanInfo.FailThresholdSeverity
|
||||
|
||||
if scanInfo.Submit && scanInfo.OmitRawResources {
|
||||
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
|
||||
}
|
||||
|
||||
if err := validateSeverity(severity); severity != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
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"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
|
||||
"github.com/enescakir/emoji"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
frameworkExample = `
|
||||
# Scan all frameworks and submit the results
|
||||
kubescape scan framework all --submit
|
||||
# Scan all frameworks
|
||||
kubescape scan framework all
|
||||
|
||||
# Scan the NSA framework
|
||||
kubescape scan framework nsa
|
||||
@@ -32,10 +34,12 @@ var (
|
||||
kubescape scan framework all
|
||||
|
||||
# Scan kubernetes YAML manifest files (single file or glob)
|
||||
kubescape scan framework nsa *.yaml
|
||||
kubescape scan framework nsa .
|
||||
|
||||
Run 'kubescape list frameworks' for the list of supported frameworks
|
||||
`
|
||||
|
||||
ErrUnknownSeverity = errors.New("unknown severity")
|
||||
)
|
||||
|
||||
func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
|
||||
@@ -62,7 +66,7 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationFramework(scanInfo); err != nil {
|
||||
if err := validateFrameworkScanInfo(scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
scanInfo.FrameworkScan = true
|
||||
@@ -108,22 +112,102 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
if !scanInfo.VerboseMode {
|
||||
cautils.SimpleDisplay(os.Stderr, "%s Run with '--verbose'/'-v' flag for detailed resources view\n\n", emoji.Detective)
|
||||
cautils.SimpleDisplay(os.Stderr, "Run with '--verbose'/'-v' flag for detailed resources view\n\n")
|
||||
}
|
||||
if results.GetRiskScore() > float32(scanInfo.FailThreshold) {
|
||||
logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold)))
|
||||
}
|
||||
|
||||
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
|
||||
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.NumberOfLowSeverity},
|
||||
{reporthandlingapis.SeverityMediumString, severityCounters.NumberOfMediumSeverity},
|
||||
{reporthandlingapis.SeverityHighString, severityCounters.NumberOfHighSeverity},
|
||||
{reporthandlingapis.SeverityCriticalString, severityCounters.NumberOfCriticalSeverity},
|
||||
}
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("you can use `keep-local` or `submit`, but not both")
|
||||
}
|
||||
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
|
||||
return fmt.Errorf("bad argument: out of range threshold")
|
||||
}
|
||||
return nil
|
||||
if scanInfo.Submit && scanInfo.OmitRawResources {
|
||||
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
|
||||
}
|
||||
severity := scanInfo.FailThresholdSeverity
|
||||
if err := validateSeverity(severity); severity != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate the user's credentials
|
||||
return scanInfo.Credentials.Validate()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
@@ -10,16 +11,16 @@ import (
|
||||
)
|
||||
|
||||
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
|
||||
kubescape scan --submit --enable-host-scan --verbose
|
||||
kubescape scan --enable-host-scan --verbose
|
||||
|
||||
# Scan kubernetes YAML manifest files
|
||||
kubescape scan *.yaml
|
||||
kubescape scan .
|
||||
|
||||
# Scan and save the results in the JSON format
|
||||
kubescape scan --format json --output results.json
|
||||
kubescape scan --format json --output results.json --format-version=v2
|
||||
|
||||
# Display all resources
|
||||
kubescape scan --verbose
|
||||
@@ -57,6 +58,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
},
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
|
||||
|
||||
},
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
// TODO - revert context
|
||||
@@ -64,36 +66,44 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
}
|
||||
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
// scanCmd.PersistentFlags().BoolVar(&scanInfo.CreateAccount, "create-account", false, "Create a Kubescape SaaS account ID account ID is not found in cache. After creating the account, the account ID will be saved in cache. In addition, the scanning results will be uploaded to the Kubescape SaaS")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
|
||||
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.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().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", "", `Output file 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().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().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().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().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.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")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.OmitRawResources, "omit-raw-resources", "", false, "Omit raw resources from the output. By default the raw resources are included in the output")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.PrintAttackTree, "print-attack-tree", "", false, "Print attack tree")
|
||||
|
||||
// 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")
|
||||
|
||||
// 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("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
|
||||
scanCmd.PersistentFlags().MarkHidden("omit-raw-resources")
|
||||
scanCmd.PersistentFlags().MarkHidden("print-attack-tree")
|
||||
|
||||
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.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{CriticalSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Critical failed resource should exceed Critical threshold set as constant",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: apis.SeverityCriticalString},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "High failed resource should not exceed Critical threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "critical"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
|
||||
Want: false,
|
||||
},
|
||||
{
|
||||
Description: "Critical failed resource exceeds High threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "High failed resource exceeds High threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Medium failed resource does not exceed High threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "high"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
|
||||
Want: false,
|
||||
},
|
||||
{
|
||||
Description: "Critical failed resource exceeds Medium threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "High failed resource exceeds Medium threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Medium failed resource exceeds Medium threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Low failed resource does not exceed Medium threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "medium"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
|
||||
Want: false,
|
||||
},
|
||||
{
|
||||
Description: "Critical failed resource exceeds Low threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{CriticalSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "High failed resource exceeds Low threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{HighSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Medium failed resource exceeds Low threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{MediumSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Low failed resource exceeds Low threshold",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "low"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 1},
|
||||
Want: true,
|
||||
},
|
||||
{
|
||||
Description: "Unknown severity returns an error",
|
||||
ScanInfo: &cautils.ScanInfo{FailThresholdSeverity: "unknown"},
|
||||
SeverityCounters: &reportsummary.SeverityCounters{LowSeverityCounter: 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{CriticalSeverityCounter: 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
|
||||
},
|
||||
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 {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
@@ -31,16 +31,21 @@ var (
|
||||
// getRBACCmd represents the RBAC command
|
||||
func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "rbac",
|
||||
Example: rbacExamples,
|
||||
Short: "Submit cluster's Role-Based Access Control(RBAC)",
|
||||
Long: ``,
|
||||
Use: "rbac",
|
||||
Deprecated: "This command is deprecated and will not be supported after 1/Jan/2023. Please use the 'scan' command instead.",
|
||||
Example: rbacExamples,
|
||||
Short: "Submit cluster's Role-Based Access Control(RBAC)",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", k8s)
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
@@ -77,9 +82,16 @@ func getKubernetesApi() *k8sinterface.KubernetesApi {
|
||||
}
|
||||
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 {
|
||||
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",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("missing results file")
|
||||
}
|
||||
@@ -61,7 +66,7 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
k8s := getKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", k8s)
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
@@ -7,16 +7,21 @@ import (
|
||||
)
|
||||
|
||||
var submitCmdExamples = `
|
||||
# Submit Kubescape scan results file
|
||||
kubescape submit results
|
||||
|
||||
# Submit exceptions file to Kubescape SaaS
|
||||
kubescape submit exceptions
|
||||
`
|
||||
|
||||
func GetSubmitCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var submitInfo metav1.Submit
|
||||
|
||||
submitCmd := &cobra.Command{
|
||||
Use: "submit <command>",
|
||||
Short: "Submit an object to the Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Use: "submit <command>",
|
||||
Short: "Submit an object to the Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Example: submitCmdExamples,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
12
core/cautils/controllink.go
Normal file
12
core/cautils/controllink.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetControlLink(controlID string) string {
|
||||
// For CIS Controls, cis-1.1.3 will be transformed to cis-1-1-3 in documentation link.
|
||||
docLinkID := strings.ReplaceAll(controlID, ".", "-")
|
||||
return fmt.Sprintf("https://hub.armosec.io/docs/%s", strings.ToLower(docLinkID))
|
||||
}
|
||||
@@ -5,11 +5,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -31,6 +33,10 @@ type ConfigObj struct {
|
||||
Token string `json:"invitationParam,omitempty"`
|
||||
CustomerAdminEMail string `json:"adminMail,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
|
||||
@@ -74,6 +80,10 @@ type ITenantConfig interface {
|
||||
GetClientID() string
|
||||
GetSecretKey() string
|
||||
GetConfigObj() *ConfigObj
|
||||
GetCloudReportURL() string
|
||||
GetCloudAPIURL() string
|
||||
GetCloudUIURL() string
|
||||
GetCloudAuthURL() string
|
||||
// GetBackendAPI() getter.IBackend
|
||||
// GenerateURL()
|
||||
|
||||
@@ -90,7 +100,7 @@ type LocalConfig struct {
|
||||
}
|
||||
|
||||
func NewLocalConfig(
|
||||
backendAPI getter.IBackend, credentials *Credentials, clusterName string) *LocalConfig {
|
||||
backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *LocalConfig {
|
||||
|
||||
lc := &LocalConfig{
|
||||
backendAPI: backendAPI,
|
||||
@@ -102,26 +112,55 @@ func NewLocalConfig(
|
||||
}
|
||||
|
||||
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.backendAPI.SetAccountID(lc.configObj.AccountID)
|
||||
lc.backendAPI.SetClientID(lc.configObj.ClientID)
|
||||
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
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
|
||||
func (lc *LocalConfig) GetTenantEmail() string { return lc.configObj.CustomerAdminEMail }
|
||||
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
|
||||
func (lc *LocalConfig) GetClientID() string { return lc.configObj.ClientID }
|
||||
func (lc *LocalConfig) GetSecretKey() string { return lc.configObj.SecretKey }
|
||||
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
|
||||
func (lc *LocalConfig) GetToken() string { return lc.configObj.Token }
|
||||
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
|
||||
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
|
||||
func (lc *LocalConfig) GetTenantEmail() string { return lc.configObj.CustomerAdminEMail }
|
||||
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
|
||||
func (lc *LocalConfig) GetClientID() string { return lc.configObj.ClientID }
|
||||
func (lc *LocalConfig) GetSecretKey() string { return lc.configObj.SecretKey }
|
||||
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
|
||||
func (lc *LocalConfig) GetToken() string { return lc.configObj.Token }
|
||||
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 {
|
||||
|
||||
// Kubescape Cloud tenant GUID
|
||||
@@ -178,7 +217,7 @@ KS_ACCOUNT_ID
|
||||
KS_CLIENT_ID
|
||||
KS_SECRET_KEY
|
||||
|
||||
TODO - supprot:
|
||||
TODO - support:
|
||||
KS_CACHE // path to cached files
|
||||
*/
|
||||
type ClusterConfig struct {
|
||||
@@ -189,7 +228,7 @@ type ClusterConfig struct {
|
||||
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
|
||||
c := &ClusterConfig{
|
||||
k8s: k8s,
|
||||
@@ -209,8 +248,12 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
|
||||
loadConfigFromFile(c.configObj)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -223,18 +266,44 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
|
||||
c.backendAPI.SetAccountID(c.configObj.AccountID)
|
||||
c.backendAPI.SetClientID(c.configObj.ClientID)
|
||||
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
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
|
||||
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
|
||||
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
|
||||
func (c *ClusterConfig) GetClientID() string { return c.configObj.ClientID }
|
||||
func (c *ClusterConfig) GetSecretKey() string { return c.configObj.SecretKey }
|
||||
func (c *ClusterConfig) GetTenantEmail() string { return c.configObj.CustomerAdminEMail }
|
||||
func (c *ClusterConfig) GetToken() string { return c.configObj.Token }
|
||||
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
|
||||
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
|
||||
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
|
||||
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
|
||||
func (c *ClusterConfig) GetClientID() string { return c.configObj.ClientID }
|
||||
func (c *ClusterConfig) GetSecretKey() string { return c.configObj.SecretKey }
|
||||
func (c *ClusterConfig) GetTenantEmail() string { return c.configObj.CustomerAdminEMail }
|
||||
func (c *ClusterConfig) GetToken() string { return c.configObj.Token }
|
||||
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 {
|
||||
|
||||
@@ -401,10 +470,7 @@ func (c *ClusterConfig) updateConfigMap() error {
|
||||
}
|
||||
|
||||
func updateConfigFile(configObj *ConfigObj) error {
|
||||
if err := os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664) //nolint:gosec
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
|
||||
@@ -468,7 +534,11 @@ func DeleteConfigFile() error {
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -516,3 +586,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 (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -16,6 +17,10 @@ func mockConfigObj() *ConfigObj {
|
||||
ClusterName: "ddd",
|
||||
CustomerAdminEMail: "ab@cd",
|
||||
Token: "eee",
|
||||
CloudReportURL: "report.armo.cloud",
|
||||
CloudAPIURL: "api.armosec.io",
|
||||
CloudUIURL: "cloud.armosec.io",
|
||||
CloudAuthURL: "auth.armosec.io",
|
||||
}
|
||||
}
|
||||
func mockLocalConfig() *LocalConfig {
|
||||
@@ -39,6 +44,10 @@ func TestConfig(t *testing.T) {
|
||||
assert.Equal(t, co.AccountID, cop.AccountID)
|
||||
assert.Equal(t, co.ClientID, cop.ClientID)
|
||||
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.CustomerAdminEMail) // 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.CustomerAdminEMail, lc.GetTenantEmail())
|
||||
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
|
||||
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.CustomerAdminEMail, c.GetTenantEmail())
|
||||
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) {
|
||||
@@ -80,6 +97,10 @@ func TestUpdateConfigData(t *testing.T) {
|
||||
assert.Equal(t, c.GetAccountID(), configMap.Data["accountID"])
|
||||
assert.Equal(t, c.GetClientID(), configMap.Data["clientID"])
|
||||
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) {
|
||||
@@ -97,6 +118,10 @@ func TestReadConfig(t *testing.T) {
|
||||
assert.Equal(t, com.ClusterName, co.ClusterName)
|
||||
assert.Equal(t, com.CustomerAdminEMail, co.CustomerAdminEMail)
|
||||
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) {
|
||||
@@ -120,6 +145,10 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
assert.Equal(t, c.GetContextName(), co.ClusterName)
|
||||
assert.Equal(t, c.GetTenantEmail(), co.CustomerAdminEMail)
|
||||
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
|
||||
@@ -139,6 +168,10 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
assert.Equal(t, c.GetAccountID(), co.AccountID)
|
||||
assert.Equal(t, c.GetClientID(), co.ClientID)
|
||||
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
|
||||
@@ -151,10 +184,12 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
// add to map
|
||||
configMap.Data["clientID"] = c.configObj.ClientID
|
||||
configMap.Data["secretKey"] = c.configObj.SecretKey
|
||||
configMap.Data["cloudReportURL"] = c.configObj.CloudReportURL
|
||||
|
||||
// delete the content
|
||||
c.configObj.ClientID = ""
|
||||
c.configObj.SecretKey = ""
|
||||
c.configObj.CloudReportURL = ""
|
||||
|
||||
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
|
||||
loadConfigFromData(c.configObj, configMap.Data)
|
||||
@@ -162,6 +197,7 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
assert.NotEmpty(t, c.GetAccountID())
|
||||
assert.NotEmpty(t, c.GetClientID())
|
||||
assert.NotEmpty(t, c.GetSecretKey())
|
||||
assert.NotEmpty(t, c.GetCloudReportURL())
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
apis "github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/prioritization"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
@@ -17,18 +18,22 @@ type KSResources map[string][]string
|
||||
type OPASessionObj struct {
|
||||
K8SResources *K8SResources // input k8s objects
|
||||
ArmoResource *KSResources // input ARMO objects
|
||||
Policies []reporthandling.Framework // list of frameworks to scan
|
||||
AllPolicies *Policies // list of all frameworks
|
||||
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<resource ID>]<resource>
|
||||
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<resource ID>]<resource result>
|
||||
ResourceSource map[string]reporthandling.Source // resources sources, map[<resource ID>]<resource result>
|
||||
ResourcesPrioritized map[string]prioritization.PrioritizedResource // resources prioritization information, map[<resource ID>]<prioritized resource>
|
||||
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
|
||||
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
|
||||
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
|
||||
ResourceAttackTracks map[string]v1alpha1.IAttackTrack // resources attack tracks, map[<resource ID>]<attack track>
|
||||
AttackTracks map[string]v1alpha1.IAttackTrack
|
||||
Report *reporthandlingv2.PostureReport // scan results v2 - Remove
|
||||
RegoInputData RegoInputData // input passed to rego for scanning. map[<control name>][<input arguments>]
|
||||
Metadata *reporthandlingv2.Metadata
|
||||
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
|
||||
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
|
||||
SessionID string // SessionID
|
||||
InfoMap map[string]apis.StatusInfo // Map errors of resources to StatusInfo
|
||||
ResourceToControlsMap map[string][]string // map[<apigroup/apiversion/resource>] = [<control_IDs>]
|
||||
SessionID string // SessionID
|
||||
Policies []reporthandling.Framework // list of frameworks to scan
|
||||
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
|
||||
OmitRawResources bool // omit raw resources from output
|
||||
}
|
||||
|
||||
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources, scanInfo *ScanInfo) *OPASessionObj {
|
||||
@@ -44,6 +49,7 @@ func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SRe
|
||||
ResourceSource: make(map[string]reporthandling.Source),
|
||||
SessionID: scanInfo.ScanID,
|
||||
Metadata: scanInfoToScanMetadata(scanInfo),
|
||||
OmitRawResources: scanInfo.OmitRawResources,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +99,7 @@ type Exception struct {
|
||||
|
||||
type RegoInputData struct {
|
||||
PostureControlInputs map[string][]string `json:"postureControlInputs"`
|
||||
DataControlInputs map[string]string `json:"dataControlInputs"`
|
||||
// ClusterName string `json:"clusterName"`
|
||||
// K8sConfig RegoK8sConfig `json:"k8sconfig"`
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes"
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/localworkload"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -30,7 +30,7 @@ const (
|
||||
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) {
|
||||
directories, _ := listDirs(basePath)
|
||||
helmDirectories := make([]string, 0)
|
||||
@@ -61,6 +61,41 @@ func LoadResourcesFromHelmCharts(basePath string) (map[string][]workloadinterfac
|
||||
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 {
|
||||
files, errs := listFiles(input)
|
||||
if len(errs) > 0 {
|
||||
@@ -104,9 +139,9 @@ func loadFiles(rootPath string, filePaths []string) (map[string][]workloadinterf
|
||||
for j := range w {
|
||||
lw := localworkload.NewLocalWorkload(w[j].GetObject())
|
||||
if relPath, err := filepath.Rel(rootPath, path); err == nil {
|
||||
lw.SetPath(relPath)
|
||||
lw.SetPath(fmt.Sprintf("%s:%d", relPath, j))
|
||||
} else {
|
||||
lw.SetPath(path)
|
||||
lw.SetPath(fmt.Sprintf("%s:%d", path, j))
|
||||
}
|
||||
wSlice = append(wSlice, lw)
|
||||
}
|
||||
@@ -262,7 +297,7 @@ func glob(root, pattern string, onlyDirectories bool) ([]string, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// listing only directotries
|
||||
// listing only directories
|
||||
if onlyDirectories {
|
||||
if info.IsDir() {
|
||||
if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/opa-utils/gitregostore"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
@@ -22,11 +25,11 @@ func NewDownloadReleasedPolicy() *DownloadReleasedPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetControl(policyName string) (*reporthandling.Control, error) {
|
||||
func (drp *DownloadReleasedPolicy) GetControl(ID string) (*reporthandling.Control, error) {
|
||||
var control *reporthandling.Control
|
||||
var err error
|
||||
|
||||
control, err = drp.gs.GetOPAControl(policyName)
|
||||
control, err = drp.gs.GetOPAControlByID(ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -53,13 +56,29 @@ func (drp *DownloadReleasedPolicy) ListFrameworks() ([]string, error) {
|
||||
return drp.gs.GetOPAFrameworksNamesList()
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) ListControls(listType ListType) ([]string, error) {
|
||||
switch listType {
|
||||
case ListID:
|
||||
return drp.gs.GetOPAControlsIDsList()
|
||||
default:
|
||||
return drp.gs.GetOPAControlsNamesList()
|
||||
func (drp *DownloadReleasedPolicy) ListControls() ([]string, error) {
|
||||
controlsIDsList, err := drp.gs.GetOPAControlsIDsList()
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
controlsNamesList, err := drp.gs.GetOPAControlsNamesList()
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
controls, err := drp.gs.GetOPAControls()
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
var controlsFrameworksList [][]string
|
||||
for _, control := range controls {
|
||||
controlsFrameworksList = append(controlsFrameworksList, control.FrameworkNames)
|
||||
}
|
||||
controlsNamesWithIDsandFrameworksList := make([]string, len(controlsIDsList))
|
||||
// by design all slices have the same lengt
|
||||
for i := range controlsIDsList {
|
||||
controlsNamesWithIDsandFrameworksList[i] = fmt.Sprintf("%v|%v|%v", controlsIDsList[i], controlsNamesList[i], strings.Join(controlsFrameworksList[i], ","))
|
||||
}
|
||||
return controlsNamesWithIDsandFrameworksList, nil
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
@@ -70,6 +89,14 @@ func (drp *DownloadReleasedPolicy) GetControlsInputs(clusterName string) (map[st
|
||||
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 {
|
||||
fwNames, err := drp.gs.GetOPAFrameworksNamesList()
|
||||
if len(fwNames) != 0 && err == nil {
|
||||
@@ -90,3 +117,11 @@ func contains(s []string, str string) bool {
|
||||
}
|
||||
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,21 +3,16 @@ package getter
|
||||
import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
)
|
||||
|
||||
// supported listing
|
||||
type ListType string
|
||||
|
||||
const ListID ListType = "id"
|
||||
const ListName ListType = "name"
|
||||
|
||||
type IPolicyGetter interface {
|
||||
GetFramework(name string) (*reporthandling.Framework, error)
|
||||
GetFrameworks() ([]reporthandling.Framework, error)
|
||||
GetControl(name string) (*reporthandling.Control, error)
|
||||
GetControl(ID string) (*reporthandling.Control, error)
|
||||
|
||||
ListFrameworks() ([]string, error)
|
||||
ListControls(ListType) ([]string, error)
|
||||
ListControls() ([]string, error)
|
||||
}
|
||||
|
||||
type IExceptionsGetter interface {
|
||||
@@ -27,10 +22,18 @@ type IBackend interface {
|
||||
GetAccountID() string
|
||||
GetClientID() string
|
||||
GetSecretKey() string
|
||||
GetCloudReportURL() string
|
||||
GetCloudAPIURL() string
|
||||
GetCloudUIURL() string
|
||||
GetCloudAuthURL() string
|
||||
|
||||
SetAccountID(accountID string)
|
||||
SetClientID(clientID string)
|
||||
SetSecretKey(secretKey string)
|
||||
SetCloudReportURL(cloudReportURL string)
|
||||
SetCloudAPIURL(cloudAPIURL string)
|
||||
SetCloudUIURL(cloudUIURL string)
|
||||
SetCloudAuthURL(cloudAuthURL string)
|
||||
|
||||
GetTenant() (*TenantResponse, error)
|
||||
}
|
||||
@@ -38,3 +41,7 @@ type IBackend interface {
|
||||
type IControlsInputsGetter interface {
|
||||
GetControlsInputs(clusterName string) (map[string][]string, error)
|
||||
}
|
||||
|
||||
type IAttackTracksGetter interface {
|
||||
GetAttackTracks() ([]v1alpha1.AttackTrack, error)
|
||||
}
|
||||
|
||||
@@ -21,18 +21,19 @@ func SaveInFile(policy interface{}, pathStr string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
|
||||
err = os.WriteFile(pathStr, encodedData, 0644) //nolint:gosec
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
pathDir := path.Dir(pathStr)
|
||||
if err := os.Mkdir(pathDir, 0744); err != nil {
|
||||
// pathDir could contain subdirectories
|
||||
if err := os.MkdirAll(pathDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
|
||||
}
|
||||
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
|
||||
err = os.WriteFile(pathStr, encodedData, 0644) //nolint:gosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,15 +4,14 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/attacktrack/v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -34,23 +33,22 @@ var (
|
||||
|
||||
// KSCloudAPI allows accessing the API of the Kubescape Cloud offering
|
||||
type KSCloudAPI struct {
|
||||
httpClient *http.Client
|
||||
apiURL string
|
||||
authURL string
|
||||
erURL string
|
||||
feURL string
|
||||
accountID string
|
||||
clientID string
|
||||
secretKey string
|
||||
authCookie string
|
||||
feToken FeLoginResponse
|
||||
loggedIn bool
|
||||
httpClient *http.Client
|
||||
cloudAPIURL string
|
||||
cloudAuthURL string
|
||||
cloudReportURL string
|
||||
cloudUIURL string
|
||||
accountID string
|
||||
clientID string
|
||||
secretKey string
|
||||
authCookie string
|
||||
feToken FeLoginResponse
|
||||
loggedIn bool
|
||||
}
|
||||
|
||||
var globalKSCloudAPIConnector *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
|
||||
}
|
||||
|
||||
@@ -64,10 +62,10 @@ func GetKSCloudAPIConnector() *KSCloudAPI {
|
||||
func NewKSCloudAPIDev() *KSCloudAPI {
|
||||
apiObj := newKSCloudAPI()
|
||||
|
||||
apiObj.apiURL = ksCloudDevBEURL
|
||||
apiObj.authURL = ksCloudDevAUTHURL
|
||||
apiObj.erURL = ksCloudDevERURL
|
||||
apiObj.feURL = ksCloudDevFEURL
|
||||
apiObj.cloudAPIURL = ksCloudDevBEURL
|
||||
apiObj.cloudAuthURL = ksCloudDevAUTHURL
|
||||
apiObj.cloudReportURL = ksCloudDevERURL
|
||||
apiObj.cloudUIURL = ksCloudDevFEURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
@@ -75,10 +73,10 @@ func NewKSCloudAPIDev() *KSCloudAPI {
|
||||
func NewKSCloudAPIProd() *KSCloudAPI {
|
||||
apiObj := newKSCloudAPI()
|
||||
|
||||
apiObj.apiURL = ksCloudBEURL
|
||||
apiObj.erURL = ksCloudERURL
|
||||
apiObj.feURL = ksCloudFEURL
|
||||
apiObj.authURL = ksCloudAUTHURL
|
||||
apiObj.cloudAPIURL = ksCloudBEURL
|
||||
apiObj.cloudReportURL = ksCloudERURL
|
||||
apiObj.cloudUIURL = ksCloudFEURL
|
||||
apiObj.cloudAuthURL = ksCloudAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
@@ -86,10 +84,10 @@ func NewKSCloudAPIProd() *KSCloudAPI {
|
||||
func NewKSCloudAPIStaging() *KSCloudAPI {
|
||||
apiObj := newKSCloudAPI()
|
||||
|
||||
apiObj.apiURL = ksCloudStageBEURL
|
||||
apiObj.erURL = ksCloudStageERURL
|
||||
apiObj.feURL = ksCloudStageFEURL
|
||||
apiObj.authURL = ksCloudStageAUTHURL
|
||||
apiObj.cloudAPIURL = ksCloudStageBEURL
|
||||
apiObj.cloudReportURL = ksCloudStageERURL
|
||||
apiObj.cloudUIURL = ksCloudStageFEURL
|
||||
apiObj.cloudAuthURL = ksCloudStageAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
@@ -97,10 +95,10 @@ func NewKSCloudAPIStaging() *KSCloudAPI {
|
||||
func NewKSCloudAPICustomized(ksCloudERURL, ksCloudBEURL, ksCloudFEURL, ksCloudAUTHURL string) *KSCloudAPI {
|
||||
apiObj := newKSCloudAPI()
|
||||
|
||||
apiObj.erURL = ksCloudERURL
|
||||
apiObj.apiURL = ksCloudBEURL
|
||||
apiObj.feURL = ksCloudFEURL
|
||||
apiObj.authURL = ksCloudAUTHURL
|
||||
apiObj.cloudReportURL = ksCloudERURL
|
||||
apiObj.cloudAPIURL = ksCloudBEURL
|
||||
apiObj.cloudUIURL = ksCloudFEURL
|
||||
apiObj.cloudAuthURL = ksCloudAUTHURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
@@ -135,17 +133,36 @@ func (api *KSCloudAPI) Get(fullURL string, headers map[string]string) (string, e
|
||||
return HttpGetter(api.httpClient, fullURL, headers)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) GetAccountID() string { return api.accountID }
|
||||
func (api *KSCloudAPI) IsLoggedIn() bool { return api.loggedIn }
|
||||
func (api *KSCloudAPI) GetClientID() string { return api.clientID }
|
||||
func (api *KSCloudAPI) GetSecretKey() string { return api.secretKey }
|
||||
func (api *KSCloudAPI) GetFrontendURL() string { return api.feURL }
|
||||
func (api *KSCloudAPI) GetApiURL() string { return api.apiURL }
|
||||
func (api *KSCloudAPI) GetAuthURL() string { return api.authURL }
|
||||
func (api *KSCloudAPI) GetReportReceiverURL() string { return api.erURL }
|
||||
func (api *KSCloudAPI) SetAccountID(accountID string) { api.accountID = accountID }
|
||||
func (api *KSCloudAPI) SetClientID(clientID string) { api.clientID = clientID }
|
||||
func (api *KSCloudAPI) SetSecretKey(secretKey string) { api.secretKey = secretKey }
|
||||
func (api *KSCloudAPI) GetAccountID() string { return api.accountID }
|
||||
func (api *KSCloudAPI) IsLoggedIn() bool { return api.loggedIn }
|
||||
func (api *KSCloudAPI) GetClientID() string { return api.clientID }
|
||||
func (api *KSCloudAPI) GetSecretKey() string { return api.secretKey }
|
||||
func (api *KSCloudAPI) GetCloudReportURL() string { return api.cloudReportURL }
|
||||
func (api *KSCloudAPI) GetCloudAPIURL() string { return api.cloudAPIURL }
|
||||
func (api *KSCloudAPI) GetCloudUIURL() string { return api.cloudUIURL }
|
||||
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) 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) {
|
||||
respStr, err := api.Get(api.getFrameworkURL(name), nil)
|
||||
@@ -175,7 +192,7 @@ func (api *KSCloudAPI) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
return frameworks, err
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) GetControl(policyName string) (*reporthandling.Control, error) {
|
||||
func (api *KSCloudAPI) GetControl(ID string) (*reporthandling.Control, error) {
|
||||
return nil, fmt.Errorf("control api is not public")
|
||||
}
|
||||
|
||||
@@ -289,7 +306,7 @@ func (api *KSCloudAPI) ListFrameworks() ([]string, error) {
|
||||
return frameworkList, nil
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) ListControls(l ListType) ([]string, error) {
|
||||
func (api *KSCloudAPI) ListControls() ([]string, error) {
|
||||
return nil, fmt.Errorf("control api is not public")
|
||||
}
|
||||
|
||||
@@ -341,7 +358,7 @@ func (api *KSCloudAPI) Login() error {
|
||||
return fmt.Errorf("error authenticating: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
responseBody, err := ioutil.ReadAll(resp.Body)
|
||||
responseBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ var NativeFrameworks = []string{"nsa", "mitre", "armobest", "devopsbest"}
|
||||
|
||||
func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
||||
@@ -28,9 +28,20 @@ func (api *KSCloudAPI) getFrameworkURL(frameworkName string) 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 {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", api.getCustomerGUIDFallBack())
|
||||
@@ -40,7 +51,7 @@ func (api *KSCloudAPI) getListFrameworkURL() string {
|
||||
}
|
||||
func (api *KSCloudAPI) getExceptionsURL(clusterName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/armoPostureExceptions"
|
||||
|
||||
q := u.Query()
|
||||
@@ -55,7 +66,7 @@ func (api *KSCloudAPI) getExceptionsURL(clusterName string) string {
|
||||
|
||||
func (api *KSCloudAPI) exceptionsURL(exceptionsPolicyName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/postureExceptionPolicy"
|
||||
|
||||
q := u.Query()
|
||||
@@ -77,7 +88,7 @@ func (api *KSCloudAPI) getAccountConfigDefault(clusterName string) string {
|
||||
|
||||
func (api *KSCloudAPI) getAccountConfig(clusterName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/armoCustomerConfiguration"
|
||||
|
||||
q := u.Query()
|
||||
@@ -92,21 +103,21 @@ func (api *KSCloudAPI) getAccountConfig(clusterName string) string {
|
||||
|
||||
func (api *KSCloudAPI) getAccountURL() string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/createTenant"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getApiToken() string {
|
||||
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"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getOpenidCustomers() string {
|
||||
u := url.URL{}
|
||||
u.Scheme, u.Host = parseHost(api.GetApiURL())
|
||||
u.Scheme, u.Host = parseHost(api.GetCloudAPIURL())
|
||||
u.Path = "api/v1/openid_customers"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
@@ -35,11 +36,11 @@ func NewLoadPolicy(filePaths []string) *LoadPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
// Return control from file
|
||||
func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, error) {
|
||||
|
||||
// GetControl returns a control from the policy file.
|
||||
func (lp *LoadPolicy) GetControl(controlID string) (*reporthandling.Control, error) {
|
||||
control := &reporthandling.Control{}
|
||||
filePath := lp.filePath()
|
||||
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -48,33 +49,39 @@ func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, e
|
||||
if err = json.Unmarshal(f, control); err != nil {
|
||||
return control, err
|
||||
}
|
||||
if controlName != "" && !strings.EqualFold(controlName, control.Name) && !strings.EqualFold(controlName, control.ControlID) {
|
||||
framework, err := lp.GetFramework(control.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("control from file not matching")
|
||||
} else {
|
||||
for _, ctrl := range framework.Controls {
|
||||
if strings.EqualFold(ctrl.Name, controlName) || strings.EqualFold(ctrl.ControlID, controlName) {
|
||||
control = &ctrl
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if controlID == "" || strings.EqualFold(controlID, control.ControlID) {
|
||||
return control, nil
|
||||
}
|
||||
|
||||
framework, err := lp.GetFramework(control.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("control from file not matching")
|
||||
}
|
||||
|
||||
for _, toPin := range framework.Controls {
|
||||
ctrl := toPin
|
||||
if strings.EqualFold(ctrl.ControlID, controlID) {
|
||||
control = &ctrl
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
return control, err
|
||||
|
||||
return control, nil
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
|
||||
framework := &reporthandling.Framework{}
|
||||
var framework reporthandling.Framework
|
||||
var err error
|
||||
for _, filePath := range lp.filePaths {
|
||||
framework = reporthandling.Framework{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, framework); err != nil {
|
||||
return framework, err
|
||||
if err = json.Unmarshal(f, &framework); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.EqualFold(frameworkName, framework.Name) {
|
||||
break
|
||||
@@ -84,7 +91,7 @@ func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framew
|
||||
|
||||
return nil, fmt.Errorf("framework from file not matching")
|
||||
}
|
||||
return framework, err
|
||||
return &framework, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
@@ -109,7 +116,7 @@ func (lp *LoadPolicy) ListFrameworks() ([]string, error) {
|
||||
return fwNames, nil
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) ListControls(listType ListType) ([]string, error) {
|
||||
func (lp *LoadPolicy) ListControls() ([]string, error) {
|
||||
// TODO - Support
|
||||
return []string{}, fmt.Errorf("loading controls list from file is not supported")
|
||||
}
|
||||
@@ -130,14 +137,19 @@ func (lp *LoadPolicy) GetControlsInputs(clusterName string) (map[string][]string
|
||||
filePath := lp.filePath()
|
||||
accountConfig := &armotypes.CustomerConfig{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
fileName := filepath.Base(filePath)
|
||||
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 {
|
||||
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
|
||||
@@ -147,3 +159,18 @@ func (lp *LoadPolicy) filePath() string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetAttackTracks() ([]v1alpha1.AttackTrack, error) {
|
||||
attackTracks := []v1alpha1.AttackTrack{}
|
||||
|
||||
f, err := os.ReadFile(lp.filePath())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(f, &attackTracks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attackTracks, nil
|
||||
}
|
||||
|
||||
16
core/cautils/gitparse_test.go
Normal file
16
core/cautils/gitparse_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
giturl "github.com/kubescape/go-git-url"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEnsureRemoteParsed(t *testing.T) {
|
||||
const remote = "git@gitlab.com:foobar/gitlab-tests/sample-project.git"
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
_, _ = giturl.NewGitURL(remote)
|
||||
})
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package cautils
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -39,7 +38,7 @@ func (s *HelmChartTestSuite) SetupSuite() {
|
||||
}
|
||||
|
||||
var obj interface{}
|
||||
file, _ := ioutil.ReadFile(filepath.Join("testdata", "helm_expected_default_values.json"))
|
||||
file, _ := os.ReadFile(filepath.Join("testdata", "helm_expected_default_values.json"))
|
||||
_ = json.Unmarshal([]byte(file), &obj)
|
||||
s.expectedDefaultValues = obj.(map[string]interface{})
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/go-git-url/apis"
|
||||
gitv5 "github.com/go-git/go-git/v5"
|
||||
configv5 "github.com/go-git/go-git/v5/config"
|
||||
plumbingv5 "github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/kubescape/go-git-url/apis"
|
||||
git2go "github.com/libgit2/git2go/v33"
|
||||
)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func unzipFile(zipPath, destinationFolder string) (*zip.ReadCloser, error) {
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range archive.File {
|
||||
filePath := filepath.Join(destinationFolder, f.Name)
|
||||
filePath := filepath.Join(destinationFolder, f.Name) //nolint:gosec
|
||||
if !strings.HasPrefix(filePath, filepath.Clean(destinationFolder)+string(os.PathSeparator)) {
|
||||
return nil, fmt.Errorf("invalid file path")
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func unzipFile(zipPath, destinationFolder string) (*zip.ReadCloser, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(dstFile, fileInArchive); err != nil {
|
||||
if _, err := io.Copy(dstFile, fileInArchive); err != nil { //nolint:gosec
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type RootInfo struct {
|
||||
Logger string // logger level
|
||||
LoggerName string // logger name ("pretty"/"zap"/"none")
|
||||
@@ -9,7 +15,12 @@ type RootInfo struct {
|
||||
|
||||
KSCloudBEURLs string // Kubescape Cloud URL
|
||||
KSCloudBEURLsDep string // Kubescape Cloud URL
|
||||
|
||||
}
|
||||
type CloudURLs struct {
|
||||
CloudReportURL string
|
||||
CloudAPIURL string
|
||||
CloudUIURL string
|
||||
CloudAuthURL string
|
||||
}
|
||||
|
||||
type Credentials struct {
|
||||
@@ -17,3 +28,23 @@ type Credentials struct {
|
||||
ClientID 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package cautils
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -11,7 +10,7 @@ import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
|
||||
giturl "github.com/armosec/go-git-url"
|
||||
giturl "github.com/kubescape/go-git-url"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
@@ -40,7 +39,8 @@ const (
|
||||
// ScanCluster string = "cluster"
|
||||
// ScanLocalFiles string = "yaml"
|
||||
localControlInputsFilename string = "controls-inputs.json"
|
||||
localExceptionsFilename string = "exceptions.json"
|
||||
LocalExceptionsFilename string = "exceptions.json"
|
||||
LocalAttackTracksFilename string = "attack-tracks.json"
|
||||
)
|
||||
|
||||
type BoolPtrFlag struct {
|
||||
@@ -94,49 +94,54 @@ const (
|
||||
)
|
||||
|
||||
type PolicyIdentifier struct {
|
||||
Name string // policy name e.g. nsa,mitre,c-0012
|
||||
Identifier string // policy Identifier e.g. c-0012 for control, nsa,mitre for frameworks
|
||||
Kind apisv1.NotificationPolicyKind // policy kind e.g. Framework,Control,Rule
|
||||
Designators armotypes.PortalDesignator
|
||||
}
|
||||
|
||||
type ScanInfo struct {
|
||||
Getters // TODO - remove from object
|
||||
PolicyIdentifier []PolicyIdentifier // TODO - remove from object
|
||||
UseExceptions string // Load file with exceptions configuration
|
||||
ControlsInputs string // Load file with inputs for controls
|
||||
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
|
||||
UseArtifactsFrom string // Load artifacts from local path. Use when running offline
|
||||
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
|
||||
Format string // Format results (table, json, junit ...)
|
||||
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
|
||||
ExcludedNamespaces string // used for host scanner namespace
|
||||
IncludeNamespaces string //
|
||||
InputPatterns []string // Yaml files input patterns
|
||||
Silent bool // Silent mode - Do not print progress logs
|
||||
FailThreshold float32 // Failure score threshold
|
||||
Submit bool // Submit results to Kubescape Cloud BE
|
||||
ScanID string // Report id of the current scan
|
||||
HostSensorEnabled BoolPtrFlag // Deploy Kubescape K8s host scanner to collect data from certain controls
|
||||
HostSensorYamlPath string // Path to hostsensor file
|
||||
Local bool // Do not submit results
|
||||
Credentials Credentials // account ID
|
||||
KubeContext string // context name
|
||||
FrameworkScan bool // false if scanning control
|
||||
ScanAll bool // true if scan all frameworks
|
||||
Getters // TODO - remove from object
|
||||
PolicyIdentifier []PolicyIdentifier // TODO - remove from object
|
||||
UseExceptions string // Load file with exceptions configuration
|
||||
ControlsInputs string // Load file with inputs for controls
|
||||
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
|
||||
UseArtifactsFrom string // Load artifacts from local path. Use when running offline
|
||||
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
|
||||
Format string // Format results (table, json, junit ...)
|
||||
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
|
||||
CustomClusterName string // Set the custom name of the cluster
|
||||
ExcludedNamespaces string // used for host scanner namespace
|
||||
IncludeNamespaces string //
|
||||
InputPatterns []string // Yaml files input patterns
|
||||
Silent bool // Silent mode - Do not print progress logs
|
||||
FailThreshold float32 // Failure score threshold
|
||||
FailThresholdSeverity string // Severity at and above which the command should fail
|
||||
Submit bool // Submit results to Kubescape Cloud BE
|
||||
CreateAccount bool // Create account in Kubescape Cloud BE if no account found in local cache
|
||||
ScanID string // Report id of the current scan
|
||||
HostSensorEnabled BoolPtrFlag // Deploy Kubescape K8s host scanner to collect data from certain controls
|
||||
HostSensorYamlPath string // Path to hostsensor file
|
||||
Local bool // Do not submit results
|
||||
Credentials Credentials // account ID
|
||||
KubeContext string // context name
|
||||
FrameworkScan bool // false if scanning control
|
||||
ScanAll bool // true if scan all frameworks
|
||||
OmitRawResources bool // true if omit raw resources from the output
|
||||
PrintAttackTree bool // true if print attack tree
|
||||
}
|
||||
|
||||
type Getters struct {
|
||||
ExceptionsGetter getter.IExceptionsGetter
|
||||
ControlsInputsGetter getter.IControlsInputsGetter
|
||||
PolicyGetter getter.IPolicyGetter
|
||||
AttackTracksGetter getter.IAttackTracksGetter
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) Init() {
|
||||
scanInfo.setUseFrom()
|
||||
scanInfo.setOutputFile()
|
||||
scanInfo.setUseArtifactsFrom()
|
||||
if scanInfo.ScanID == "" {
|
||||
scanInfo.ScanID = uuid.NewString()
|
||||
@@ -156,7 +161,7 @@ func (scanInfo *ScanInfo) setUseArtifactsFrom() {
|
||||
scanInfo.UseArtifactsFrom = dir
|
||||
}
|
||||
// set frameworks files
|
||||
files, err := ioutil.ReadDir(scanInfo.UseArtifactsFrom)
|
||||
files, err := os.ReadDir(scanInfo.UseArtifactsFrom)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to read files from directory", helpers.String("dir", scanInfo.UseArtifactsFrom), helpers.Error(err))
|
||||
}
|
||||
@@ -173,51 +178,33 @@ func (scanInfo *ScanInfo) setUseArtifactsFrom() {
|
||||
// set config-inputs file
|
||||
scanInfo.ControlsInputs = filepath.Join(scanInfo.UseArtifactsFrom, localControlInputsFilename)
|
||||
// set exceptions
|
||||
scanInfo.UseExceptions = filepath.Join(scanInfo.UseArtifactsFrom, localExceptionsFilename)
|
||||
scanInfo.UseExceptions = filepath.Join(scanInfo.UseArtifactsFrom, LocalExceptionsFilename)
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setUseFrom() {
|
||||
if scanInfo.UseDefault {
|
||||
for _, policy := range scanInfo.PolicyIdentifier {
|
||||
scanInfo.UseFrom = append(scanInfo.UseFrom, getter.GetDefaultPath(policy.Name+".json"))
|
||||
scanInfo.UseFrom = append(scanInfo.UseFrom, getter.GetDefaultPath(policy.Identifier+".json"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setOutputFile() {
|
||||
if scanInfo.Output == "" {
|
||||
return
|
||||
}
|
||||
if scanInfo.Format == "json" {
|
||||
if filepath.Ext(scanInfo.Output) != ".json" {
|
||||
scanInfo.Output += ".json"
|
||||
}
|
||||
}
|
||||
if scanInfo.Format == "junit" {
|
||||
if filepath.Ext(scanInfo.Output) != ".xml" {
|
||||
scanInfo.Output += ".xml"
|
||||
}
|
||||
}
|
||||
if scanInfo.Format == "pdf" {
|
||||
if filepath.Ext(scanInfo.Output) != ".pdf" {
|
||||
scanInfo.Output += ".pdf"
|
||||
}
|
||||
// Formats returns a slice of output formats that have been requested for a given scan
|
||||
func (scanInfo *ScanInfo) Formats() []string {
|
||||
formatString := scanInfo.Format
|
||||
if formatString != "" {
|
||||
return strings.Split(scanInfo.Format, ",")
|
||||
} else {
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// func (scanInfo *ScanInfo) GetScanningEnvironment() string {
|
||||
// if len(scanInfo.InputPatterns) != 0 {
|
||||
// return ScanLocalFiles
|
||||
// }
|
||||
// return ScanCluster
|
||||
// }
|
||||
|
||||
func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind apisv1.NotificationPolicyKind) {
|
||||
for _, policy := range policies {
|
||||
if !scanInfo.contains(policy) {
|
||||
newPolicy := PolicyIdentifier{}
|
||||
newPolicy.Kind = kind
|
||||
newPolicy.Name = policy
|
||||
newPolicy.Identifier = policy
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
}
|
||||
}
|
||||
@@ -225,7 +212,7 @@ func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind apisv1.No
|
||||
|
||||
func (scanInfo *ScanInfo) contains(policyName string) bool {
|
||||
for _, policy := range scanInfo.PolicyIdentifier {
|
||||
if policy.Name == policyName {
|
||||
if policy.Identifier == policyName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -253,7 +240,7 @@ func scanInfoToScanMetadata(scanInfo *ScanInfo) *reporthandlingv2.Metadata {
|
||||
}
|
||||
// append frameworks
|
||||
for _, policy := range scanInfo.PolicyIdentifier {
|
||||
metadata.ScanMetadata.TargetNames = append(metadata.ScanMetadata.TargetNames, policy.Name)
|
||||
metadata.ScanMetadata.TargetNames = append(metadata.ScanMetadata.TargetNames, policy.Identifier)
|
||||
}
|
||||
|
||||
metadata.ScanMetadata.KubescapeVersion = BuildNumber
|
||||
@@ -423,6 +410,7 @@ func metadataGitLocal(input string) (*reporthandlingv2.RepoContextMetadata, erro
|
||||
Date: commit.Committer.Date,
|
||||
CommitterName: commit.Committer.Name,
|
||||
}
|
||||
context.LocalRootPath, _ = gitParser.GetRootDir()
|
||||
|
||||
return context, nil
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ func TestSetContextMetadata(t *testing.T) {
|
||||
assert.Nil(t, ctx.HelmContextMetadata)
|
||||
assert.Nil(t, ctx.RepoContextMetadata)
|
||||
}
|
||||
{
|
||||
// TODO: tests were commented out due to actual http calls ; http calls should be mocked.
|
||||
/*{
|
||||
ctx := reporthandlingv2.ContextMetadata{}
|
||||
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.Owner)
|
||||
assert.Equal(t, "master", ctx.RepoContextMetadata.Branch)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
func TestGetHostname(t *testing.T) {
|
||||
@@ -42,3 +43,30 @@ func TestGetScanningContext(t *testing.T) {
|
||||
assert.Equal(t, ContextCluster, GetScanningContext(""))
|
||||
assert.Equal(t, ContextGitURL, GetScanningContext("https://github.com/kubescape/kubescape"))
|
||||
}
|
||||
|
||||
func TestScanInfoFormats(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Want []string
|
||||
}{
|
||||
{"", []string{}},
|
||||
{"json", []string{"json"}},
|
||||
{"pdf", []string{"pdf"}},
|
||||
{"html", []string{"html"}},
|
||||
{"sarif", []string{"sarif"}},
|
||||
{"html,pdf,sarif", []string{"html", "pdf", "sarif"}},
|
||||
{"pretty-printer,pdf,sarif", []string{"pretty-printer", "pdf", "sarif"}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Input, func(t *testing.T) {
|
||||
input := tc.Input
|
||||
want := tc.Want
|
||||
scanInfo := &ScanInfo{Format: input}
|
||||
|
||||
got := scanInfo.Formats()
|
||||
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@ import (
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
const SKIP_VERSION_CHECK_DEPRECATED = "KUBESCAPE_SKIP_UPDATE_CHECK"
|
||||
const SKIP_VERSION_CHECK = "KS_SKIP_UPDATE_CHECK"
|
||||
const SKIP_VERSION_CHECK_DEPRECATED_ENV = "KUBESCAPE_SKIP_UPDATE_CHECK"
|
||||
const SKIP_VERSION_CHECK_ENV = "KS_SKIP_UPDATE_CHECK"
|
||||
const CLIENT_ENV = "KS_CLIENT"
|
||||
|
||||
var BuildNumber string
|
||||
var Client string
|
||||
var LatestReleaseVersion string
|
||||
|
||||
const UnknownBuildNumber = "unknown"
|
||||
|
||||
@@ -30,9 +32,14 @@ func NewIVersionCheckHandler() IVersionCheckHandler {
|
||||
if BuildNumber == "" {
|
||||
logger.L().Warning("unknown build number, this might affect your scan results. Please make sure you are updated to latest version")
|
||||
}
|
||||
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK); ok && boolutils.StringToBool(v) {
|
||||
|
||||
if v, ok := os.LookupEnv(CLIENT_ENV); ok && v != "" {
|
||||
Client = v
|
||||
}
|
||||
|
||||
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK_ENV); ok && boolutils.StringToBool(v) {
|
||||
return NewVersionCheckHandlerMock()
|
||||
} else if v, ok := os.LookupEnv(SKIP_VERSION_CHECK_DEPRECATED); ok && boolutils.StringToBool(v) {
|
||||
} else if v, ok := os.LookupEnv(SKIP_VERSION_CHECK_DEPRECATED_ENV); ok && boolutils.StringToBool(v) {
|
||||
return NewVersionCheckHandlerMock()
|
||||
}
|
||||
return NewVersionCheckHandler()
|
||||
@@ -108,9 +115,11 @@ func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckReques
|
||||
return fmt.Errorf("failed to get latest version")
|
||||
}
|
||||
|
||||
LatestReleaseVersion := latestVersion.ClientUpdate
|
||||
|
||||
if latestVersion.ClientUpdate != "" {
|
||||
if BuildNumber != "" && semver.Compare(BuildNumber, latestVersion.ClientUpdate) == -1 {
|
||||
logger.L().Warning(warningMessage(latestVersion.ClientUpdate))
|
||||
if BuildNumber != "" && semver.Compare(BuildNumber, LatestReleaseVersion) == -1 {
|
||||
logger.L().Warning(warningMessage(LatestReleaseVersion))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package cautils
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/k8s-interface/cloudsupport"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
)
|
||||
|
||||
@@ -17,8 +18,13 @@ var (
|
||||
"LinuxKernelVariables",
|
||||
"KubeletInfo",
|
||||
"KubeProxyInfo",
|
||||
"ControlPlaneInfo",
|
||||
"CloudProviderInfo",
|
||||
}
|
||||
CloudResources = []string{
|
||||
"ClusterDescribe",
|
||||
string(cloudsupport.TypeApiServerInfo),
|
||||
}
|
||||
CloudResources = []string{"ClusterDescribe"}
|
||||
)
|
||||
|
||||
func MapKSResource(ksResourceMap *KSResources, resources []string) []string {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
|
||||
|
||||
tenant := getTenantConfig(nil, "", getKubernetesApi())
|
||||
tenant := getTenantConfig(nil, "", "", getKubernetesApi())
|
||||
|
||||
if setConfig.Account != "" {
|
||||
tenant.GetConfigObj().AccountID = setConfig.Account
|
||||
@@ -19,19 +19,31 @@ func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
|
||||
if 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()
|
||||
}
|
||||
|
||||
// View cached configurations
|
||||
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())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ks *Kubescape) DeleteCachedConfig(deleteConfig *metav1.DeleteConfig) error {
|
||||
|
||||
tenant := getTenantConfig(nil, "", getKubernetesApi()) // change k8sinterface
|
||||
tenant := getTenantConfig(nil, "", "", getKubernetesApi()) // change k8sinterface
|
||||
return tenant.DeleteCachedConfig()
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func (ks *Kubescape) DeleteExceptions(delExceptions *v1.DeleteExceptions) error {
|
||||
|
||||
// load cached config
|
||||
getTenantConfig(&delExceptions.Credentials, "", getKubernetesApi())
|
||||
getTenantConfig(&delExceptions.Credentials, "", "", getKubernetesApi())
|
||||
|
||||
// login kubescape SaaS
|
||||
ksCloudAPI := getter.GetKSCloudAPIConnector()
|
||||
|
||||
@@ -6,19 +6,28 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
TargetControlsInputs = "controls-inputs"
|
||||
TargetExceptions = "exceptions"
|
||||
TargetControl = "control"
|
||||
TargetFramework = "framework"
|
||||
TargetArtifacts = "artifacts"
|
||||
TargetAttackTracks = "attack-tracks"
|
||||
)
|
||||
|
||||
var downloadFunc = map[string]func(*metav1.DownloadInfo) error{
|
||||
"controls-inputs": downloadConfigInputs,
|
||||
"exceptions": downloadExceptions,
|
||||
"control": downloadControl,
|
||||
"framework": downloadFramework,
|
||||
"artifacts": downloadArtifacts,
|
||||
TargetControlsInputs: downloadConfigInputs,
|
||||
TargetExceptions: downloadExceptions,
|
||||
TargetControl: downloadControl,
|
||||
TargetFramework: downloadFramework,
|
||||
TargetArtifacts: downloadArtifacts,
|
||||
TargetAttackTracks: downloadAttackTracks,
|
||||
}
|
||||
|
||||
func DownloadSupportCommands() []string {
|
||||
@@ -70,6 +79,7 @@ func downloadArtifacts(downloadInfo *metav1.DownloadInfo) error {
|
||||
"controls-inputs": downloadConfigInputs,
|
||||
"exceptions": downloadExceptions,
|
||||
"framework": downloadFramework,
|
||||
"attack-tracks": downloadAttackTracks,
|
||||
}
|
||||
for artifact := range artifacts {
|
||||
if err := downloadArtifact(&metav1.DownloadInfo{Target: artifact, Path: downloadInfo.Path, FileName: fmt.Sprintf("%s.json", artifact)}, artifacts); err != nil {
|
||||
@@ -80,9 +90,9 @@ func downloadArtifacts(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.Identifier, tenant.GetAccountID(), nil)
|
||||
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetContextName())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -103,17 +113,14 @@ func downloadConfigInputs(downloadInfo *metav1.DownloadInfo) error {
|
||||
}
|
||||
|
||||
func downloadExceptions(downloadInfo *metav1.DownloadInfo) error {
|
||||
var err error
|
||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi())
|
||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||
exceptionsGetter := getExceptionsGetter("", tenant.GetAccountID(), nil)
|
||||
|
||||
exceptionsGetter := getExceptionsGetter("")
|
||||
exceptions := []armotypes.PostureExceptionPolicy{}
|
||||
if tenant.GetAccountID() != "" {
|
||||
exceptions, err = exceptionsGetter.GetExceptions(tenant.GetContextName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exceptions, err := exceptionsGetter.GetExceptions(tenant.GetContextName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
|
||||
}
|
||||
@@ -126,13 +133,37 @@ func downloadExceptions(downloadInfo *metav1.DownloadInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadAttackTracks(downloadInfo *metav1.DownloadInfo) error {
|
||||
var err error
|
||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||
|
||||
attackTracksGetter := getAttackTracksGetter(tenant.GetAccountID(), nil)
|
||||
|
||||
attackTracks, err := attackTracksGetter.GetAttackTracks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
|
||||
}
|
||||
// save in file
|
||||
err = getter.SaveInFile(attackTracks, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Downloaded", helpers.String("attack tracks", downloadInfo.Target), helpers.String("path", filepath.Join(downloadInfo.Path, downloadInfo.FileName)))
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func downloadFramework(downloadInfo *metav1.DownloadInfo) error {
|
||||
|
||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi())
|
||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||
|
||||
g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
|
||||
|
||||
if downloadInfo.Name == "" {
|
||||
if downloadInfo.Identifier == "" {
|
||||
// if framework name not specified - download all frameworks
|
||||
frameworks, err := g.GetFrameworks()
|
||||
if err != nil {
|
||||
@@ -149,9 +180,9 @@ func downloadFramework(downloadInfo *metav1.DownloadInfo) error {
|
||||
// return fmt.Errorf("missing framework name")
|
||||
} else {
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Name)
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Identifier)
|
||||
}
|
||||
framework, err := g.GetFramework(downloadInfo.Name)
|
||||
framework, err := g.GetFramework(downloadInfo.Identifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -170,29 +201,29 @@ func downloadFramework(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)
|
||||
|
||||
if downloadInfo.Name == "" {
|
||||
if downloadInfo.Identifier == "" {
|
||||
// TODO - support
|
||||
return fmt.Errorf("missing control name")
|
||||
return fmt.Errorf("missing control ID")
|
||||
}
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Name)
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Identifier)
|
||||
}
|
||||
controls, err := g.GetControl(downloadInfo.Name)
|
||||
controls, err := g.GetControl(downloadInfo.Identifier)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to download control id '%s', %s", downloadInfo.Identifier, err.Error())
|
||||
}
|
||||
if controls == nil {
|
||||
return fmt.Errorf("failed to download control - received an empty objects")
|
||||
return fmt.Errorf("failed to download control id '%s' - received an empty objects", downloadInfo.Identifier)
|
||||
}
|
||||
downloadTo := filepath.Join(downloadInfo.Path, downloadInfo.FileName)
|
||||
err = getter.SaveInFile(controls, downloadTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("name", downloadInfo.Name), helpers.String("path", downloadTo))
|
||||
logger.L().Success("Downloaded", helpers.String("artifact", downloadInfo.Target), helpers.String("ID", downloadInfo.Identifier), helpers.String("path", downloadTo))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/hostsensorutils"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resourcehandler"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer"
|
||||
printerv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter"
|
||||
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
|
||||
@@ -25,20 +28,32 @@ func getKubernetesApi() *k8sinterface.KubernetesApi {
|
||||
}
|
||||
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 {
|
||||
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 != "" {
|
||||
// load exceptions from file
|
||||
return getter.NewLoadPolicy([]string{useExceptions})
|
||||
} else {
|
||||
}
|
||||
if accountID != "" {
|
||||
// download exceptions from Kubescape Cloud backend
|
||||
return getter.GetKSCloudAPIConnector()
|
||||
}
|
||||
// download exceptions from GitHub
|
||||
if downloadReleasedPolicy == nil {
|
||||
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy()
|
||||
}
|
||||
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull attack tracks, fallback to cache
|
||||
logger.L().Warning("failed to get exceptions from github release, loading attack tracks from cache", helpers.Error(err))
|
||||
return getter.NewLoadPolicy([]string{getter.GetDefaultPath(cautils.LocalExceptionsFilename)})
|
||||
}
|
||||
return downloadReleasedPolicy
|
||||
|
||||
}
|
||||
|
||||
func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, submit bool) *cautils.RBACObjects {
|
||||
@@ -58,7 +73,7 @@ func getReporter(tenantConfig cautils.ITenantConfig, reportID string, submit, fw
|
||||
}
|
||||
if tenantConfig.GetAccountID() == "" {
|
||||
// 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
|
||||
if !fwScan {
|
||||
@@ -87,7 +102,7 @@ func getHostSensorHandler(scanInfo *cautils.ScanInfo, k8s *k8sinterface.Kubernet
|
||||
// we need to determined which controls needs host scanner
|
||||
if scanInfo.HostSensorEnabled.Get() == nil && hasHostSensorControls {
|
||||
scanInfo.HostSensorEnabled.SetBool(false) // default - do not run host scanner
|
||||
logger.L().Warning("Kubernetes cluster nodes scanning is disabled. This is required to collect valuable data for certain controls. You can enable it using the --enable-host-scan flag")
|
||||
logger.L().Warning("Kubernetes cluster nodes scanning is disabled. This is required to collect valuable data for certain controls. You can enable it using the --enable-host-scan flag")
|
||||
}
|
||||
if hostSensorVal := scanInfo.HostSensorEnabled.Get(); hostSensorVal != nil && *hostSensorVal {
|
||||
hostSensorHandler, err := hostsensorutils.NewHostSensorHandler(k8s, scanInfo.HostSensorYamlPath)
|
||||
@@ -110,34 +125,39 @@ func getFieldSelector(scanInfo *cautils.ScanInfo) resourcehandler.IFieldSelector
|
||||
return &resourcehandler.EmptySelector{}
|
||||
}
|
||||
|
||||
func policyIdentifierNames(pi []cautils.PolicyIdentifier) string {
|
||||
policiesNames := ""
|
||||
func policyIdentifierIdentities(pi []cautils.PolicyIdentifier) string {
|
||||
policiesIdentities := ""
|
||||
for i := range pi {
|
||||
policiesNames += pi[i].Name
|
||||
policiesIdentities += pi[i].Identifier
|
||||
if i+1 < len(pi) {
|
||||
policiesNames += ","
|
||||
policiesIdentities += ","
|
||||
}
|
||||
}
|
||||
if policiesNames == "" {
|
||||
policiesNames = "all"
|
||||
if policiesIdentities == "" {
|
||||
policiesIdentities = "all"
|
||||
}
|
||||
return policiesNames
|
||||
return policiesIdentities
|
||||
}
|
||||
|
||||
// setSubmitBehavior - Setup the desired cluster behavior regarding submitting to the Kubescape Cloud BE
|
||||
func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig) {
|
||||
|
||||
/*
|
||||
If "First run (local config not found)" -
|
||||
Default/keep-local - Do not send report
|
||||
Submit - Create tenant & Submit report
|
||||
If CloudReportURL not set - Do not send report
|
||||
|
||||
If "Submitted" -
|
||||
If There is no account - Do not send report
|
||||
|
||||
If There is account -
|
||||
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
|
||||
if !scanInfo.FrameworkScan {
|
||||
scanInfo.Submit = false
|
||||
@@ -150,27 +170,30 @@ func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantC
|
||||
return
|
||||
}
|
||||
|
||||
if tenantConfig.IsConfigFound() { // config found in cache (submitted)
|
||||
if !scanInfo.Local {
|
||||
if tenantConfig.GetAccountID() != "" {
|
||||
if _, err := uuid.Parse(tenantConfig.GetAccountID()); err != nil {
|
||||
scanInfo.Submit = false
|
||||
return
|
||||
}
|
||||
}
|
||||
// Submit report
|
||||
scanInfo.Submit = true
|
||||
}
|
||||
if scanInfo.Local {
|
||||
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
|
||||
} else {
|
||||
scanInfo.Submit = true
|
||||
}
|
||||
|
||||
if scanInfo.CreateAccount {
|
||||
scanInfo.Submit = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return getter.NewLoadPolicy(loadPoliciesFromFile)
|
||||
}
|
||||
if tennatEmail != "" && frameworkScope {
|
||||
if tenantEmail != "" && getter.GetKSCloudAPIConnector().GetCloudAPIURL() != "" && frameworkScope {
|
||||
g := getter.GetKSCloudAPIConnector() // download policy from Kubescape Cloud backend
|
||||
return g
|
||||
}
|
||||
@@ -223,3 +246,28 @@ func listFrameworksNames(policyGetter getter.IPolicyGetter) []string {
|
||||
}
|
||||
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 { // if failed to pull attack tracks, fallback to cache
|
||||
logger.L().Warning("failed to get attack tracks from github release, loading attack tracks from cache", helpers.Error(err))
|
||||
return getter.NewLoadPolicy([]string{getter.GetDefaultPath(cautils.LocalAttackTracksFilename)})
|
||||
}
|
||||
return downloadReleasedPolicy
|
||||
}
|
||||
|
||||
// getUIPrinter returns a printer that will be used to print to the program’s UI (terminal)
|
||||
func getUIPrinter(verboseMode bool, formatVersion string, attackTree bool, viewType cautils.ViewTypes) printer.IPrinter {
|
||||
p := printerv2.NewPrettyPrinter(verboseMode, formatVersion, attackTree, viewType)
|
||||
|
||||
// Since the UI of the program is a CLI (Stdout), it means that it should always print to Stdout
|
||||
p.SetWriter(os.Stdout.Name())
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
39
core/core/initutils_test.go
Normal file
39
core/core/initutils_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
)
|
||||
|
||||
func Test_getUIPrinter(t *testing.T) {
|
||||
scanInfo := &cautils.ScanInfo{
|
||||
FormatVersion: "v2",
|
||||
VerboseMode: true,
|
||||
View: "control",
|
||||
}
|
||||
wantFormatVersion := scanInfo.FormatVersion
|
||||
wantVerboseMode := scanInfo.VerboseMode
|
||||
wantViewType := cautils.ViewTypes(scanInfo.View)
|
||||
|
||||
got := getUIPrinter(scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View))
|
||||
|
||||
gotValue := reflect.ValueOf(got).Elem()
|
||||
gotFormatVersion := gotValue.FieldByName("formatVersion").String()
|
||||
gotVerboseMode := gotValue.FieldByName("verboseMode").Bool()
|
||||
gotViewType := cautils.ViewTypes(gotValue.FieldByName("viewType").String())
|
||||
|
||||
if gotFormatVersion != wantFormatVersion {
|
||||
t.Errorf("Got: %s, want: %s", gotFormatVersion, wantFormatVersion)
|
||||
}
|
||||
|
||||
if gotVerboseMode != wantVerboseMode {
|
||||
t.Errorf("Got: %t, want: %t", gotVerboseMode, wantVerboseMode)
|
||||
}
|
||||
|
||||
if gotViewType != wantViewType {
|
||||
t.Errorf("Got: %v, want: %v", gotViewType, wantViewType)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,8 +6,11 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer"
|
||||
v2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
var listFunc = map[string]func(*metav1.ListPolicies) ([]string, error){
|
||||
@@ -16,7 +19,7 @@ var listFunc = map[string]func(*metav1.ListPolicies) ([]string, error){
|
||||
"exceptions": listExceptions,
|
||||
}
|
||||
|
||||
var listFormatFunc = map[string]func(*metav1.ListPolicies, []string){
|
||||
var listFormatFunc = map[string]func(string, []string){
|
||||
"pretty-print": prettyPrintListFormat,
|
||||
"json": jsonListFormat,
|
||||
}
|
||||
@@ -29,14 +32,18 @@ func ListSupportActions() []string {
|
||||
return commands
|
||||
}
|
||||
func (ks *Kubescape) List(listPolicies *metav1.ListPolicies) error {
|
||||
if f, ok := listFunc[listPolicies.Target]; ok {
|
||||
policies, err := f(listPolicies)
|
||||
if policyListerFunc, ok := listFunc[listPolicies.Target]; ok {
|
||||
policies, err := policyListerFunc(listPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Strings(policies)
|
||||
|
||||
listFormatFunc[listPolicies.Format](listPolicies, policies)
|
||||
if listFormatFunction, ok := listFormatFunc[listPolicies.Format]; ok {
|
||||
listFormatFunction(listPolicies.Target, policies)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid format \"%s\", Supported formats: 'pretty-print'/'json' ", listPolicies.Format)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -44,29 +51,25 @@ func (ks *Kubescape) List(listPolicies *metav1.ListPolicies) error {
|
||||
}
|
||||
|
||||
func listFrameworks(listPolicies *metav1.ListPolicies) ([]string, error) {
|
||||
tenant := getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi()) // change k8sinterface
|
||||
g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
|
||||
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
|
||||
policyGetter := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
|
||||
|
||||
return listFrameworksNames(g), nil
|
||||
return listFrameworksNames(policyGetter), nil
|
||||
}
|
||||
|
||||
func listControls(listPolicies *metav1.ListPolicies) ([]string, error) {
|
||||
tenant := getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi()) // change k8sinterface
|
||||
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
|
||||
|
||||
g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
|
||||
l := getter.ListName
|
||||
if listPolicies.ListIDs {
|
||||
l = getter.ListID
|
||||
}
|
||||
return g.ListControls(l)
|
||||
policyGetter := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
|
||||
return policyGetter.ListControls()
|
||||
}
|
||||
|
||||
func listExceptions(listPolicies *metav1.ListPolicies) ([]string, error) {
|
||||
// load tenant metav1
|
||||
getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi())
|
||||
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi())
|
||||
|
||||
var exceptionsNames []string
|
||||
ksCloudAPI := getExceptionsGetter("")
|
||||
ksCloudAPI := getExceptionsGetter("", tenant.GetAccountID(), nil)
|
||||
exceptions, err := ksCloudAPI.GetExceptions("")
|
||||
if err != nil {
|
||||
return exceptionsNames, err
|
||||
@@ -77,12 +80,73 @@ func listExceptions(listPolicies *metav1.ListPolicies) ([]string, error) {
|
||||
return exceptionsNames, nil
|
||||
}
|
||||
|
||||
func prettyPrintListFormat(listPolicies *metav1.ListPolicies, policies []string) {
|
||||
sep := "\n * "
|
||||
fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep))
|
||||
func prettyPrintListFormat(targetPolicy string, policies []string) {
|
||||
if targetPolicy == "controls" {
|
||||
prettyPrintControls(policies)
|
||||
return
|
||||
}
|
||||
|
||||
header := fmt.Sprintf("Supported %s", targetPolicy)
|
||||
|
||||
policyTable := tablewriter.NewWriter(printer.GetWriter(""))
|
||||
policyTable.SetAutoWrapText(true)
|
||||
policyTable.SetHeader([]string{header})
|
||||
policyTable.SetHeaderLine(true)
|
||||
policyTable.SetRowLine(true)
|
||||
data := v2.Matrix{}
|
||||
|
||||
controlRows := generatePolicyRows(policies)
|
||||
data = append(data, controlRows...)
|
||||
|
||||
policyTable.SetAlignment(tablewriter.ALIGN_CENTER)
|
||||
policyTable.AppendBulk(data)
|
||||
policyTable.Render()
|
||||
}
|
||||
|
||||
func jsonListFormat(listPolicies *metav1.ListPolicies, policies []string) {
|
||||
func jsonListFormat(targetPolicy string, policies []string) {
|
||||
j, _ := json.MarshalIndent(policies, "", " ")
|
||||
|
||||
fmt.Printf("%s\n", j)
|
||||
}
|
||||
|
||||
func prettyPrintControls(policies []string) {
|
||||
controlsTable := tablewriter.NewWriter(printer.GetWriter(""))
|
||||
controlsTable.SetAutoWrapText(true)
|
||||
controlsTable.SetHeader([]string{"Control ID", "Control Name", "Docs", "Frameworks"})
|
||||
controlsTable.SetHeaderLine(true)
|
||||
controlsTable.SetRowLine(true)
|
||||
data := v2.Matrix{}
|
||||
|
||||
controlRows := generateControlRows(policies)
|
||||
data = append(data, controlRows...)
|
||||
|
||||
controlsTable.AppendBulk(data)
|
||||
controlsTable.Render()
|
||||
}
|
||||
|
||||
func generateControlRows(policies []string) [][]string {
|
||||
rows := [][]string{}
|
||||
|
||||
for _, control := range policies {
|
||||
idAndControlAndFrameworks := strings.Split(control, "|")
|
||||
id, control, framework := idAndControlAndFrameworks[0], idAndControlAndFrameworks[1], idAndControlAndFrameworks[2]
|
||||
|
||||
docs := cautils.GetControlLink(id)
|
||||
|
||||
currentRow := []string{id, control, docs, framework}
|
||||
|
||||
rows = append(rows, currentRow)
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func generatePolicyRows(policies []string) [][]string {
|
||||
rows := [][]string{}
|
||||
|
||||
for _, policy := range policies {
|
||||
currentRow := []string{policy}
|
||||
rows = append(rows, currentRow)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/opaprocessor"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/policyhandler"
|
||||
"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/printer"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter"
|
||||
@@ -26,7 +27,8 @@ type componentInterfaces struct {
|
||||
tenantConfig cautils.ITenantConfig
|
||||
resourceHandler resourcehandler.IResourceHandler
|
||||
report reporter.IReport
|
||||
printerHandler printer.IPrinter
|
||||
outputPrinters []printer.IPrinter
|
||||
uiPrinter printer.IPrinter
|
||||
hostSensorHandler hostsensorutils.IHostSensor
|
||||
}
|
||||
|
||||
@@ -43,7 +45,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
||||
|
||||
// ================== 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
|
||||
setSubmitBehavior(scanInfo, tenantConfig)
|
||||
@@ -53,12 +55,16 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
||||
if err := tenantConfig.SetTenant(); err != nil {
|
||||
logger.L().Error(err.Error())
|
||||
}
|
||||
|
||||
if scanInfo.OmitRawResources {
|
||||
logger.L().Warning("omit-raw-resources flag will be ignored in submit mode")
|
||||
}
|
||||
}
|
||||
|
||||
// ================== version testing ======================================
|
||||
|
||||
v := cautils.NewIVersionCheckHandler()
|
||||
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierNames(scanInfo.PolicyIdentifier), "", cautils.ScanningContextToScanningScope(scanInfo.GetScanningContext())))
|
||||
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierIdentities(scanInfo.PolicyIdentifier), "", cautils.ScanningContextToScanningScope(scanInfo.GetScanningContext())))
|
||||
|
||||
// ================== setup host scanner object ======================================
|
||||
|
||||
@@ -88,9 +94,17 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
||||
// reporting behavior - setup reporter
|
||||
reportHandler := getReporter(tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan, scanInfo.GetScanningContext())
|
||||
|
||||
// setup printer
|
||||
printerHandler := resultshandling.NewPrinter(scanInfo.Format, scanInfo.FormatVersion, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View))
|
||||
printerHandler.SetWriter(scanInfo.Output)
|
||||
// setup printers
|
||||
formats := scanInfo.Formats()
|
||||
|
||||
outputPrinters := make([]printer.IPrinter, 0)
|
||||
for _, format := range formats {
|
||||
printerHandler := resultshandling.NewPrinter(format, scanInfo.FormatVersion, scanInfo.PrintAttackTree, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View))
|
||||
printerHandler.SetWriter(scanInfo.Output)
|
||||
outputPrinters = append(outputPrinters, printerHandler)
|
||||
}
|
||||
|
||||
uiPrinter := getUIPrinter(scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View))
|
||||
|
||||
// ================== return interface ======================================
|
||||
|
||||
@@ -98,7 +112,8 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
||||
tenantConfig: tenantConfig,
|
||||
resourceHandler: resourceHandler,
|
||||
report: reportHandler,
|
||||
printerHandler: printerHandler,
|
||||
outputPrinters: outputPrinters,
|
||||
uiPrinter: uiPrinter,
|
||||
hostSensorHandler: hostSensorHandler,
|
||||
}
|
||||
}
|
||||
@@ -121,7 +136,8 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
|
||||
// set policy getter only after setting the customerGUID
|
||||
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetTenantEmail(), scanInfo.FrameworkScan, 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
|
||||
if scanInfo.ScanAll {
|
||||
@@ -135,7 +151,7 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
|
||||
}
|
||||
}()
|
||||
|
||||
resultsHandling := resultshandling.NewResultsHandler(interfaces.report, interfaces.printerHandler)
|
||||
resultsHandling := resultshandling.NewResultsHandler(interfaces.report, interfaces.outputPrinters, interfaces.uiPrinter)
|
||||
|
||||
// ===================== policies & resources =====================
|
||||
policyHandler := policyhandler.NewPolicyHandler(interfaces.resourceHandler)
|
||||
@@ -152,15 +168,13 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
|
||||
return resultsHandling, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
/*
|
||||
// ======================== prioritization ===================
|
||||
|
||||
// ======================== prioritization ===================
|
||||
priotizationHandler := resourcesprioritization.NewResourcesPrioritizationHandler(true)
|
||||
if err := priotizationHandler.PrioritizeResources(scanData); err != nil {
|
||||
return resultsHandling, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
*/
|
||||
if priotizationHandler, err := resourcesprioritization.NewResourcesPrioritizationHandler(scanInfo.Getters.AttackTracksGetter, scanInfo.PrintAttackTree); err != nil {
|
||||
logger.L().Warning("failed to get attack tracks, this may affect the scanning results", helpers.Error(err))
|
||||
} else if err := priotizationHandler.PrioritizeResources(scanData); err != nil {
|
||||
return resultsHandling, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
// ========================= results handling =====================
|
||||
resultsHandling.SetData(scanData)
|
||||
|
||||
@@ -39,7 +39,7 @@ func (ks *Kubescape) SubmitExceptions(credentials *cautils.Credentials, excPath
|
||||
logger.L().Info("submitting exceptions", helpers.String("path", excPath))
|
||||
|
||||
// load cached config
|
||||
tenantConfig := getTenantConfig(credentials, "", getKubernetesApi())
|
||||
tenantConfig := getTenantConfig(credentials, "", "", getKubernetesApi())
|
||||
if err := tenantConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
@@ -3,9 +3,13 @@ package v1
|
||||
import "io"
|
||||
|
||||
type SetConfig struct {
|
||||
Account string
|
||||
ClientID string
|
||||
SecretKey string
|
||||
Account string
|
||||
ClientID string
|
||||
SecretKey string
|
||||
CloudReportURL string
|
||||
CloudAPIURL string
|
||||
CloudUIURL string
|
||||
CloudAuthURL string
|
||||
}
|
||||
|
||||
type ViewConfig struct {
|
||||
|
||||
@@ -6,6 +6,6 @@ type DownloadInfo struct {
|
||||
Path string // directory to save artifact. Default is "~/.kubescape/"
|
||||
FileName string // can be empty
|
||||
Target string // type of artifact to download
|
||||
Name string // name of artifact to download
|
||||
Identifier string // identifier of artifact to download
|
||||
Credentials cautils.Credentials
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import "github.com/kubescape/kubescape/v2/core/cautils"
|
||||
|
||||
type ListPolicies struct {
|
||||
Target string
|
||||
ListIDs bool
|
||||
Format string
|
||||
Credentials cautils.Credentials
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
var mockControl_0006 = `{"guid":"","name":"Allowed hostPath","attributes":{"armoBuiltin":true},"id":"C-0006","controlID":"C-0006","creationTime":"","description":"Mounting host directory to the container can be abused to get access to sensitive data and gain persistence on the host machine.","remediation":"Refrain from using host path mount.","rules":[{"guid":"","name":"alert-rw-hostpath","attributes":{"armoBuiltin":true,"m$K8sThreatMatrix":"Persistence::Writable hostPath mount, Lateral Movement::Writable volume mounts on the host"},"creationTime":"","rule":"package armo_builtins\n\n# input: pod\n# apiversion: v1\n# does: returns hostPath volumes\n\ndeny[msga] {\n pod := input[_]\n pod.kind == \"Pod\"\n volumes := pod.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := pod.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n podname := pod.metadata.name\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"pod: %v has: %v as hostPath volume\", [podname, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [pod]\n\t\t}\n\t}\n}\n\n#handles majority of workload resources\ndeny[msga] {\n\twl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n volumes := wl.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := wl.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t\n\t}\n}\n\n#handles CronJobs\ndeny[msga] {\n\twl := input[_]\n\twl.kind == \"CronJob\"\n volumes := wl.spec.jobTemplate.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\n\tcontainer = wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.jobTemplate.spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\"packagename\": \"armo_builtins\",\n\t\"alertScore\": 7,\n\t\"failedPaths\": [result],\n\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\nisRWMount(mount, begginingOfPath, i, k) = path {\n not mount.readOnly == true\n not mount.readOnly == false\n path = \"\"\n}\nisRWMount(mount, begginingOfPath, i, k) = path {\n mount.readOnly == false\n path = sprintf(\"%vcontainers[%v].volumeMounts[%v].readOnly\", [begginingOfPath, format_int(i, 10), format_int(k, 10)])\n} ","resourceEnumerator":"","ruleLanguage":"Rego","match":[{"apiGroups":["*"],"apiVersions":["*"],"resources":["Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","CronJob","Pod"]}],"ruleDependencies":[{"packageName":"cautils"},{"packageName":"kubernetes.api.client"}],"configInputs":null,"controlConfigInputs":null,"description":"determines if any workload contains a hostPath volume with rw permissions","remediation":"Set the readOnly field of the mount to true","ruleQuery":""}],"rulesIDs":[""],"baseScore":6}`
|
||||
var mockControl_0006 = `{"guid":"","name":"HostPath mount","attributes":{"armoBuiltin":true},"id":"C-0048","controlID":"C-0048","creationTime":"","description":"Mounting host directory to the container can be abused to get access to sensitive data and gain persistence on the host machine.","remediation":"Refrain from using host path mount.","rules":[{"guid":"","name":"alert-rw-hostpath","attributes":{"armoBuiltin":true,"m$K8sThreatMatrix":"Persistence::Writable hostPath mount, Lateral Movement::Writable volume mounts on the host"},"creationTime":"","rule":"package armo_builtins\n\n# input: pod\n# apiversion: v1\n# does: returns hostPath volumes\n\ndeny[msga] {\n pod := input[_]\n pod.kind == \"Pod\"\n volumes := pod.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := pod.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n podname := pod.metadata.name\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"pod: %v has: %v as hostPath volume\", [podname, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [pod]\n\t\t}\n\t}\n}\n\n#handles majority of workload resources\ndeny[msga] {\n\twl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n volumes := wl.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\tcontainer := wl.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": [result],\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t\n\t}\n}\n\n#handles CronJobs\ndeny[msga] {\n\twl := input[_]\n\twl.kind == \"CronJob\"\n volumes := wl.spec.jobTemplate.spec.template.spec.volumes\n volume := volumes[_]\n volume.hostPath\n\n\tcontainer = wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tvolumeMount := container.volumeMounts[k]\n\tvolumeMount.name == volume.name\n\tbegginingOfPath := \"spec.jobTemplate.spec.template.spec.\"\n\tresult := isRWMount(volumeMount, begginingOfPath, i, k)\n\n\tmsga := {\n\t\"alertMessage\": sprintf(\"%v: %v has: %v as hostPath volume\", [wl.kind, wl.metadata.name, volume.name]),\n\t\"packagename\": \"armo_builtins\",\n\t\"alertScore\": 7,\n\t\"failedPaths\": [result],\n\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\nisRWMount(mount, begginingOfPath, i, k) = path {\n not mount.readOnly == true\n not mount.readOnly == false\n path = \"\"\n}\nisRWMount(mount, begginingOfPath, i, k) = path {\n mount.readOnly == false\n path = sprintf(\"%vcontainers[%v].volumeMounts[%v].readOnly\", [begginingOfPath, format_int(i, 10), format_int(k, 10)])\n} ","resourceEnumerator":"","ruleLanguage":"Rego","match":[{"apiGroups":["*"],"apiVersions":["*"],"resources":["Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","CronJob","Pod"]}],"ruleDependencies":[{"packageName":"cautils"},{"packageName":"kubernetes.api.client"}],"configInputs":null,"controlConfigInputs":null,"description":"determines if any workload contains a hostPath volume with rw permissions","remediation":"Set the readOnly field of the mount to true","ruleQuery":""}],"rulesIDs":[""],"baseScore":6}`
|
||||
|
||||
var mockControl_0044 = `{"guid":"","name":"Container hostPort","attributes":{"armoBuiltin":true},"id":"C-0044","controlID":"C-0044","creationTime":"","description":"Configuring hostPort limits you to a particular port, and if any two workloads that specify the same HostPort they cannot be deployed to the same node. Therefore, if the number of replica of such workload is higher than the number of nodes, the deployment will fail.","remediation":"Avoid usage of hostPort unless it is absolutely necessary. Use NodePort / ClusterIP instead.","rules":[{"guid":"","name":"container-hostPort","attributes":{"armoBuiltin":true},"creationTime":"","rule":"package armo_builtins\n\n\n# Fails if pod has container with hostPort\ndeny[msga] {\n pod := input[_]\n pod.kind == \"Pod\"\n container := pod.spec.containers[i]\n\tbegginingOfPath := \"spec.\"\n\tpath := isHostPort(container, i, begginingOfPath)\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"Container: %v has Host-port\", [ container.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": path,\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [pod]\n\t\t}\n\t}\n}\n\n# Fails if workload has container with hostPort\ndeny[msga] {\n wl := input[_]\n\tspec_template_spec_patterns := {\"Deployment\",\"ReplicaSet\",\"DaemonSet\",\"StatefulSet\",\"Job\"}\n\tspec_template_spec_patterns[wl.kind]\n container := wl.spec.template.spec.containers[i]\n\tbegginingOfPath := \"spec.template.spec.\"\n path := isHostPort(container, i, begginingOfPath)\n\tmsga := {\n\t\t\"alertMessage\": sprintf(\"Container: %v in %v: %v has Host-port\", [ container.name, wl.kind, wl.metadata.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": path,\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\n# Fails if cronjob has container with hostPort\ndeny[msga] {\n \twl := input[_]\n\twl.kind == \"CronJob\"\n\tcontainer = wl.spec.jobTemplate.spec.template.spec.containers[i]\n\tbegginingOfPath := \"spec.jobTemplate.spec.template.spec.\"\n path := isHostPort(container, i, begginingOfPath)\n msga := {\n\t\t\"alertMessage\": sprintf(\"Container: %v in %v: %v has Host-port\", [ container.name, wl.kind, wl.metadata.name]),\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertScore\": 7,\n\t\t\"failedPaths\": path,\n\t\t\"alertObject\": {\n\t\t\t\"k8sApiObjects\": [wl]\n\t\t}\n\t}\n}\n\n\n\nisHostPort(container, i, begginingOfPath) = path {\n\tpath = [sprintf(\"%vcontainers[%v].ports[%v].hostPort\", [begginingOfPath, format_int(i, 10), format_int(j, 10)]) | port = container.ports[j]; port.hostPort]\n\tcount(path) > 0\n}\n","resourceEnumerator":"","ruleLanguage":"Rego","match":[{"apiGroups":["*"],"apiVersions":["*"],"resources":["Deployment","ReplicaSet","DaemonSet","StatefulSet","Job","Pod","CronJob"]}],"ruleDependencies":[],"configInputs":null,"controlConfigInputs":null,"description":"fails if container has hostPort","remediation":"Make sure you do not configure hostPort for the container, if necessary use NodePort / ClusterIP","ruleQuery":"armo_builtins"}],"rulesIDs":[""],"baseScore":4}`
|
||||
|
||||
@@ -31,7 +31,7 @@ func MockFramework_0013() *reporthandling.Framework {
|
||||
return fw
|
||||
}
|
||||
|
||||
// MockFramework_0006_0013 mock control 0013 and control 0006 - "Non-root containers" and "Allowed hostPath"
|
||||
// MockFramework_0006_0013 mock control 0013 and control 0006 - "Non-root containers" and "HostPath mount"
|
||||
func MockFramework_0006_0013() *reporthandling.Framework {
|
||||
fw := &reporthandling.Framework{
|
||||
PortalBase: armotypes.PortalBase{
|
||||
|
||||
@@ -50,7 +50,7 @@ func randSeq(n int, bank []rune) string {
|
||||
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = bank[rand.Intn(len(bank))]
|
||||
b[i] = bank[rand.Intn(len(bank))] //nolint:gosec
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func GenerateContainerScanLayer(layer *ScanResultLayer) {
|
||||
layer.LayerHash = randSeq(32, hash)
|
||||
layer.Vulnerabilities = make(VulnerabilitiesList, 0)
|
||||
layer.Packages = make(LinuxPkgs, 0)
|
||||
vuls := rand.Intn(10) + 1
|
||||
vuls := rand.Intn(10) + 1 //nolint:gosec
|
||||
|
||||
for i := 0; i < vuls; i++ {
|
||||
v := Vulnerability{}
|
||||
|
||||
@@ -12,7 +12,7 @@ type ElasticContainerVulnerabilityResult struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
IsFixed int `json:"isFixed"`
|
||||
IntroducedInLayer string `json:"layerHash"`
|
||||
RelevantLinks []string `json:"links"` // shitty SE practice
|
||||
RelevantLinks []string `json:"links"` // Bad SE practice
|
||||
|
||||
Vulnerability `json:",inline"`
|
||||
}
|
||||
|
||||
@@ -28,13 +28,17 @@ spec:
|
||||
tolerations:
|
||||
# this toleration is to have the DaemonDet runnable on master nodes
|
||||
# remove it if your masters can't run pods
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
- key: node-role.kubernetes.io/master
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: host-sensor
|
||||
image: quay.io/kubescape/host-scanner:latest
|
||||
image: quay.io/kubescape/host-scanner:v1.0.39
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: true
|
||||
privileged: true
|
||||
readOnlyRootFilesystem: true
|
||||
procMount: Unmasked
|
||||
@@ -69,4 +73,4 @@ spec:
|
||||
name: host-filesystem
|
||||
hostNetwork: true
|
||||
hostPID: true
|
||||
hostIPC: true
|
||||
hostIPC: true
|
||||
|
||||
@@ -3,6 +3,7 @@ package hostsensorutils
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
@@ -99,6 +100,30 @@ func (hsh *HostSensorHandler) sendAllPodsHTTPGETRequest(path, requestKind string
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// return host-scanner version
|
||||
func (hsh *HostSensorHandler) GetVersion() (string, error) {
|
||||
// loop over pods and port-forward it to each of them
|
||||
podList, err := hsh.getPodList()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sendAllPodsHTTPGETRequest: %v", err)
|
||||
}
|
||||
|
||||
// initialization of the channels
|
||||
hsh.workerPool.init(len(podList))
|
||||
hsh.workerPool.hostSensorApplyJobs(podList, "/version", "version")
|
||||
for job := range hsh.workerPool.jobs {
|
||||
resBytes, err := hsh.HTTPGetToPod(job.podName, job.path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
version := strings.ReplaceAll(string(resBytes), "\"", "")
|
||||
version = strings.ReplaceAll(version, "\n", "")
|
||||
return version, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// return list of LinuxKernelVariables
|
||||
func (hsh *HostSensorHandler) GetKernelVariables() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
// loop over pods and port-forward it to each of them
|
||||
@@ -129,6 +154,18 @@ func (hsh *HostSensorHandler) GetKubeProxyInfo() ([]hostsensor.HostSensorDataEnv
|
||||
return hsh.sendAllPodsHTTPGETRequest("/kubeProxyInfo", "KubeProxyInfo")
|
||||
}
|
||||
|
||||
// return list of KubeProxyInfo
|
||||
func (hsh *HostSensorHandler) GetControlPlaneInfo() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
// loop over pods and port-forward it to each of them
|
||||
return hsh.sendAllPodsHTTPGETRequest("/controlPlaneInfo", ControlPlaneInfo)
|
||||
}
|
||||
|
||||
// return list of KubeProxyInfo
|
||||
func (hsh *HostSensorHandler) GetCloudProviderInfo() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
// loop over pods and port-forward it to each of them
|
||||
return hsh.sendAllPodsHTTPGETRequest("/cloudProviderInfo", CloudProviderInfo)
|
||||
}
|
||||
|
||||
// return list of KubeletCommandLine
|
||||
func (hsh *HostSensorHandler) GetKubeletCommandLine() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
// loop over pods and port-forward it to each of them
|
||||
@@ -186,6 +223,16 @@ func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnv
|
||||
var kcData []hostsensor.HostSensorDataEnvelope
|
||||
var err error
|
||||
logger.L().Debug("Accessing host scanner")
|
||||
version, err := hsh.GetVersion()
|
||||
if err != nil {
|
||||
logger.L().Warning(err.Error())
|
||||
}
|
||||
if len(version) > 0 {
|
||||
logger.L().Info("Host scanner version : " + version)
|
||||
} else {
|
||||
logger.L().Info("Unknown host scanner version")
|
||||
}
|
||||
//
|
||||
kcData, err = hsh.GetKubeletConfigurations()
|
||||
if err != nil {
|
||||
addInfoToMap(KubeletConfiguration, infoMap, err)
|
||||
@@ -269,6 +316,26 @@ func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnv
|
||||
res = append(res, kcData...)
|
||||
}
|
||||
|
||||
// GetControlPlaneInfo
|
||||
kcData, err = hsh.GetControlPlaneInfo()
|
||||
if err != nil {
|
||||
addInfoToMap(ControlPlaneInfo, infoMap, err)
|
||||
logger.L().Warning(err.Error())
|
||||
}
|
||||
if len(kcData) > 0 {
|
||||
res = append(res, kcData...)
|
||||
}
|
||||
|
||||
// GetCloudProviderInfo
|
||||
kcData, err = hsh.GetCloudProviderInfo()
|
||||
if err != nil {
|
||||
addInfoToMap(CloudProviderInfo, infoMap, err)
|
||||
logger.L().Warning(err.Error())
|
||||
}
|
||||
if len(kcData) > 0 {
|
||||
res = append(res, kcData...)
|
||||
}
|
||||
|
||||
logger.L().Debug("Done reading information from host scanner")
|
||||
return res, infoMap, nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ var (
|
||||
KubeletCommandLine = "KubeletCommandLine"
|
||||
KubeletInfo = "KubeletInfo"
|
||||
KubeProxyInfo = "KubeProxyInfo"
|
||||
ControlPlaneInfo = "ControlPlaneInfo"
|
||||
CloudProviderInfo = "CloudProviderInfo"
|
||||
|
||||
MapHostSensorResourceToApiGroup = map[string]string{
|
||||
KubeletConfiguration: "hostdata.kubescape.cloud/v1beta0",
|
||||
@@ -26,6 +28,8 @@ var (
|
||||
LinuxKernelVariables: "hostdata.kubescape.cloud/v1beta0",
|
||||
KubeletInfo: "hostdata.kubescape.cloud/v1beta0",
|
||||
KubeProxyInfo: "hostdata.kubescape.cloud/v1beta0",
|
||||
ControlPlaneInfo: "hostdata.kubescape.cloud/v1beta0",
|
||||
CloudProviderInfo: "hostdata.kubescape.cloud/v1beta0",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ type OPAProcessor struct {
|
||||
func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *resources.RegoDependenciesData) *OPAProcessor {
|
||||
if regoDependenciesData != nil && sessionObj != nil {
|
||||
regoDependenciesData.PostureControlInputs = sessionObj.RegoInputData.PostureControlInputs
|
||||
regoDependenciesData.DataControlInputs = sessionObj.RegoInputData.DataControlInputs
|
||||
}
|
||||
return &OPAProcessor{
|
||||
OPASessionObj: sessionObj,
|
||||
@@ -43,12 +44,12 @@ func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *re
|
||||
}
|
||||
func (opap *OPAProcessor) ProcessRulesListenner() error {
|
||||
|
||||
policies := ConvertFrameworksToPolicies(opap.Policies, cautils.BuildNumber)
|
||||
opap.OPASessionObj.AllPolicies = ConvertFrameworksToPolicies(opap.Policies, cautils.BuildNumber)
|
||||
|
||||
ConvertFrameworksToSummaryDetails(&opap.Report.SummaryDetails, opap.Policies, policies)
|
||||
ConvertFrameworksToSummaryDetails(&opap.Report.SummaryDetails, opap.Policies, opap.OPASessionObj.AllPolicies)
|
||||
|
||||
// process
|
||||
if err := opap.Process(policies); err != nil {
|
||||
if err := opap.Process(opap.OPASessionObj.AllPolicies); err != nil {
|
||||
logger.L().Error(err.Error())
|
||||
// Return error?
|
||||
}
|
||||
@@ -68,23 +69,26 @@ func (opap *OPAProcessor) Process(policies *cautils.Policies) error {
|
||||
|
||||
cautils.StartSpinner()
|
||||
|
||||
var errs error
|
||||
for _, control := range policies.Controls {
|
||||
for _, toPin := range policies.Controls {
|
||||
control := toPin
|
||||
|
||||
resourcesAssociatedControl, err := opap.processControl(&control)
|
||||
if err != nil {
|
||||
logger.L().Error(err.Error())
|
||||
}
|
||||
|
||||
if len(resourcesAssociatedControl) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// update resources with latest results
|
||||
if len(resourcesAssociatedControl) != 0 {
|
||||
for resourceID, controlResult := range resourcesAssociatedControl {
|
||||
if _, ok := opap.ResourcesResult[resourceID]; !ok {
|
||||
opap.ResourcesResult[resourceID] = resourcesresults.Result{ResourceID: resourceID}
|
||||
}
|
||||
t := opap.ResourcesResult[resourceID]
|
||||
t.AssociatedControls = append(t.AssociatedControls, controlResult)
|
||||
opap.ResourcesResult[resourceID] = t
|
||||
for resourceID, controlResult := range resourcesAssociatedControl {
|
||||
if _, ok := opap.ResourcesResult[resourceID]; !ok {
|
||||
opap.ResourcesResult[resourceID] = resourcesresults.Result{ResourceID: resourceID}
|
||||
}
|
||||
t := opap.ResourcesResult[resourceID]
|
||||
t.AssociatedControls = append(t.AssociatedControls, controlResult)
|
||||
opap.ResourcesResult[resourceID] = t
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +98,7 @@ func (opap *OPAProcessor) Process(policies *cautils.Policies) error {
|
||||
|
||||
opap.loggerDoneScanning()
|
||||
|
||||
return errs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) loggerStartScanning() {
|
||||
@@ -153,12 +157,16 @@ func (opap *OPAProcessor) processControl(control *reporthandling.Control) (map[s
|
||||
func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule, fixedControlInputs map[string][]string) (map[string]*resourcesresults.ResourceAssociatedRule, error) {
|
||||
|
||||
postureControlInputs := opap.regoDependenciesData.GetFilteredPostureControlInputs(rule.ConfigInputs) // get store
|
||||
dataControlInputs := map[string]string{"cloudProvider": opap.OPASessionObj.Report.ClusterCloudProvider}
|
||||
|
||||
// Merge configurable control input and fixed control input
|
||||
for k, v := range fixedControlInputs {
|
||||
postureControlInputs[k] = v
|
||||
}
|
||||
|
||||
RuleRegoDependenciesData := resources.RegoDependenciesData{DataControlInputs: dataControlInputs,
|
||||
PostureControlInputs: postureControlInputs}
|
||||
|
||||
inputResources, err := reporthandling.RegoResourcesAggregator(rule, getAllSupportedObjects(opap.K8SResources, opap.ArmoResource, opap.AllResources, rule))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting aggregated k8sObjects: %s", err.Error())
|
||||
@@ -185,7 +193,7 @@ func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule, fixedCont
|
||||
opap.AllResources[inputResources[i].GetID()] = inputResources[i]
|
||||
}
|
||||
|
||||
ruleResponses, err := opap.runOPAOnSingleRule(rule, inputRawResources, ruleData, postureControlInputs)
|
||||
ruleResponses, err := opap.runOPAOnSingleRule(rule, inputRawResources, ruleData, RuleRegoDependenciesData)
|
||||
if err != nil {
|
||||
// TODO - Handle error
|
||||
logger.L().Error(err.Error())
|
||||
@@ -217,16 +225,16 @@ func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule, fixedCont
|
||||
return resources, err
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) runOPAOnSingleRule(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, postureControlInputs map[string][]string) ([]reporthandling.RuleResponse, error) {
|
||||
func (opap *OPAProcessor) runOPAOnSingleRule(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, ruleRegoDependenciesData resources.RegoDependenciesData) ([]reporthandling.RuleResponse, error) {
|
||||
switch rule.RuleLanguage {
|
||||
case reporthandling.RegoLanguage, reporthandling.RegoLanguage2:
|
||||
return opap.runRegoOnK8s(rule, k8sObjects, getRuleData, postureControlInputs)
|
||||
return opap.runRegoOnK8s(rule, k8sObjects, getRuleData, ruleRegoDependenciesData)
|
||||
default:
|
||||
return nil, fmt.Errorf("rule: '%s', language '%v' not supported", rule.Name, rule.RuleLanguage)
|
||||
}
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) runRegoOnK8s(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, postureControlInputs map[string][]string) ([]reporthandling.RuleResponse, error) {
|
||||
func (opap *OPAProcessor) runRegoOnK8s(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, ruleRegoDependenciesData resources.RegoDependenciesData) ([]reporthandling.RuleResponse, error) {
|
||||
|
||||
// compile modules
|
||||
modules, err := getRuleDependencies()
|
||||
@@ -239,7 +247,7 @@ func (opap *OPAProcessor) runRegoOnK8s(rule *reporthandling.PolicyRule, k8sObjec
|
||||
return nil, fmt.Errorf("in 'runRegoOnSingleRule', failed to compile rule, name: %s, reason: %s", rule.Name, err.Error())
|
||||
}
|
||||
|
||||
store, err := resources.TOStorage(postureControlInputs)
|
||||
store, err := ruleRegoDependenciesData.TOStorage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -282,8 +290,12 @@ func (opap *OPAProcessor) enumerateData(rule *reporthandling.PolicyRule, k8sObje
|
||||
return k8sObjects, nil
|
||||
}
|
||||
postureControlInputs := opap.regoDependenciesData.GetFilteredPostureControlInputs(rule.ConfigInputs)
|
||||
dataControlInputs := map[string]string{"cloudProvider": opap.OPASessionObj.Report.ClusterCloudProvider}
|
||||
|
||||
ruleResponse, err := opap.runOPAOnSingleRule(rule, k8sObjects, ruleEnumeratorData, postureControlInputs)
|
||||
RuleRegoDependenciesData := resources.RegoDependenciesData{DataControlInputs: dataControlInputs,
|
||||
PostureControlInputs: postureControlInputs}
|
||||
|
||||
ruleResponse, err := opap.runOPAOnSingleRule(rule, k8sObjects, ruleEnumeratorData, RuleRegoDependenciesData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ import (
|
||||
resources "github.com/kubescape/opa-utils/resources"
|
||||
)
|
||||
|
||||
// updateResults update the results objects and report objects. This is a critical function - DO NOT CHANGE
|
||||
/*
|
||||
- remove sensible data
|
||||
- adding exceptions
|
||||
- summarize results
|
||||
*/
|
||||
// updateResults updates the results objects and report objects. This is a critical function - DO NOT CHANGE
|
||||
//
|
||||
// The function:
|
||||
// - removes sensible data
|
||||
// - adds exceptions
|
||||
// - summarizes results
|
||||
func (opap *OPAProcessor) updateResults() {
|
||||
|
||||
// remove data from all objects
|
||||
@@ -49,16 +49,6 @@ func (opap *OPAProcessor) updateResults() {
|
||||
// map control to error
|
||||
controlToInfoMap := mapControlToInfo(opap.ResourceToControlsMap, opap.InfoMap, opap.Report.SummaryDetails.Controls)
|
||||
opap.Report.SummaryDetails.InitResourcesSummary(controlToInfoMap)
|
||||
// for f := range opap.PostureReport.FrameworkReports {
|
||||
// // set exceptions
|
||||
// exceptions.SetFrameworkExceptions(&opap.PostureReport.FrameworkReports[f], opap.Exceptions, cautils.ClusterName)
|
||||
|
||||
// // set counters
|
||||
// reporthandling.SetUniqueResourcesCounter(&opap.PostureReport.FrameworkReports[f])
|
||||
|
||||
// // set default score
|
||||
// // reporthandling.SetDefaultScore(&opap.PostureReport.FrameworkReports[f])
|
||||
// }
|
||||
}
|
||||
|
||||
func mapControlToInfo(mapResourceToControls map[string][]string, infoMap map[string]apis.StatusInfo, controlSummary reportsummary.ControlSummaries) map[string]apis.StatusInfo {
|
||||
@@ -127,9 +117,11 @@ func getKubernetesObjects(k8sResources *cautils.K8SResources, allResources map[s
|
||||
groupResources := k8sinterface.ResourceGroupToString(groups, version, resource)
|
||||
for _, groupResource := range groupResources {
|
||||
if k8sObj, ok := (*k8sResources)[groupResource]; ok {
|
||||
if k8sObj == nil {
|
||||
// logger.L().Debug("skipping", helpers.String("resource", groupResource))
|
||||
}
|
||||
/*
|
||||
if k8sObj == nil {
|
||||
// logger.L().Debug("skipping", helpers.String("resource", groupResource))
|
||||
}
|
||||
*/
|
||||
for i := range k8sObj {
|
||||
k8sObjects = append(k8sObjects, allResources[k8sObj[i]])
|
||||
}
|
||||
|
||||
@@ -2,10 +2,21 @@ package policyhandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
helpersv1 "github.com/kubescape/opa-utils/reporthandling/helpers/v1"
|
||||
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
cloudsupportv1 "github.com/kubescape/k8s-interface/cloudsupport/v1"
|
||||
reportv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/k8s-interface/cloudsupport"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resourcehandler"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
)
|
||||
|
||||
// PolicyHandler -
|
||||
@@ -49,6 +60,8 @@ func (policyHandler *PolicyHandler) CollectResources(policyIdentifier []cautils.
|
||||
func (policyHandler *PolicyHandler) getResources(policyIdentifier []cautils.PolicyIdentifier, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) error {
|
||||
opaSessionObj.Report.ClusterAPIServerInfo = policyHandler.resourceHandler.GetClusterAPIServerInfo()
|
||||
|
||||
setCloudMetadata(opaSessionObj)
|
||||
|
||||
resourcesMap, allResources, ksResources, err := policyHandler.resourceHandler.GetResources(opaSessionObj, &policyIdentifier[0].Designators)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -67,3 +80,70 @@ func getDesignator(policyIdentifier []cautils.PolicyIdentifier) *armotypes.Porta
|
||||
}
|
||||
return &armotypes.PortalDesignator{}
|
||||
}
|
||||
|
||||
func setCloudMetadata(opaSessionObj *cautils.OPASessionObj) {
|
||||
iCloudMetadata := getCloudMetadata(opaSessionObj, k8sinterface.GetConfig())
|
||||
if iCloudMetadata == nil {
|
||||
return
|
||||
}
|
||||
cloudMetadata := reportv2.NewCloudMetadata(iCloudMetadata)
|
||||
opaSessionObj.Metadata.ContextMetadata.ClusterContextMetadata.CloudMetadata = cloudMetadata
|
||||
opaSessionObj.Metadata.ClusterMetadata.CloudMetadata = cloudMetadata // deprecated - fallback
|
||||
opaSessionObj.Report.ClusterCloudProvider = iCloudMetadata.Provider().ToString() // deprecated - fallback
|
||||
}
|
||||
|
||||
// getCloudMetadata - get cloud metadata from kubeconfig or API server
|
||||
// There are 3 options:
|
||||
// 1. Get cloud provider from API server git version (EKS, GKE)
|
||||
// 2. Get cloud provider from kubeconfig by parsing the cluster context (EKS, GKE)
|
||||
// 3. Get cloud provider from kubeconfig by parsing the server URL (AKS)
|
||||
func getCloudMetadata(opaSessionObj *cautils.OPASessionObj, config *clientcmdapi.Config) apis.ICloudParser {
|
||||
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var provider string
|
||||
|
||||
// attempting to get cloud provider from API server git version
|
||||
if opaSessionObj.Report.ClusterAPIServerInfo != nil {
|
||||
provider = cloudsupport.GetCloudProvider(opaSessionObj.Report.ClusterAPIServerInfo.GitVersion)
|
||||
}
|
||||
|
||||
if provider == cloudsupportv1.AKS || isAKS(config) {
|
||||
return helpersv1.NewAKSMetadata(k8sinterface.GetContextName())
|
||||
}
|
||||
if provider == cloudsupportv1.EKS || isEKS(config) {
|
||||
return helpersv1.NewEKSMetadata(k8sinterface.GetContextName())
|
||||
}
|
||||
if provider == cloudsupportv1.GKE || isGKE(config) {
|
||||
return helpersv1.NewGKEMetadata(k8sinterface.GetContextName())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if the server is AKS. e.g. https://XXX.XX.XXX.azmk8s.io:443
|
||||
func isAKS(config *clientcmdapi.Config) bool {
|
||||
const serverIdentifierAKS = "azmk8s.io"
|
||||
if cluster, ok := config.Clusters[config.CurrentContext]; ok {
|
||||
return strings.Contains(cluster.Server, serverIdentifierAKS)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the server is EKS. e.g. arn:aws:eks:eu-west-1:xxx:cluster/xxxx
|
||||
func isEKS(config *clientcmdapi.Config) bool {
|
||||
if context, ok := config.Contexts[config.CurrentContext]; ok {
|
||||
return strings.Contains(context.Cluster, cloudsupportv1.EKS)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the server is GKE. e.g. gke_xxx-xx-0000_us-central1-c_xxxx-1
|
||||
func isGKE(config *clientcmdapi.Config) bool {
|
||||
if context, ok := config.Contexts[config.CurrentContext]; ok {
|
||||
return strings.Contains(context.Cluster, cloudsupportv1.GKE)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
201
core/pkg/policyhandler/handlenotification_test.go
Normal file
201
core/pkg/policyhandler/handlenotification_test.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package policyhandler
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
helpersv1 "github.com/kubescape/opa-utils/reporthandling/helpers/v1"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed kubeconfig_mock.json
|
||||
kubeConfigMock string
|
||||
)
|
||||
|
||||
func getKubeConfigMock() *clientcmdapi.Config {
|
||||
kubeConfig := clientcmdapi.Config{}
|
||||
if err := json.Unmarshal([]byte(kubeConfigMock), &kubeConfig); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &kubeConfig
|
||||
}
|
||||
func Test_getCloudMetadata(t *testing.T) {
|
||||
type args struct {
|
||||
context string
|
||||
opaSessionObj *cautils.OPASessionObj
|
||||
kubeConfig *clientcmdapi.Config
|
||||
}
|
||||
kubeConfig := getKubeConfigMock()
|
||||
tests := []struct {
|
||||
want apis.ICloudParser
|
||||
args args
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "Test_getCloudMetadata - GitVersion: GKE",
|
||||
args: args{
|
||||
opaSessionObj: &cautils.OPASessionObj{
|
||||
Report: &reporthandlingv2.PostureReport{
|
||||
ClusterAPIServerInfo: &version.Info{
|
||||
GitVersion: "v1.25.4-gke.1600",
|
||||
},
|
||||
},
|
||||
},
|
||||
context: "",
|
||||
kubeConfig: kubeConfig,
|
||||
},
|
||||
want: helpersv1.NewGKEMetadata(""),
|
||||
},
|
||||
{
|
||||
name: "Test_getCloudMetadata_context_GKE",
|
||||
args: args{
|
||||
opaSessionObj: &cautils.OPASessionObj{
|
||||
Report: &reporthandlingv2.PostureReport{
|
||||
ClusterAPIServerInfo: nil,
|
||||
},
|
||||
},
|
||||
kubeConfig: kubeConfig,
|
||||
context: "gke_xxx-xx-0000_us-central1-c_xxxx-1",
|
||||
},
|
||||
want: helpersv1.NewGKEMetadata(""),
|
||||
},
|
||||
{
|
||||
name: "Test_getCloudMetadata_context_EKS",
|
||||
args: args{
|
||||
opaSessionObj: &cautils.OPASessionObj{
|
||||
Report: &reporthandlingv2.PostureReport{
|
||||
ClusterAPIServerInfo: nil,
|
||||
},
|
||||
},
|
||||
kubeConfig: kubeConfig,
|
||||
context: "arn:aws:eks:eu-west-1:xxx:cluster/xxxx",
|
||||
},
|
||||
want: helpersv1.NewEKSMetadata(""),
|
||||
},
|
||||
{
|
||||
name: "Test_getCloudMetadata_context_AKS",
|
||||
args: args{
|
||||
opaSessionObj: &cautils.OPASessionObj{
|
||||
Report: &reporthandlingv2.PostureReport{
|
||||
ClusterAPIServerInfo: nil,
|
||||
},
|
||||
},
|
||||
kubeConfig: kubeConfig,
|
||||
context: "xxxx-2",
|
||||
},
|
||||
want: helpersv1.NewAKSMetadata(""),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.args.kubeConfig.CurrentContext = tt.args.context
|
||||
got := getCloudMetadata(tt.args.opaSessionObj, tt.args.kubeConfig)
|
||||
if got == nil {
|
||||
t.Errorf("getCloudMetadata() = %v, want %v", got, tt.want.Provider())
|
||||
return
|
||||
}
|
||||
if got.Provider() != tt.want.Provider() {
|
||||
t.Errorf("getCloudMetadata() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isGKE(t *testing.T) {
|
||||
type args struct {
|
||||
config *clientcmdapi.Config
|
||||
context string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Test_isGKE",
|
||||
args: args{
|
||||
config: getKubeConfigMock(),
|
||||
context: "gke_xxx-xx-0000_us-central1-c_xxxx-1",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// set context
|
||||
tt.args.config.CurrentContext = tt.args.context
|
||||
if got := isGKE(tt.args.config); got != tt.want {
|
||||
t.Errorf("isGKE() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isEKS(t *testing.T) {
|
||||
type args struct {
|
||||
config *clientcmdapi.Config
|
||||
context string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Test_isEKS",
|
||||
args: args{
|
||||
config: getKubeConfigMock(),
|
||||
context: "arn:aws:eks:eu-west-1:xxx:cluster/xxxx",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// set context
|
||||
tt.args.config.CurrentContext = tt.args.context
|
||||
if got := isEKS(tt.args.config); got != tt.want {
|
||||
t.Errorf("isEKS() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isAKS(t *testing.T) {
|
||||
type args struct {
|
||||
config *clientcmdapi.Config
|
||||
context string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Test_isAKS",
|
||||
args: args{
|
||||
config: getKubeConfigMock(),
|
||||
context: "xxxx-2",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// set context
|
||||
tt.args.config.CurrentContext = tt.args.context
|
||||
if got := isAKS(tt.args.config); got != tt.want {
|
||||
t.Errorf("isAKS() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,8 @@ func (policyHandler *PolicyHandler) getPolicies(policyIdentifier []cautils.Polic
|
||||
controlsInputs, err := policyHandler.getters.ControlsInputsGetter.GetControlsInputs(cautils.ClusterName)
|
||||
if err == nil {
|
||||
policiesAndResources.RegoInputData.PostureControlInputs = controlsInputs
|
||||
} else {
|
||||
logger.L().Error(err.Error())
|
||||
}
|
||||
cautils.StopSpinner()
|
||||
|
||||
@@ -54,14 +56,16 @@ func (policyHandler *PolicyHandler) getScanPolicies(policyIdentifier []cautils.P
|
||||
switch getScanKind(policyIdentifier) {
|
||||
case apisv1.KindFramework: // Download frameworks
|
||||
for _, rule := range policyIdentifier {
|
||||
receivedFramework, err := policyHandler.getters.PolicyGetter.GetFramework(rule.Name)
|
||||
receivedFramework, err := policyHandler.getters.PolicyGetter.GetFramework(rule.Identifier)
|
||||
if err != nil {
|
||||
return frameworks, policyDownloadError(err)
|
||||
}
|
||||
if err := validateFramework(receivedFramework); err != nil {
|
||||
return frameworks, err
|
||||
}
|
||||
if receivedFramework != nil {
|
||||
frameworks = append(frameworks, *receivedFramework)
|
||||
|
||||
cache := getter.GetDefaultPath(rule.Name + ".json")
|
||||
cache := getter.GetDefaultPath(rule.Identifier + ".json")
|
||||
if err := getter.SaveInFile(receivedFramework, cache); err != nil {
|
||||
logger.L().Warning("failed to cache file", helpers.String("file", cache), helpers.Error(err))
|
||||
}
|
||||
@@ -71,15 +75,15 @@ func (policyHandler *PolicyHandler) getScanPolicies(policyIdentifier []cautils.P
|
||||
f := reporthandling.Framework{}
|
||||
var receivedControl *reporthandling.Control
|
||||
var err error
|
||||
for _, rule := range policyIdentifier {
|
||||
receivedControl, err = policyHandler.getters.PolicyGetter.GetControl(rule.Name)
|
||||
for _, policy := range policyIdentifier {
|
||||
receivedControl, err = policyHandler.getters.PolicyGetter.GetControl(policy.Identifier)
|
||||
if err != nil {
|
||||
return frameworks, policyDownloadError(err)
|
||||
}
|
||||
if receivedControl != nil {
|
||||
f.Controls = append(f.Controls, *receivedControl)
|
||||
|
||||
cache := getter.GetDefaultPath(rule.Name + ".json")
|
||||
cache := getter.GetDefaultPath(policy.Identifier + ".json")
|
||||
if err := getter.SaveInFile(receivedControl, cache); err != nil {
|
||||
logger.L().Warning("failed to cache file", helpers.String("file", cache), helpers.Error(err))
|
||||
}
|
||||
@@ -96,7 +100,7 @@ func (policyHandler *PolicyHandler) getScanPolicies(policyIdentifier []cautils.P
|
||||
func policyIdentifierToSlice(rules []cautils.PolicyIdentifier) []string {
|
||||
s := []string{}
|
||||
for i := range rules {
|
||||
s = append(s, fmt.Sprintf("%s: %s", rules[i].Kind, rules[i].Name))
|
||||
s = append(s, fmt.Sprintf("%s: %s", rules[i].Kind, rules[i].Identifier))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package policyhandler
|
||||
|
||||
// func TestGetPoliciesFromBackend(t *testing.T) {
|
||||
// notification := reporthandling.PolicyNotification{
|
||||
// Rules: []reporthandling.PolicyIdentifier{
|
||||
// {
|
||||
// Kind: reporthandling.KindFramework,
|
||||
// Name: "mitretest",
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// // os.Setenv(cacli., "")
|
||||
// ph := PolicyHandler{
|
||||
// cacli: &cacli.Cacli{},
|
||||
// }
|
||||
// f, err := ph.GetPoliciesFromBackend(¬ification)
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
// if len(f) == 0 {
|
||||
// t.Errorf("empty")
|
||||
// }
|
||||
// }
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
apisv1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
)
|
||||
@@ -21,3 +22,16 @@ func policyDownloadError(err error) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// validate the framework
|
||||
func validateFramework(framework *reporthandling.Framework) error {
|
||||
if framework == nil {
|
||||
return fmt.Errorf("received empty framework")
|
||||
}
|
||||
|
||||
// validate the controls are not empty
|
||||
if len(framework.Controls) == 0 {
|
||||
return fmt.Errorf("failed to load controls for framework: %s: empty list of controls", framework.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
48
core/pkg/policyhandler/handlepullpoliciesutils_test.go
Normal file
48
core/pkg/policyhandler/handlepullpoliciesutils_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package policyhandler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
func Test_validateFramework(t *testing.T) {
|
||||
type args struct {
|
||||
framework *reporthandling.Framework
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty framework",
|
||||
args: args{
|
||||
framework: &reporthandling.Framework{
|
||||
Controls: []reporthandling.Control{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "none empty framework",
|
||||
args: args{
|
||||
framework: &reporthandling.Framework{
|
||||
Controls: []reporthandling.Control{
|
||||
{
|
||||
ControlID: "c-0001",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := validateFramework(tt.args.framework); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateControls() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
82
core/pkg/policyhandler/kubeconfig_mock.json
Normal file
82
core/pkg/policyhandler/kubeconfig_mock.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"preferences": {},
|
||||
"clusters": {
|
||||
"xxxx-2": {
|
||||
"server": "https://XXX.XX.XXX.azmk8s.io:443"
|
||||
},
|
||||
"arn:aws:eks:eu-west-1:xxx:cluster/xxxx": {
|
||||
"server": "https://XX.XX.eu-west-1.eks.amazonaws.com"
|
||||
},
|
||||
"xxxx-0": {
|
||||
"server": "https://kubernetes.docker.XXX:6443"
|
||||
},
|
||||
"xxxx-1": {
|
||||
"server": "https://127.0.0.1:49498"
|
||||
},
|
||||
"gke_xxx-xx-0000_us-central1-c_xxxx-1": {
|
||||
"server": "https://0.0.0.0"
|
||||
},
|
||||
"microk8s-cluster": {
|
||||
"server": "https://0.0.0.0:16443"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"arn:aws:eks:eu-west-1:xxx:cluster/xxxx": {
|
||||
"exec": {
|
||||
"command": "aws",
|
||||
"args": [
|
||||
"--region",
|
||||
"eu-west-1",
|
||||
"eks",
|
||||
"get-token",
|
||||
"--cluster-name",
|
||||
"xxx"
|
||||
],
|
||||
"env": null,
|
||||
"apiVersion": "client.authentication.k8s.io/v1beta1",
|
||||
"provideClusterInfo": false,
|
||||
"Config": null,
|
||||
"InteractiveMode": "IfAvailable",
|
||||
"StdinUnavailable": false,
|
||||
"StdinUnavailableMessage": ""
|
||||
}
|
||||
},
|
||||
"gke_elated-pottery-xxx_us-central1-c_xxxx-1": {
|
||||
"auth-provider": {
|
||||
"name": "gcp",
|
||||
"config": {
|
||||
"cmd-args": "config config-helper --format=json",
|
||||
"cmd-path": "/opt/homebrew/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin/gcloud"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"contexts": {
|
||||
"xxxx-2": {
|
||||
"cluster": "xxxx-2",
|
||||
"user": "clusterUser_MyResourceGroup_xxxx-2"
|
||||
},
|
||||
"arn:aws:eks:eu-west-1:xxx:cluster/xxxx": {
|
||||
"cluster": "arn:aws:eks:eu-west-1:xxx:cluster/xxxx",
|
||||
"user": "arn:aws:eks:eu-west-1:xxx:cluster/xxxx"
|
||||
},
|
||||
"docker-desktop": {
|
||||
"cluster": "docker-desktop",
|
||||
"user": "docker-desktop"
|
||||
},
|
||||
"xxxx-0": {
|
||||
"cluster": "xxxx-0",
|
||||
"user": "xxxx-0",
|
||||
"namespace": "default"
|
||||
},
|
||||
"gke_xxx-xx-0000_us-central1-c_xxxx-1": {
|
||||
"cluster": "gke_xxx-xx-0000_us-central1-c_xxxx-1",
|
||||
"user": "gke_xxx-xx-0000_us-central1-c_xxxx-1"
|
||||
},
|
||||
"microk8s": {
|
||||
"cluster": "microk8s-cluster",
|
||||
"user": "admin"
|
||||
}
|
||||
},
|
||||
"current-context": "xxxx-0"
|
||||
}
|
||||
@@ -25,14 +25,17 @@ func (ksCivAdaptor *KSCivAdaptor) Login() error {
|
||||
}
|
||||
func (ksCivAdaptor *KSCivAdaptor) GetImagesVulnerabilities(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageVulnerabilityReport, error) {
|
||||
resultList := make([]registryvulnerabilities.ContainerImageVulnerabilityReport, 0)
|
||||
for _, imageID := range imageIDs {
|
||||
for _, toPin := range imageIDs {
|
||||
imageID := toPin
|
||||
result, err := ksCivAdaptor.GetImageVulnerability(&imageID)
|
||||
if err == nil {
|
||||
resultList = append(resultList, *result)
|
||||
} else {
|
||||
if err != nil {
|
||||
logger.L().Debug("failed to get image vulnerabilities", helpers.String("image", imageID.Tag), helpers.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
resultList = append(resultList, *result)
|
||||
}
|
||||
|
||||
return resultList, nil
|
||||
}
|
||||
|
||||
@@ -51,7 +54,7 @@ func (ksCivAdaptor *KSCivAdaptor) GetImageVulnerability(imageID *registryvulnera
|
||||
pageNumber := 1
|
||||
request := V2ListRequest{PageSize: &pageSize, PageNum: &pageNumber, InnerFilters: filter, OrderBy: "timestamp:desc"}
|
||||
requestBody, _ := json.Marshal(request)
|
||||
requestUrl := fmt.Sprintf("https://%s/api/v1/vulnerability/scanResultsDetails?customerGUID=%s", ksCivAdaptor.ksCloudAPI.GetApiURL(), ksCivAdaptor.ksCloudAPI.GetAccountID())
|
||||
requestUrl := fmt.Sprintf("https://%s/api/v1/vulnerability/scanResultsDetails?customerGUID=%s", ksCivAdaptor.ksCloudAPI.GetCloudAPIURL(), ksCivAdaptor.ksCloudAPI.GetAccountID())
|
||||
|
||||
resp, err := ksCivAdaptor.ksCloudAPI.Post(requestUrl, map[string]string{"Content-Type": "application/json"}, requestBody)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,7 +14,7 @@ func (armoCivAdaptor *KSCivAdaptor) getImageLastScanId(imageID *registryvulnerab
|
||||
pageNumber := 1
|
||||
request := V2ListRequest{PageSize: &pageSize, PageNum: &pageNumber, InnerFilters: filter, OrderBy: "timestamp:desc"}
|
||||
requestBody, _ := json.Marshal(request)
|
||||
requestUrl := fmt.Sprintf("https://%s/api/v1/vulnerability/scanResultsSumSummary?customerGUID=%s", armoCivAdaptor.ksCloudAPI.GetApiURL(), armoCivAdaptor.ksCloudAPI.GetAccountID())
|
||||
requestUrl := fmt.Sprintf("https://%s/api/v1/vulnerability/scanResultsSumSummary?customerGUID=%s", armoCivAdaptor.ksCloudAPI.GetCloudAPIURL(), armoCivAdaptor.ksCloudAPI.GetAccountID())
|
||||
|
||||
resp, err := armoCivAdaptor.ksCloudAPI.Post(requestUrl, map[string]string{"Content-Type": "application/json"}, requestBody)
|
||||
if err != nil {
|
||||
|
||||
@@ -22,7 +22,7 @@ type V2ListRequest struct {
|
||||
// How to order (sort) the list, field name + sort order (asc/desc), like https://www.w3schools.com/sql/sql_orderby.asp
|
||||
// Example: "timestamp:asc,severity:desc"
|
||||
OrderBy string `json:"orderBy,omitempty"`
|
||||
// Cursor to the next page of former requset. Not supported yet
|
||||
// Cursor to the next page of former request. Not supported yet
|
||||
// Cursor cannot be used with another parameters of this struct
|
||||
Cursor string `json:"cursor,omitempty"`
|
||||
// FieldsList allow us to return only subset of the source document fields
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
|
||||
### Layers
|
||||
|
||||
* Controls and Rules: that actual control logic implementation, the "tests" themselves. Implemented in rego
|
||||
* OPA engine: the [OPA](https://github.com/open-policy-agent/opa) rego interpreter
|
||||
* Rules processor: Kubescape component, it enumerates and runs the controls while also preparing the all the input data that the controls need for running
|
||||
* Data sources: set of different modules providing data to the Rules processor so it can run the controls with them. Examples: Kubernetes objects, cloud vendor API objects and adding in this proposal the vulnerability infomration
|
||||
* Controls and Rules: that actual control logic implementation, the "tests" themselves. Implemented in rego.
|
||||
* OPA engine: the [OPA](https://github.com/open-policy-agent/opa) rego interpreter.
|
||||
* Rules processor: Kubescape component, it enumerates and runs the controls while preparing all of the input data that the controls need for running.
|
||||
* Data sources: set of different modules providing data to the Rules processor so it can run the controls with them. Examples: Kubernetes objects, cloud vendor API objects and adding in this proposal the vulnerability information.
|
||||
* Cloud Image Vulnerability adaption interface: the subject of this proposal, it gives a common interface for different registry/vulnerability vendors to adapt to.
|
||||
* CIV adaptors: specific implementation of the CIV interface, example Harbor adaption
|
||||
* CIV adaptors: specific implementation of the CIV interface, example Harbor adaption.
|
||||
```
|
||||
-----------------------
|
||||
| Controls/Rules (rego) |
|
||||
@@ -88,7 +88,7 @@ type ContainerImageInformation struct {
|
||||
|
||||
type IContainerImageVulnerabilityAdaptor interface {
|
||||
// Credentials are coming from user input (CLI or configuration file) and they are abstracted at string to string map level
|
||||
// so and example use would be like registry: "simpledockerregistry:80" and credentials like {"username":"joedoe","password":"abcd1234"}
|
||||
// so an example use would be like registry: "simpledockerregistry:80" and credentials like {"username":"joedoe","password":"abcd1234"}
|
||||
Login(registry string, credentials map[string]string) error
|
||||
|
||||
// For "help" purposes
|
||||
@@ -161,4 +161,4 @@ The rego results will be a combination of the k8s artifact and the list of relev
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
33
core/pkg/registryadaptors/gcp/v1/Readme.md
Normal file
33
core/pkg/registryadaptors/gcp/v1/Readme.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# GCP Adaptor
|
||||
|
||||
### How we add gcp adaptor
|
||||
|
||||
As there can be possiblities of use of multiple registries we check for each adaptor if we have required credentias. For every adaptor having credentials we append the adaptor to the adaptors slice.
|
||||
|
||||
Particularly for gcp, we frstly bring the `gcpCloudAPI` from the connector. We still haven't created a proper function that initiats the gcpCloudAPI with projectId, credentialsPath, credentialsCheck fields. We check for `credentialsCheck` bool which is set true when we have credentials(to be set when initializing the gcpCloudAPI)
|
||||
|
||||
### How we fetch vulnerabilities for images
|
||||
|
||||
Step 1:
|
||||
Get container analysis client
|
||||
For this we needs credentials of the service account. Out of few approaches here we are using [JSON key file](https://cloud.google.com/container-registry/docs/advanced-authentication#json-key) for credentials and path to this file should be stored in `credentialsPath`
|
||||
|
||||
Step 2:
|
||||
Do ListOccurrenceRequest
|
||||
For this we need the `projectID` and the `resourceUrl`. ProjectID should be provided by the users and resourceUrl is processed imageTag that we get from kubescape resources
|
||||
|
||||
Step 3:
|
||||
Get Occurrence iterator
|
||||
We use context and the request from the ListOccurenceRequest to get the iterators
|
||||
|
||||
|
||||
### How we convert the response to Vulnerabilities
|
||||
|
||||
Response from the iterator has two type of kinds i.e. Discovery and Vulnerabilties and both has differnent struct
|
||||
|
||||
### How can this adaptor be used by the user
|
||||
|
||||
To know about GCR service accounts follow https://cloud.google.com/container-registry/docs/gcr-service-account
|
||||
export variables
|
||||
`export KS_GCP_CREDENTIALS_PATH=<path to service account credentials file>`
|
||||
`export KS_GCP_PROJECT_ID=<your project ID>`
|
||||
24
core/pkg/registryadaptors/gcp/v1/datastructure.go
Normal file
24
core/pkg/registryadaptors/gcp/v1/datastructure.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
)
|
||||
|
||||
type GCPAdaptor struct {
|
||||
GCPCloudAPI *getter.GCPCloudAPI
|
||||
}
|
||||
|
||||
type Mock struct {
|
||||
Name string
|
||||
Notename string
|
||||
CvssScore float32
|
||||
CreatedTime int64
|
||||
UpdatedTime int64
|
||||
Type string
|
||||
ShortDescription string
|
||||
AffectedCPEURI string
|
||||
AffectedPackage string
|
||||
FixAvailable bool
|
||||
AffectedVersion string
|
||||
FixedVersion string
|
||||
}
|
||||
91
core/pkg/registryadaptors/gcp/v1/gcpadaptor.go
Normal file
91
core/pkg/registryadaptors/gcp/v1/gcpadaptor.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
containeranalysis "cloud.google.com/go/containeranalysis/apiv1"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
grafeaspb "google.golang.org/genproto/googleapis/grafeas/v1"
|
||||
)
|
||||
|
||||
func NewGCPAdaptor(GCPCloudAPI *getter.GCPCloudAPI) *GCPAdaptor {
|
||||
return &GCPAdaptor{
|
||||
GCPCloudAPI: GCPCloudAPI,
|
||||
}
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) Login() error {
|
||||
client, err := containeranalysis.NewClient(GCPAdaptor.GCPCloudAPI.GetContext(), option.WithCredentialsFile(GCPAdaptor.GCPCloudAPI.GetCredentialsPath()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
GCPAdaptor.GCPCloudAPI.SetClient(client)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) GetImagesVulnerabilities(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageVulnerabilityReport, error) {
|
||||
resultList := make([]registryvulnerabilities.ContainerImageVulnerabilityReport, 0)
|
||||
for _, toPin := range imageIDs {
|
||||
imageID := toPin
|
||||
result, err := GCPAdaptor.GetImageVulnerability(&imageID)
|
||||
if err != nil {
|
||||
logger.L().Debug("failed to get image vulnerabilities", helpers.String("image", imageID.Tag), helpers.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
resultList = append(resultList, *result)
|
||||
}
|
||||
|
||||
return resultList, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) GetImageVulnerability(imageID *registryvulnerabilities.ContainerImageIdentifier) (*registryvulnerabilities.ContainerImageVulnerabilityReport, error) {
|
||||
|
||||
resourceUrl := fmt.Sprintf("https://%s", imageID.Tag)
|
||||
|
||||
req := &grafeaspb.ListOccurrencesRequest{
|
||||
Parent: fmt.Sprintf("projects/%s", GCPAdaptor.GCPCloudAPI.GetProjectID()),
|
||||
Filter: fmt.Sprintf(`resourceUrl=%q`, resourceUrl),
|
||||
}
|
||||
|
||||
it := GCPAdaptor.GCPCloudAPI.GetClient().GetGrafeasClient().ListOccurrences(GCPAdaptor.GCPCloudAPI.GetContext(), req)
|
||||
occs := []*grafeaspb.Occurrence{}
|
||||
var count int
|
||||
for {
|
||||
occ, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
occs = append(occs, occ)
|
||||
count++
|
||||
}
|
||||
vulnerabilities := responseObjectToVulnerabilities(occs, count)
|
||||
|
||||
resultImageVulnerabilityReport := registryvulnerabilities.ContainerImageVulnerabilityReport{
|
||||
ImageID: *imageID,
|
||||
Vulnerabilities: vulnerabilities,
|
||||
}
|
||||
return &resultImageVulnerabilityReport, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) DescribeAdaptor() string {
|
||||
return "GCP image vulnerabilities scanner, docs: https://cloud.google.com/container-analysis/docs/container-analysis"
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) GetImagesInformation(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageInformation, error) {
|
||||
// TODO
|
||||
return []registryvulnerabilities.ContainerImageInformation{}, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) GetImagesScanStatus(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageScanStatus, error) {
|
||||
// TODO
|
||||
return []registryvulnerabilities.ContainerImageScanStatus{}, nil
|
||||
}
|
||||
31
core/pkg/registryadaptors/gcp/v1/gcpadaptor_test.go
Normal file
31
core/pkg/registryadaptors/gcp/v1/gcpadaptor_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSum(t *testing.T) {
|
||||
var err error
|
||||
var adaptor registryvulnerabilities.IContainerImageVulnerabilityAdaptor
|
||||
|
||||
adaptor, err = NewGCPAdaptorMock()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, adaptor.Login())
|
||||
|
||||
imageVulnerabilityReports, err := adaptor.GetImagesVulnerabilities([]registryvulnerabilities.ContainerImageIdentifier{{Tag: "gcr.io/myproject/nginx@sha256:1XXXXX"}, {Tag: "gcr.io/myproject/nginx@sha256:2XXXXX"}})
|
||||
assert.NoError(t, err)
|
||||
|
||||
for i := range imageVulnerabilityReports {
|
||||
var length int
|
||||
if i == 0 {
|
||||
length = 5
|
||||
} else if i == 1 {
|
||||
length = 3
|
||||
}
|
||||
assert.Equal(t, length, len(imageVulnerabilityReports[i].Vulnerabilities))
|
||||
}
|
||||
}
|
||||
186
core/pkg/registryadaptors/gcp/v1/gcpadaptormock.go
Normal file
186
core/pkg/registryadaptors/gcp/v1/gcpadaptormock.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
grafeaspb "google.golang.org/genproto/googleapis/grafeas/v1"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type GCPAdaptorMock struct {
|
||||
resultList []registryvulnerabilities.ContainerImageVulnerabilityReport
|
||||
}
|
||||
|
||||
func NewGCPAdaptorMock() (*GCPAdaptorMock, error) {
|
||||
return &GCPAdaptorMock{}, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) Login() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) GetImagesVulnerabilities(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageVulnerabilityReport, error) {
|
||||
resultList := make([]registryvulnerabilities.ContainerImageVulnerabilityReport, 0)
|
||||
for _, toPin := range imageIDs {
|
||||
imageID := toPin
|
||||
result, err := GCPAdaptorMock.GetImageVulnerability(&imageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resultList = append(resultList, *result)
|
||||
|
||||
return resultList, nil //nolint:staticcheck // we return at once and shorten the mocked result
|
||||
}
|
||||
|
||||
GCPAdaptorMock.resultList = resultList
|
||||
return GCPAdaptorMock.resultList, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) GetImageVulnerability(imageID *registryvulnerabilities.ContainerImageIdentifier) (*registryvulnerabilities.ContainerImageVulnerabilityReport, error) {
|
||||
vulnerability := []*grafeaspb.Occurrence_Vulnerability{}
|
||||
occurrence := []*grafeaspb.Occurrence{}
|
||||
arr := GetMockData()
|
||||
|
||||
for i, _ := range arr {
|
||||
if imageID.Tag == "gcr.io/myproject/nginx@sha256:2XXXXX" && i == 4 {
|
||||
break
|
||||
}
|
||||
vulnerability = append(vulnerability, &grafeaspb.Occurrence_Vulnerability{
|
||||
Vulnerability: &grafeaspb.VulnerabilityOccurrence{
|
||||
Type: arr[i].Type,
|
||||
CvssScore: arr[i].CvssScore,
|
||||
ShortDescription: arr[i].ShortDescription,
|
||||
PackageIssue: []*grafeaspb.VulnerabilityOccurrence_PackageIssue{
|
||||
{
|
||||
FixedVersion: &grafeaspb.Version{
|
||||
FullName: arr[i].FixedVersion,
|
||||
},
|
||||
AffectedVersion: &grafeaspb.Version{
|
||||
FullName: arr[i].AffectedVersion,
|
||||
},
|
||||
AffectedCpeUri: arr[i].AffectedCPEURI,
|
||||
AffectedPackage: arr[i].AffectedPackage,
|
||||
},
|
||||
},
|
||||
FixAvailable: arr[i].FixAvailable,
|
||||
},
|
||||
})
|
||||
|
||||
occurrence = append(occurrence, &grafeaspb.Occurrence{
|
||||
Name: arr[i].Name,
|
||||
Kind: grafeaspb.NoteKind_ATTESTATION,
|
||||
NoteName: arr[i].Notename,
|
||||
CreateTime: ×tamppb.Timestamp{
|
||||
Seconds: arr[i].CreatedTime,
|
||||
},
|
||||
UpdateTime: ×tamppb.Timestamp{
|
||||
Seconds: arr[i].UpdatedTime,
|
||||
},
|
||||
Details: vulnerability[i],
|
||||
})
|
||||
}
|
||||
|
||||
vulnerabilities := responseObjectToVulnerabilities(occurrence, 5)
|
||||
|
||||
resultImageVulnerabilityReport := registryvulnerabilities.ContainerImageVulnerabilityReport{
|
||||
ImageID: *imageID,
|
||||
Vulnerabilities: vulnerabilities,
|
||||
}
|
||||
return &resultImageVulnerabilityReport, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) DescribeAdaptor() string {
|
||||
// TODO
|
||||
return ""
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) GetImagesInformation(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageInformation, error) {
|
||||
// TODO
|
||||
return []registryvulnerabilities.ContainerImageInformation{}, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) GetImagesScanStatus(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageScanStatus, error) {
|
||||
// TODO
|
||||
return []registryvulnerabilities.ContainerImageScanStatus{}, nil
|
||||
}
|
||||
|
||||
//==============================================================================================================================
|
||||
//==============================================================================================================================
|
||||
//==============================================================================================================================
|
||||
|
||||
func GetMockData() []Mock {
|
||||
arr := []Mock{
|
||||
{
|
||||
Name: "projects/stable-furnace-356005/occurrences/41fd9fec-6fab-4531-a4ee-e7b97d518554",
|
||||
Notename: "projects/goog-vulnz/notes/CVE-2009-4487",
|
||||
CvssScore: 6.8,
|
||||
CreatedTime: 1661061853,
|
||||
UpdatedTime: 1661061853,
|
||||
Type: "OS",
|
||||
ShortDescription: "CVE-2009-4487",
|
||||
AffectedCPEURI: "cpe:/o:debian:debian_linux:11",
|
||||
AffectedPackage: "nginx",
|
||||
FixAvailable: true,
|
||||
AffectedVersion: "1.23.1-1~bullseye",
|
||||
FixedVersion: "",
|
||||
},
|
||||
{
|
||||
Name: "projects/stable-furnace-356005/occurrences/b28fa29f-5c2b-45c7-9727-2f1f02ed1957",
|
||||
Notename: "projects/goog-vulnz/notes/CVE-2017-17740",
|
||||
CvssScore: 2.3,
|
||||
CreatedTime: 3237628,
|
||||
UpdatedTime: 5989893,
|
||||
Type: "OS",
|
||||
ShortDescription: "CVE-2017-17740",
|
||||
AffectedCPEURI: "cpe:/o:debian:debian_linux:11",
|
||||
AffectedPackage: "openldap",
|
||||
FixAvailable: false,
|
||||
AffectedVersion: "1.3.5",
|
||||
FixedVersion: "1.3.5",
|
||||
},
|
||||
{
|
||||
Name: "projects/stable-furnace-356005/occurrences/b28fa29f-5c2b-45c7-9727-2f1f02ed1957",
|
||||
Notename: "projects/goog-vulnz/notes/CVE-2017-17740",
|
||||
CvssScore: 2.3,
|
||||
CreatedTime: 3237628,
|
||||
UpdatedTime: 5989893,
|
||||
Type: "OS",
|
||||
ShortDescription: "CVE-2017-17740",
|
||||
AffectedCPEURI: "cpe:/o:debian:debian_linux:11",
|
||||
AffectedPackage: "openldap",
|
||||
FixAvailable: false,
|
||||
AffectedVersion: "1.3.5",
|
||||
FixedVersion: "1.3.5",
|
||||
},
|
||||
{
|
||||
Name: "projects/stable-furnace-356005/occurrences/b28fa29f-5c2b-45c7-9727-2f1f02ed1957",
|
||||
Notename: "projects/goog-vulnz/notes/CVE-2017-17740",
|
||||
CvssScore: 2.3,
|
||||
CreatedTime: 3237628,
|
||||
UpdatedTime: 5989893,
|
||||
Type: "OS",
|
||||
ShortDescription: "CVE-2017-17740",
|
||||
AffectedCPEURI: "cpe:/o:debian:debian_linux:11",
|
||||
AffectedPackage: "openldap",
|
||||
FixAvailable: false,
|
||||
AffectedVersion: "1.3.5",
|
||||
FixedVersion: "1.3.5",
|
||||
},
|
||||
{
|
||||
Name: "projects/stable-furnace-356005/occurrences/b28fa29f-5c2b-45c7-9727-2f1f02ed1957",
|
||||
Notename: "projects/goog-vulnz/notes/CVE-2017-17740",
|
||||
CvssScore: 2.3,
|
||||
CreatedTime: 3237628,
|
||||
UpdatedTime: 5989893,
|
||||
Type: "OS",
|
||||
ShortDescription: "CVE-2017-17740",
|
||||
AffectedCPEURI: "cpe:/o:debian:debian_linux:11",
|
||||
AffectedPackage: "openldap",
|
||||
FixAvailable: false,
|
||||
AffectedVersion: "1.3.5",
|
||||
FixedVersion: "1.3.5",
|
||||
},
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
36
core/pkg/registryadaptors/gcp/v1/gcpadaptorutils.go
Normal file
36
core/pkg/registryadaptors/gcp/v1/gcpadaptorutils.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
grafeaspb "google.golang.org/genproto/googleapis/grafeas/v1"
|
||||
)
|
||||
|
||||
func responseObjectToVulnerabilities(vulnerabilityList []*grafeaspb.Occurrence, count int) []registryvulnerabilities.Vulnerability {
|
||||
vulnerabilities := make([]registryvulnerabilities.Vulnerability, count)
|
||||
for i, vulnerabilityEntry := range vulnerabilityList {
|
||||
if vulnerabilityEntry.GetKind().String() != "DISCOVERY" {
|
||||
vulnerabilities[i].Name = vulnerabilityEntry.Name
|
||||
vulnerabilities[i].NoteName = vulnerabilityEntry.NoteName
|
||||
vulnerabilities[i].CreateTime = vulnerabilityEntry.CreateTime.AsTime()
|
||||
vulnerabilities[i].UpdateTime = vulnerabilityEntry.UpdateTime.AsTime()
|
||||
vulnerabilities[i].CVSS = vulnerabilityEntry.GetVulnerability().CvssScore
|
||||
vulnerabilities[i].AffectedCPEURI = vulnerabilityEntry.GetVulnerability().PackageIssue[0].AffectedCpeUri
|
||||
vulnerabilities[i].AffectedPackage = vulnerabilityEntry.GetVulnerability().PackageIssue[0].AffectedPackage
|
||||
vulnerabilities[i].AffectedVersion = vulnerabilityEntry.GetVulnerability().PackageIssue[0].AffectedVersion.FullName
|
||||
vulnerabilities[i].FixedVersion = vulnerabilityEntry.GetVulnerability().PackageIssue[0].FixedVersion.FullName
|
||||
vulnerabilities[i].FixedCPEURI = vulnerabilityEntry.GetVulnerability().PackageIssue[0].FixedCpeUri
|
||||
vulnerabilities[i].FixedPackege = vulnerabilityEntry.GetVulnerability().PackageIssue[0].FixedPackage
|
||||
vulnerabilities[i].FixAvailablePackage = vulnerabilityEntry.GetVulnerability().PackageIssue[0].GetFixAvailable()
|
||||
vulnerabilities[i].PackageType = vulnerabilityEntry.GetVulnerability().PackageIssue[0].PackageType
|
||||
vulnerabilities[i].EffectiveSeverityPackage = vulnerabilityEntry.GetVulnerability().PackageIssue[0].EffectiveSeverity.String()
|
||||
vulnerabilities[i].AffectedPackage = vulnerabilityEntry.GetVulnerability().PackageIssue[0].AffectedPackage
|
||||
vulnerabilities[i].Severity = vulnerabilityEntry.GetVulnerability().Severity.Enum().String()
|
||||
vulnerabilities[i].ShortDescription = vulnerabilityEntry.GetVulnerability().ShortDescription
|
||||
vulnerabilities[i].LongDescription = vulnerabilityEntry.GetVulnerability().LongDescription
|
||||
} else {
|
||||
vulnerabilities[i].Description = vulnerabilityEntry.GetDiscovery().String()
|
||||
}
|
||||
}
|
||||
|
||||
return vulnerabilities
|
||||
}
|
||||
@@ -28,19 +28,36 @@ type Categories struct {
|
||||
}
|
||||
|
||||
type Vulnerability struct {
|
||||
Name string `json:"name"`
|
||||
RelatedPackageName string `json:"packageName"`
|
||||
PackageVersion string `json:"packageVersion"`
|
||||
Link string `json:"link"`
|
||||
Description string `json:"description"`
|
||||
Severity string `json:"severity"`
|
||||
Metadata interface{} `json:"metadata"`
|
||||
Fixes []FixedIn `json:"fixedIn"`
|
||||
Relevancy string `json:"relevant"` // use the related enum
|
||||
UrgentCount int `json:"urgent"`
|
||||
NeglectedCount int `json:"neglected"`
|
||||
HealthStatus string `json:"healthStatus"`
|
||||
Categories Categories `json:"categories"`
|
||||
Name string `json:"name"`
|
||||
RelatedPackageName string `json:"packageName"`
|
||||
PackageVersion string `json:"packageVersion"`
|
||||
Link string `json:"link"`
|
||||
Description string `json:"description"`
|
||||
Severity string `json:"severity"`
|
||||
Metadata interface{} `json:"metadata"`
|
||||
Fixes []FixedIn `json:"fixedIn"`
|
||||
Relevancy string `json:"relevant"` // use the related enum
|
||||
UrgentCount int `json:"urgent"`
|
||||
NeglectedCount int `json:"neglected"`
|
||||
HealthStatus string `json:"healthStatus"`
|
||||
Categories Categories `json:"categories"`
|
||||
NoteName string `json:",omitempty"`
|
||||
CreateTime time.Time `json:",omitempty"`
|
||||
UpdateTime time.Time `json:",omitempty"` // Vulnerablity started
|
||||
CVSS float32 `json:",omitempty"` // other cvss versions are available
|
||||
AffectedCPEURI string `json:",omitempty"` // Package issue
|
||||
AffectedPackage string `json:",omitempty"`
|
||||
AffectedVersion string `json:",omitempty"`
|
||||
FixedVersion string `json:",omitempty"`
|
||||
FixedCPEURI string `json:",omitempty"`
|
||||
FixedPackege string `json:",omitempty"`
|
||||
FixAvailablePackage bool `json:",omitempty"`
|
||||
PackageType string `json:",omitempty"`
|
||||
EffectiveSeverityPackage string `json:",omitempty"`
|
||||
ShortDescription string `json:",omitempty"` // Package issue ends
|
||||
LongDescription string `json:",omitempty"`
|
||||
EffectiveSeverity string `json:",omitempty"`
|
||||
FixAvailable bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
type ContainerImageVulnerabilityReport struct {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package registryvulnerabilities
|
||||
|
||||
type IContainerImageVulnerabilityAdaptor interface {
|
||||
// Credentials are coming from user input (CLI or configuration file) and they are abstracted at string to string map level
|
||||
// Login Credentials are coming from user input (CLI or configuration file) and they are abstracted at string to string map level
|
||||
// so and example use would be like registry: "simpledockerregistry:80" and credentials like {"username":"joedoe","password":"abcd1234"}
|
||||
Login() error
|
||||
|
||||
// For "help" purposes
|
||||
// DescribeAdaptor For "help" purposes
|
||||
DescribeAdaptor() string
|
||||
|
||||
GetImagesScanStatus(imageIDs []ContainerImageIdentifier) ([]ContainerImageScanStatus, error)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
type IFieldSelector interface {
|
||||
GetNamespacesSelectors(*schema.GroupVersionResource) []string
|
||||
GetClusterScope(*schema.GroupVersionResource) bool
|
||||
}
|
||||
|
||||
type EmptySelector struct {
|
||||
@@ -19,6 +20,10 @@ func (es *EmptySelector) GetNamespacesSelectors(resource *schema.GroupVersionRes
|
||||
return []string{""} //
|
||||
}
|
||||
|
||||
func (es *EmptySelector) GetClusterScope(*schema.GroupVersionResource) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type ExcludeSelector struct {
|
||||
namespace string
|
||||
}
|
||||
@@ -27,6 +32,14 @@ func NewExcludeSelector(ns string) *ExcludeSelector {
|
||||
return &ExcludeSelector{namespace: ns}
|
||||
}
|
||||
|
||||
func (es *ExcludeSelector) GetClusterScope(resource *schema.GroupVersionResource) bool {
|
||||
// for selector, 'namespace' is in Namespaced scope
|
||||
if resource.Resource == "namespaces" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type IncludeSelector struct {
|
||||
namespace string
|
||||
}
|
||||
@@ -34,6 +47,15 @@ type IncludeSelector struct {
|
||||
func NewIncludeSelector(ns string) *IncludeSelector {
|
||||
return &IncludeSelector{namespace: ns}
|
||||
}
|
||||
|
||||
func (is *IncludeSelector) GetClusterScope(resource *schema.GroupVersionResource) bool {
|
||||
// for selector, 'namespace' is in Namespaced scope
|
||||
if resource.Resource == "namespaces" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (es *ExcludeSelector) GetNamespacesSelectors(resource *schema.GroupVersionResource) []string {
|
||||
fieldSelectors := ""
|
||||
for _, n := range strings.Split(es.namespace, ",") {
|
||||
|
||||
@@ -76,9 +76,10 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess
|
||||
|
||||
}
|
||||
|
||||
if err := fileHandler.registryAdaptors.collectImagesVulnerabilities(k8sResources, allResources, ksResources); err != nil {
|
||||
logger.L().Warning("failed to collect images vulnerabilities", helpers.Error(err))
|
||||
}
|
||||
// Should Kubescape scan image related controls when scanning local files?
|
||||
// if err := fileHandler.registryAdaptors.collectImagesVulnerabilities(k8sResources, allResources, ksResources); err != nil {
|
||||
// logger.L().Warning("failed to collect images vulnerabilities", helpers.Error(err))
|
||||
// }
|
||||
|
||||
cautils.StopSpinner()
|
||||
logger.L().Success("Done accessing local objects")
|
||||
@@ -103,6 +104,8 @@ func getResourcesFromPath(path string) (map[string]reporthandling.Source, []work
|
||||
gitRepo, err := cautils.NewLocalGitRepository(path)
|
||||
if err == nil && gitRepo != nil {
|
||||
repoRoot, _ = gitRepo.GetRootDir()
|
||||
} else {
|
||||
repoRoot, _ = filepath.Abs(path)
|
||||
}
|
||||
|
||||
// load resource from local file system
|
||||
@@ -141,7 +144,7 @@ func getResourcesFromPath(path string) (map[string]reporthandling.Source, []work
|
||||
}
|
||||
|
||||
workloadSource := reporthandling.Source{
|
||||
RelativePath: source,
|
||||
RelativePath: relSource,
|
||||
FileType: filetype,
|
||||
LastCommit: lastCommit,
|
||||
}
|
||||
@@ -196,6 +199,44 @@ func getResourcesFromPath(path string) (map[string]reporthandling.Source, []work
|
||||
logger.L().Debug("helm templates found in local storage", helpers.Int("helmTemplates", len(helmSourceToWorkloads)), helpers.Int("workloads", len(workloads)))
|
||||
}
|
||||
|
||||
// Load resources from Kustomize directory
|
||||
kustomizeSourceToWorkloads, kustomizeDirectoryName := cautils.LoadResourcesFromKustomizeDirectory(path)
|
||||
|
||||
// update workloads and workloadIDToSource with workloads from Kustomize Directory
|
||||
for source, ws := range kustomizeSourceToWorkloads {
|
||||
workloads = append(workloads, ws...)
|
||||
relSource, err := filepath.Rel(repoRoot, source)
|
||||
|
||||
if err == nil {
|
||||
source = relSource
|
||||
}
|
||||
|
||||
var lastCommit reporthandling.LastCommit
|
||||
if gitRepo != nil {
|
||||
commitInfo, _ := gitRepo.GetFileLastCommit(source)
|
||||
if commitInfo != nil {
|
||||
lastCommit = reporthandling.LastCommit{
|
||||
Hash: commitInfo.SHA,
|
||||
Date: commitInfo.Author.Date,
|
||||
CommitterName: commitInfo.Author.Name,
|
||||
CommitterEmail: commitInfo.Author.Email,
|
||||
Message: commitInfo.Message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workloadSource := reporthandling.Source{
|
||||
RelativePath: source,
|
||||
FileType: reporthandling.SourceTypeKustomizeDirectory,
|
||||
KustomizeDirectoryName: kustomizeDirectoryName,
|
||||
LastCommit: lastCommit,
|
||||
}
|
||||
|
||||
for i := range ws {
|
||||
workloadIDToSource[ws[i].GetID()] = workloadSource
|
||||
}
|
||||
}
|
||||
|
||||
return workloadIDToSource, workloads, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
giturl "github.com/armosec/go-git-url"
|
||||
giturl "github.com/kubescape/go-git-url"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
|
||||
@@ -79,22 +79,30 @@ func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessio
|
||||
sessionObj.SetNumberOfWorkerNodes(numberOfWorkerNodes)
|
||||
}
|
||||
|
||||
cautils.StopSpinner()
|
||||
logger.L().Success("Accessed to Kubernetes objects")
|
||||
|
||||
imgVulnResources := cautils.MapImageVulnResources(ksResourceMap)
|
||||
// check that controls use image vulnerability resources
|
||||
if len(imgVulnResources) > 0 {
|
||||
logger.L().Info("Requesting images vulnerabilities results")
|
||||
cautils.StartSpinner()
|
||||
if err := k8sHandler.registryAdaptors.collectImagesVulnerabilities(k8sResourcesMap, allResources, ksResourceMap); err != nil {
|
||||
logger.L().Warning("failed to collect image vulnerabilities", helpers.Error(err))
|
||||
cautils.SetInfoMapForResources(fmt.Sprintf("failed to pull image scanning data: %s. for more information: https://hub.armosec.io/docs/configuration-of-image-vulnerabilities", err.Error()), imgVulnResources, sessionObj.InfoMap)
|
||||
} else {
|
||||
if isEmptyImgVulns(*ksResourceMap) {
|
||||
cautils.SetInfoMapForResources("image scanning is not configured. for more information: https://hub.armosec.io/docs/configuration-of-image-vulnerabilities", imgVulnResources, sessionObj.InfoMap)
|
||||
}
|
||||
}
|
||||
cautils.StopSpinner()
|
||||
logger.L().Success("Requested images vulnerabilities results")
|
||||
}
|
||||
|
||||
hostResources := cautils.MapHostResources(ksResourceMap)
|
||||
// check that controls use host sensor resources
|
||||
if len(hostResources) > 0 {
|
||||
logger.L().Info("Requesting Host scanner data")
|
||||
cautils.StartSpinner()
|
||||
if sessionObj.Metadata.ScanMetadata.HostScanner {
|
||||
infoMap, err := k8sHandler.collectHostResources(allResources, ksResourceMap)
|
||||
if err != nil {
|
||||
@@ -108,6 +116,8 @@ func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessio
|
||||
sessionObj.InfoMap = infoMap
|
||||
}
|
||||
}
|
||||
cautils.StopSpinner()
|
||||
logger.L().Success("Requested Host scanner data")
|
||||
} else {
|
||||
cautils.SetInfoMapForResources("enable-host-scan flag not used. For more information: https://hub.armosec.io/docs/host-sensor", hostResources, sessionObj.InfoMap)
|
||||
}
|
||||
@@ -133,14 +143,29 @@ func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessio
|
||||
sessionObj.Metadata.ContextMetadata.ClusterContextMetadata.CloudProvider = provider
|
||||
}
|
||||
}
|
||||
|
||||
// api server info resource
|
||||
err = k8sHandler.collectAPIServerInfoResource(allResources, ksResourceMap)
|
||||
if err != nil {
|
||||
logger.L().Warning("failed to collect api server info resource", helpers.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
cautils.StopSpinner()
|
||||
logger.L().Success("Accessed to Kubernetes objects")
|
||||
|
||||
return k8sResourcesMap, allResources, ksResourceMap, nil
|
||||
}
|
||||
|
||||
func (k8sHandler *K8sResourceHandler) collectAPIServerInfoResource(allResources map[string]workloadinterface.IMetadata, ksResourceMap *cautils.KSResources) error {
|
||||
clusterAPIServerInfo, err := k8sHandler.k8s.DiscoveryClient.ServerVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resource := cloudsupport.NewApiServerVersionInfo(clusterAPIServerInfo)
|
||||
allResources[resource.GetID()] = resource
|
||||
(*ksResourceMap)[fmt.Sprintf("%s/%s", resource.GetApiVersion(), resource.GetKind())] = []string{resource.GetID()}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k8sHandler *K8sResourceHandler) GetClusterAPIServerInfo() *version.Info {
|
||||
clusterAPIServerInfo, err := k8sHandler.k8s.DiscoveryClient.ServerVersion()
|
||||
if err != nil {
|
||||
@@ -218,10 +243,14 @@ func (k8sHandler *K8sResourceHandler) pullSingleResource(resource *schema.GroupV
|
||||
|
||||
// set dynamic object
|
||||
var clientResource dynamic.ResourceInterface
|
||||
if namespace != "" && k8sinterface.IsNamespaceScope(resource) {
|
||||
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource).Namespace(namespace)
|
||||
} else {
|
||||
if namespace != "" {
|
||||
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource)
|
||||
} else if k8sinterface.IsNamespaceScope(resource) {
|
||||
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource).Namespace(namespace)
|
||||
} else if k8sHandler.fieldSelector.GetClusterScope(resource) {
|
||||
clientResource = k8sHandler.k8s.DynamicClient.Resource(*resource)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
// list resources
|
||||
|
||||
@@ -22,6 +22,8 @@ var (
|
||||
ImageVulnerabilities = "ImageVulnerabilities"
|
||||
KubeletInfo = "KubeletInfo"
|
||||
KubeProxyInfo = "KubeProxyInfo"
|
||||
ControlPlaneInfo = "ControlPlaneInfo"
|
||||
CloudProviderInfo = "CloudProviderInfo"
|
||||
|
||||
MapResourceToApiGroup = map[string]string{
|
||||
KubeletConfiguration: "hostdata.kubescape.cloud/v1beta0",
|
||||
@@ -33,6 +35,8 @@ var (
|
||||
LinuxKernelVariables: "hostdata.kubescape.cloud/v1beta0",
|
||||
KubeletInfo: "hostdata.kubescape.cloud/v1beta0",
|
||||
KubeProxyInfo: "hostdata.kubescape.cloud/v1beta0",
|
||||
ControlPlaneInfo: "hostdata.kubescape.cloud/v1beta0",
|
||||
CloudProviderInfo: "hostdata.kubescape.cloud/v1beta0",
|
||||
}
|
||||
MapResourceToApiGroupVuln = map[string][]string{
|
||||
ImageVulnerabilities: {"armo.vuln.images/v1", "image.vulnscan.com/v1"}}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
armosecadaptorv1 "github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/armosec/v1"
|
||||
gcpadaptorv1 "github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/gcp/v1"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
|
||||
"github.com/kubescape/opa-utils/shared"
|
||||
@@ -158,5 +159,12 @@ func listAdaptores() ([]registryvulnerabilities.IContainerImageVulnerabilityAdap
|
||||
}
|
||||
}
|
||||
|
||||
gcpCloudAPI := getter.GetGlobalGCPCloudAPIConnector()
|
||||
if gcpCloudAPI != nil {
|
||||
if gcpCloudAPI.GetCredentialsCheck() {
|
||||
adaptors = append(adaptors, gcpadaptorv1.NewGCPAdaptor(getter.GetGlobalGCPCloudAPIConnector()))
|
||||
}
|
||||
}
|
||||
|
||||
return adaptors, nil
|
||||
}
|
||||
|
||||
@@ -6,21 +6,21 @@ import (
|
||||
nethttp "net/http"
|
||||
"os"
|
||||
|
||||
giturl "github.com/armosec/go-git-url"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
giturl "github.com/kubescape/go-git-url"
|
||||
)
|
||||
|
||||
// To Check if the given repository is Public(No Authentication needed), send a HTTP GET request to the URL
|
||||
// If response code is 200, the repository is Public.
|
||||
func isGitRepoPublic(URL string) bool {
|
||||
resp, err := nethttp.Get(URL)
|
||||
|
||||
func isGitRepoPublic(u string) bool {
|
||||
resp, err := nethttp.Get(u) //nolint:gosec
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// if the status code is 200, our get request is successful.
|
||||
// It only happens when the repository is public.
|
||||
if resp.StatusCode == 200 {
|
||||
@@ -38,6 +38,17 @@ func isGitTokenPresent(gitURL giturl.IGitAPI) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Get the error message according to the provider
|
||||
func getProviderError(gitURL giturl.IGitAPI) error {
|
||||
switch gitURL.GetProvider(){
|
||||
case "github":
|
||||
return fmt.Errorf("%w", errors.New("GITHUB_TOKEN is not present"))
|
||||
case "gitlab":
|
||||
return fmt.Errorf("%w", errors.New("GITLAB_TOKEN is not present"))
|
||||
}
|
||||
return fmt.Errorf("%w", errors.New("unable to find the host name"))
|
||||
}
|
||||
|
||||
// cloneRepo clones a repository to a local temporary directory and returns the directory
|
||||
func cloneRepo(gitURL giturl.IGitAPI) (string, error) {
|
||||
|
||||
@@ -60,9 +71,9 @@ func cloneRepo(gitURL giturl.IGitAPI) (string, error) {
|
||||
auth = nil
|
||||
} else {
|
||||
|
||||
// Return Error if the GITHUB_TOKEN is not present
|
||||
// Return Error if the AUTH_TOKEN is not present
|
||||
if isGitTokenPresent := isGitTokenPresent(gitURL); !isGitTokenPresent {
|
||||
return "", fmt.Errorf("%w", errors.New("GITHUB_TOKEN is not present"))
|
||||
return "", getProviderError(gitURL)
|
||||
}
|
||||
auth = &http.BasicAuth{
|
||||
Username: "anything Except Empty String",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user