mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-22 22:03:50 +00:00
Compare commits
470 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
033e8f6b44 | ||
|
|
bef40f0e6c | ||
|
|
aa2f69125f | ||
|
|
d30f3960a7 | ||
|
|
5f43da94ba | ||
|
|
2aa8a0c935 | ||
|
|
c02f8c6cb5 | ||
|
|
aa0be474e2 | ||
|
|
c0161c9b33 | ||
|
|
514da1e2db | ||
|
|
75dfceb5da | ||
|
|
1ae76b4377 | ||
|
|
b6f90cba8e | ||
|
|
62af441a1d | ||
|
|
228b8957d3 | ||
|
|
b4ce999ab3 | ||
|
|
cc06a414fe | ||
|
|
d3c37c4e5f | ||
|
|
3b448b62b1 | ||
|
|
6a3f5658b1 | ||
|
|
f65e791522 | ||
|
|
d91304f9ad | ||
|
|
61ce00108e | ||
|
|
a4eb773eee | ||
|
|
cfc69f5a0f | ||
|
|
a44823c3ed | ||
|
|
8a166e5ba5 | ||
|
|
9a7aeff870 | ||
|
|
cb3bdb9df2 | ||
|
|
0be8d57eaa | ||
|
|
79b9cbf1d6 | ||
|
|
500df8737e | ||
|
|
b8acbd1bee | ||
|
|
0bde8a65ba | ||
|
|
d2884b8936 | ||
|
|
e692359b47 | ||
|
|
473746eab0 | ||
|
|
050878cbd6 | ||
|
|
e100f18bb0 | ||
|
|
05c82fc166 | ||
|
|
839c3e261f | ||
|
|
95b579d191 | ||
|
|
8656715753 | ||
|
|
05b6394c5c | ||
|
|
d3bdbf31ac | ||
|
|
995f615b10 | ||
|
|
392625b774 | ||
|
|
306b9d28ca | ||
|
|
6fe87bba20 | ||
|
|
c0d534072d | ||
|
|
009221aa98 | ||
|
|
46e5aff5f9 | ||
|
|
59498361e7 | ||
|
|
c652da130d | ||
|
|
9e524ffc34 | ||
|
|
004cc0c469 | ||
|
|
bd089d76af | ||
|
|
d5025b54bf | ||
|
|
740497047d | ||
|
|
3f6cbd57b2 | ||
|
|
2c9524ed45 | ||
|
|
384922680a | ||
|
|
d2e9f8f4f8 | ||
|
|
b4f10f854e | ||
|
|
8ce64d2a7f | ||
|
|
d917e21364 | ||
|
|
32cedaf565 | ||
|
|
4c2a5e9a11 | ||
|
|
a41d2a46ff | ||
|
|
4794cbfb36 | ||
|
|
d021217cf7 | ||
|
|
4573d83831 | ||
|
|
2bb612ca3f | ||
|
|
35534112c6 | ||
|
|
f51e531f3a | ||
|
|
2490856ccb | ||
|
|
9a5a87b027 | ||
|
|
45b8c89865 | ||
|
|
e68e6dcd3d | ||
|
|
670ff4a15d | ||
|
|
b616a37800 | ||
|
|
ce488a3645 | ||
|
|
fb47a9c742 | ||
|
|
80ace81a12 | ||
|
|
1efdae5197 | ||
|
|
a4c88edfca | ||
|
|
8f38c2f627 | ||
|
|
bbf68d4ce8 | ||
|
|
e1eec47a22 | ||
|
|
fc05075817 | ||
|
|
5bb64b634a | ||
|
|
7bc2c2be13 | ||
|
|
27e2c044da | ||
|
|
1213e8d6ac | ||
|
|
3f58d68d2a | ||
|
|
803e62020e | ||
|
|
fde437312f | ||
|
|
18425c915b | ||
|
|
0de6892ddd | ||
|
|
dfb92ffec3 | ||
|
|
85317f1ee1 | ||
|
|
f22f60508f | ||
|
|
716bdaaf38 | ||
|
|
1b0e2b87de | ||
|
|
2c57b809d2 | ||
|
|
d9c96db212 | ||
|
|
5f7391a76b | ||
|
|
accd80eda8 | ||
|
|
e49499f085 | ||
|
|
521f8930d7 | ||
|
|
11b9a8eb6e | ||
|
|
0d4350ae24 | ||
|
|
62a6a25aa1 | ||
|
|
14a74e7312 | ||
|
|
3fad2f3430 | ||
|
|
c35d1e8791 | ||
|
|
0367255a2a | ||
|
|
f5f5552ecd | ||
|
|
046a22bd2b | ||
|
|
ad94ac7595 | ||
|
|
cfa3993b79 | ||
|
|
972793b98a | ||
|
|
35682bf5b8 | ||
|
|
b023f592aa | ||
|
|
a1c34646f1 | ||
|
|
9ac3768f1d | ||
|
|
ff7881130f | ||
|
|
37effda7c5 | ||
|
|
0cac7cb1a5 | ||
|
|
8d41d11ca3 | ||
|
|
0ef516d147 | ||
|
|
f57a30898c | ||
|
|
a10c67555d | ||
|
|
14d0df3926 | ||
|
|
c085aeaa68 | ||
|
|
8543afccca | ||
|
|
61b5603a3b | ||
|
|
e3efffb2ec | ||
|
|
fe9a342b42 | ||
|
|
c7668b4436 | ||
|
|
ccdf6b227f | ||
|
|
0aea384f41 | ||
|
|
467059cd26 | ||
|
|
f41af36ea9 | ||
|
|
e2f8902222 | ||
|
|
52bfd4cadc | ||
|
|
7cdc556292 | ||
|
|
039bda9eaf | ||
|
|
a6d73d6f8b | ||
|
|
8e5af59153 | ||
|
|
278467518e | ||
|
|
a7080a5778 | ||
|
|
6a71ef6745 | ||
|
|
10eb576260 | ||
|
|
f14acb79bf | ||
|
|
b8e011bd27 | ||
|
|
f6295308cd | ||
|
|
f981675850 | ||
|
|
93bb7610e6 | ||
|
|
23975ee359 | ||
|
|
14eaedf375 | ||
|
|
ced0b741b9 | ||
|
|
13e805b213 | ||
|
|
c424c1e394 | ||
|
|
77d68bdc73 | ||
|
|
a1555bb9cd | ||
|
|
3ca61b218e | ||
|
|
e7917277e7 | ||
|
|
aa18be17fa | ||
|
|
39c7af5f8d | ||
|
|
a5f7f8bbe4 | ||
|
|
420e491963 | ||
|
|
36f2ff997a | ||
|
|
c33807d052 | ||
|
|
fb3946b64f | ||
|
|
51322e7270 | ||
|
|
3f084d8525 | ||
|
|
b1f4002036 | ||
|
|
bb1cbe0902 | ||
|
|
a095634755 | ||
|
|
1b9ff074af | ||
|
|
f8361446a4 | ||
|
|
5713490f14 | ||
|
|
1ceac2a0a0 | ||
|
|
8a2967a0db | ||
|
|
86297720d5 | ||
|
|
1aeb2b96e2 | ||
|
|
4ee8b9d7f6 | ||
|
|
1d208ed5ec | ||
|
|
3883aaabab | ||
|
|
6fb3c070d0 | ||
|
|
d8d8b4ed73 | ||
|
|
907f46769f | ||
|
|
1ffdb717f7 | ||
|
|
9080603bce | ||
|
|
5796ae9084 | ||
|
|
50636e3a7e | ||
|
|
501d4c9dfc | ||
|
|
84cbc4ae04 | ||
|
|
cbb2a3e46f | ||
|
|
493197c073 | ||
|
|
31a2952101 | ||
|
|
acaccc23e8 | ||
|
|
70e339164d | ||
|
|
0de5d72d75 | ||
|
|
d604cc7faf | ||
|
|
d843a3e359 | ||
|
|
37586662b3 | ||
|
|
193687418f | ||
|
|
72e6bb9537 | ||
|
|
d69e790c61 | ||
|
|
01d41520d4 | ||
|
|
aea9eb9e01 | ||
|
|
26717b13e9 | ||
|
|
5f36417bd9 | ||
|
|
021ea34814 | ||
|
|
4a08fbdf28 | ||
|
|
268753091d | ||
|
|
ec688829b5 | ||
|
|
ec5bf58b0f | ||
|
|
f877d821f0 | ||
|
|
6c22cfef1e | ||
|
|
05305d858b | ||
|
|
e094237bbf | ||
|
|
77eb52bc51 | ||
|
|
c79834cec7 | ||
|
|
aefc5fded7 | ||
|
|
5fd5a5d4fa | ||
|
|
0368ecf7f3 | ||
|
|
d9ec5dcb56 | ||
|
|
030bc6c6b6 | ||
|
|
c1dd2fe0f4 | ||
|
|
4e0851868e | ||
|
|
276178c27c | ||
|
|
3006e6bcbf | ||
|
|
3a50c5686e | ||
|
|
f8eea4d082 | ||
|
|
8a42d77990 | ||
|
|
3980d1a9b0 | ||
|
|
53741ec26e | ||
|
|
c398cf46c9 | ||
|
|
e869ce4a64 | ||
|
|
4064be6577 | ||
|
|
1f00cf4151 | ||
|
|
bae0ca62b8 | ||
|
|
b7a51a2495 | ||
|
|
4f6a3e39d0 | ||
|
|
528f6b7402 | ||
|
|
c252f29e6d | ||
|
|
fea84c9652 | ||
|
|
9b9940f708 | ||
|
|
a34ab17307 | ||
|
|
477a3e7263 | ||
|
|
f94c9496df | ||
|
|
1c31281b7b | ||
|
|
0e5204ecb4 | ||
|
|
f3dc6235d7 | ||
|
|
37cdf1a19e | ||
|
|
1fb642c777 | ||
|
|
8f791ceb12 | ||
|
|
f40eaa0f56 | ||
|
|
cb34d17ba1 | ||
|
|
328ba82007 | ||
|
|
010ed1b047 | ||
|
|
5a81a77d92 | ||
|
|
c7ea10d206 | ||
|
|
a37d00b40a | ||
|
|
0168b768d2 | ||
|
|
9a85b57ba4 | ||
|
|
eafece6497 | ||
|
|
8f08271664 | ||
|
|
da0271e624 | ||
|
|
94f52fb4ac | ||
|
|
524c2922a4 | ||
|
|
0891d64654 | ||
|
|
d1c23f7442 | ||
|
|
8cbbe35f24 | ||
|
|
a21e9d706e | ||
|
|
57160c4d04 | ||
|
|
8b46a49e23 | ||
|
|
c11ebb49f7 | ||
|
|
e4c3935a1b | ||
|
|
ade062fdd3 | ||
|
|
b0f6357482 | ||
|
|
38a9c11286 | ||
|
|
0d95f02e60 | ||
|
|
1c30528eea | ||
|
|
d1b116d314 | ||
|
|
9d20fd41a8 | ||
|
|
54648bb973 | ||
|
|
fc4edb12f9 | ||
|
|
9a1b8d7ce2 | ||
|
|
6909975503 | ||
|
|
5d94bd990a | ||
|
|
67c8719f34 | ||
|
|
d5b60c6ac8 | ||
|
|
a99d2e9e26 | ||
|
|
5c7d89cb9e | ||
|
|
ae7810f0d3 | ||
|
|
5a90dc46f0 | ||
|
|
294f886588 | ||
|
|
17aec665cf | ||
|
|
959b25e8b7 | ||
|
|
9fd2bf3480 | ||
|
|
7b061a4e51 | ||
|
|
4fcd89390b | ||
|
|
667ffe9cd3 | ||
|
|
6f4086cd8c | ||
|
|
2a45a1a400 | ||
|
|
eee201de1e | ||
|
|
6be24bd22a | ||
|
|
ca927dec30 | ||
|
|
3a78ef46a3 | ||
|
|
bdb1cd0905 | ||
|
|
ffb556a637 | ||
|
|
40acfb5e9d | ||
|
|
de8bcfa0d2 | ||
|
|
9439f407da | ||
|
|
5095e62961 | ||
|
|
3301907864 | ||
|
|
151175c40f | ||
|
|
234d4fa537 | ||
|
|
f384e8a6e3 | ||
|
|
66068757e1 | ||
|
|
8a7cda5dd1 | ||
|
|
8e67104ba4 | ||
|
|
0c9da9ddc8 | ||
|
|
a5ef6aa126 | ||
|
|
c133b7a2c2 | ||
|
|
a0ca68cc41 | ||
|
|
41cae0bc93 | ||
|
|
b4198fde8c | ||
|
|
bd24f35738 | ||
|
|
6fcbb757b5 | ||
|
|
3b8825e5d2 | ||
|
|
5cf3244918 | ||
|
|
934c9ccc8b | ||
|
|
41dfdfd1e8 | ||
|
|
427fb59c99 | ||
|
|
ae825800f6 | ||
|
|
d72700acf6 | ||
|
|
3310a6a26f | ||
|
|
740b5aa772 | ||
|
|
04b55e764a | ||
|
|
beb4062bb1 | ||
|
|
5d4cd4acdc | ||
|
|
aec8198131 | ||
|
|
0a850e47df | ||
|
|
5544820c5e | ||
|
|
4f466d517a | ||
|
|
cd0f20ca2f | ||
|
|
b3661848dc | ||
|
|
548201c256 | ||
|
|
a54e5d9f8b | ||
|
|
536257afa1 | ||
|
|
5a71c3270a | ||
|
|
d194dd173f | ||
|
|
be03a9e984 | ||
|
|
a90177e7c0 | ||
|
|
be9e8ca47d | ||
|
|
eb9fe85c75 | ||
|
|
47183c405f | ||
|
|
2725923b9b | ||
|
|
f6c03ed7a2 | ||
|
|
76b5548216 | ||
|
|
cc57a34a32 | ||
|
|
f7099b62e6 | ||
|
|
093ee8916e | ||
|
|
ac0259157b | ||
|
|
9cb937798f | ||
|
|
11d4926c85 | ||
|
|
836211ae2b | ||
|
|
fddf3d3f58 | ||
|
|
b036d1079e | ||
|
|
137c39e918 | ||
|
|
8778d022cf | ||
|
|
043bdbacec | ||
|
|
e0e19b0258 | ||
|
|
c72e0f790a | ||
|
|
87e79110a2 | ||
|
|
faeae1af60 | ||
|
|
b371fbad01 | ||
|
|
90831e153d | ||
|
|
009d8275c1 | ||
|
|
05c88e0ffc | ||
|
|
7d12552932 | ||
|
|
b761505bb1 | ||
|
|
63367f4f31 | ||
|
|
6f9d6b4af3 | ||
|
|
6ed8287b01 | ||
|
|
d948e20682 | ||
|
|
42929dac58 | ||
|
|
74449c64a2 | ||
|
|
0bd164c69e | ||
|
|
d44c082134 | ||
|
|
d756b9bfe4 | ||
|
|
6144050212 | ||
|
|
b5fe456b0d | ||
|
|
37791ff391 | ||
|
|
c2d99163a6 | ||
|
|
d948353b99 | ||
|
|
2649cb75f6 | ||
|
|
7c8da4a4b9 | ||
|
|
b72f5d75f7 | ||
|
|
947826a764 | ||
|
|
906c69a86d | ||
|
|
9c65aadcc7 | ||
|
|
70a9a7bbbd | ||
|
|
09879e00ba | ||
|
|
5cbb7d940d | ||
|
|
050976d7a3 | ||
|
|
6a96d9f8b5 | ||
|
|
8c55dfbcf6 | ||
|
|
78c5b49b5a | ||
|
|
c33ca04a4f | ||
|
|
f12e66d315 | ||
|
|
8e4bb36df8 | ||
|
|
034412f6fe | ||
|
|
88d83ba72d | ||
|
|
829e7a33aa | ||
|
|
1dcde1538e | ||
|
|
b876cd0975 | ||
|
|
a06d11dc17 | ||
|
|
b1f5cd45c4 | ||
|
|
2c44c0e1f0 | ||
|
|
37c429b264 | ||
|
|
82994ce754 | ||
|
|
621f64c363 | ||
|
|
419a77f144 | ||
|
|
f043358a59 | ||
|
|
26a64b788b | ||
|
|
89a05d247b | ||
|
|
718d549bb3 | ||
|
|
54a6a8324a | ||
|
|
7faf24cf88 | ||
|
|
7bebc7a814 | ||
|
|
8647a087dd | ||
|
|
78670665c4 | ||
|
|
1dd587cd83 | ||
|
|
d69cccf821 | ||
|
|
8ae3b9c28f | ||
|
|
9b6ad102b1 | ||
|
|
e6787b77fb | ||
|
|
46449045a6 | ||
|
|
d81984b4c6 | ||
|
|
22b463f306 | ||
|
|
475e45b848 | ||
|
|
32fac97d21 | ||
|
|
f9f32a1062 | ||
|
|
c90c3bbd05 | ||
|
|
cbae9a087b | ||
|
|
4da199c43e | ||
|
|
00d8660a91 | ||
|
|
76c2f6afe0 | ||
|
|
efa53bd83c | ||
|
|
01f6a1e1c0 | ||
|
|
9f78703dee | ||
|
|
1a0e96338e | ||
|
|
eff4690e0e | ||
|
|
266480c234 | ||
|
|
a922d01005 | ||
|
|
0d9711c8bb | ||
|
|
edab68f4fb | ||
|
|
08f04e19ef | ||
|
|
e62234a6ac | ||
|
|
b1276d56f7 | ||
|
|
b53bf320a6 | ||
|
|
e561f78ada | ||
|
|
8e950e0f54 | ||
|
|
0adb9dd540 |
45
.github/workflows/build.yaml
vendored
45
.github/workflows/build.yaml
vendored
@@ -16,8 +16,8 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: v1.0.${{ github.run_number }}
|
||||
release_name: Release v1.0.${{ github.run_number }}
|
||||
tag_name: v2.0.${{ github.run_number }}
|
||||
release_name: Release v2.0.${{ github.run_number }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
build:
|
||||
@@ -29,7 +29,6 @@ jobs:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
@@ -40,13 +39,19 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v1.0.${{ github.run_number }}
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
ArmoBEServer: api.armo.cloud
|
||||
ArmoERServer: report.armo.cloud
|
||||
ArmoWebsite: portal.armo.cloud
|
||||
CGO_ENABLED: 0
|
||||
run: python build.py
|
||||
run: python3 --version && python3 build.py
|
||||
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: python3 smoke_testing/init.py ${PWD}/build/${{ matrix.os }}/kubescape
|
||||
|
||||
- name: Upload Release binaries
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
@@ -57,3 +62,33 @@ jobs:
|
||||
asset_path: build/${{ matrix.os }}/kubescape
|
||||
asset_name: kubescape-${{ matrix.os }}
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
|
||||
build-docker:
|
||||
name: Build docker container, tag and upload to registry
|
||||
needs: build
|
||||
if: ${{ github.repository == 'armosec/kubescape' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set name
|
||||
run: echo quay.io/armosec/kubescape:v2.0.${{ github.run_number }} > build_tag.txt
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file build/Dockerfile --tag $(cat build_tag.txt) --build-arg run_number=${{ github.run_number }}
|
||||
|
||||
- name: Re-Tag Image to latest
|
||||
run: docker tag $(cat build_tag.txt) quay.io/armosec/kubescape:latest
|
||||
|
||||
- name: Login to Quay.io
|
||||
env: # Or as an environment variable
|
||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
docker push $(cat build_tag.txt)
|
||||
docker push quay.io/armosec/kubescape:latest
|
||||
|
||||
|
||||
41
.github/workflows/build_dev.yaml
vendored
41
.github/workflows/build_dev.yaml
vendored
@@ -3,9 +3,6 @@ name: build-dev
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
pull_request:
|
||||
branches: [ dev ]
|
||||
types: [ closed ]
|
||||
jobs:
|
||||
build:
|
||||
name: Create cross-platform dev build
|
||||
@@ -26,15 +23,49 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v1.0.${{ github.run_number }}
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
ArmoBEServer: api.armo.cloud
|
||||
ArmoERServer: report.euprod1.cyberarmorsoft.com
|
||||
ArmoWebsite: portal.armo.cloud
|
||||
CGO_ENABLED: 0
|
||||
run: mkdir -p build/${{ matrix.os }} && go mod tidy && go build -ldflags "-w -s -X github.com/armosec/kubescape/cmd.BuildNumber=$RELEASE -X github.com/armosec/kubescape/cautils/getter.ArmoBEURL=$ArmoBEServer -X github.com/armosec/kubescape/cautils/getter.ArmoERURL=$ArmoERServer -X github.com/armosec/kubescape/cautils/getter.ArmoFEURL=$ArmoWebsite" -o build/${{ matrix.os }}/kubescape # && md5sum build/${{ matrix.os }}/kubescape > build/${{ matrix.os }}/kubescape.md5
|
||||
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 == 'armosec/kubescape' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set name
|
||||
run: echo quay.io/armosec/kubescape:dev-v2.0.${{ github.run_number }} > build_tag.txt
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file build/Dockerfile --tag $(cat build_tag.txt) --build-arg run_number=${{ github.run_number }}
|
||||
|
||||
- name: Login to Quay.io
|
||||
env: # Or as an environment variable
|
||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
docker push $(cat build_tag.txt)
|
||||
|
||||
|
||||
|
||||
15
.github/workflows/master_pr_checks.yaml
vendored
15
.github/workflows/master_pr_checks.yaml
vendored
@@ -24,15 +24,16 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v1.0.${{ github.run_number }}
|
||||
RELEASE: v2.0.${{ github.run_number }}
|
||||
ArmoBEServer: api.armo.cloud
|
||||
ArmoERServer: report.armo.cloud
|
||||
ArmoWebsite: portal.armo.cloud
|
||||
CGO_ENABLED: 0
|
||||
run: python build.py
|
||||
run: python3 --version && python3 build.py
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: kubescape-${{ matrix.os }}
|
||||
path: build/${{ matrix.os }}/kubescape
|
||||
- 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
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
*.vs*
|
||||
*kubescape*
|
||||
*debug*
|
||||
*vender*
|
||||
*.pyc*
|
||||
.idea
|
||||
127
CODE_OF_CONDUCT.md
Normal file
127
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Contributor Covenant 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.
|
||||
@@ -19,7 +19,8 @@ Please note we have a code of conduct, please follow it in all your interactions
|
||||
build.
|
||||
2. Update the README.md with details of changes to the interface, this includes new environment
|
||||
variables, exposed ports, useful file locations and container parameters.
|
||||
3. We will merge the Pull Request in once you have the sign-off.
|
||||
3. Open Pull Request to `dev` branch - we test the component before merging into the `master` branch
|
||||
4. We will merge the Pull Request in once you have the sign-off.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
|
||||
9
MAINTAINERS.md
Normal file
9
MAINTAINERS.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Maintainers
|
||||
|
||||
The following table lists Kubescape project maintainers
|
||||
|
||||
| Name | GitHub | Email | Organization | Repositories/Area of Expertise | Added/Renewed On |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Ben Hirschberg | @slashben | ben@armosec.io | ARMO | Kubescape CLI | 2021-09-01 |
|
||||
| Rotem Refael | @rotemamsa | rrefael@armosec.io | ARMO | Kubescape CLI | 2021-10-11 |
|
||||
| David Wertenteil | @dwertent | dwertent@armosec.io | ARMO | Kubescape CLI | 2021-09-01 |
|
||||
189
README.md
189
README.md
@@ -3,9 +3,12 @@
|
||||
[](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
|
||||
[](https://goreportcard.com/report/github.com/armosec/kubescape)
|
||||
|
||||
Kubescape is the first tool for testing if Kubernetes is deployed securely as defined in [Kubernetes Hardening Guidance by NSA and CISA](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
|
||||
Kubescape is a K8s open-source tool providing a multi-cloud K8s 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) , [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/)), software vulnerabilities, and RBAC (role-based-access-control) violations at early stages of the CI/CD pipeline, calculates risk score instantly and shows risk trends over time.
|
||||
It became 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.
|
||||
|
||||
Use Kubescape to test clusters or scan single YAML files and integrate it to your processes.
|
||||
</br>
|
||||
|
||||
<img src="docs/demo.gif">
|
||||
|
||||
@@ -21,15 +24,22 @@ curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh |
|
||||
|
||||
## Run:
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
kubescape scan --submit --enable-host-scan
|
||||
```
|
||||
|
||||
If you wish to scan all namespaces in your cluster, remove the `--exclude-namespaces` flag.
|
||||
|
||||
<img src="docs/summary.png">
|
||||
|
||||
</br>
|
||||
|
||||
> 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 themselves more robust and complete as Kubernetes develops.
|
||||
|
||||
</br>
|
||||
|
||||
### Click [👍](https://github.com/armosec/kubescape/stargazers) if you want us to continue to develop and improve Kubescape 😀
|
||||
|
||||
</br>
|
||||
|
||||
|
||||
# Being part of the team
|
||||
|
||||
We invite you to our team! We are excited about this project and want to return the love we get.
|
||||
@@ -41,8 +51,20 @@ Want to contribute? Want to discuss something? Have an issue?
|
||||
|
||||
[<img src="docs/discord-banner.png" width="100" alt="logo" align="center">](https://armosec.github.io/kubescape/)
|
||||
|
||||
|
||||
# Options and examples
|
||||
|
||||
## Playground
|
||||
* [Kubescape playground](https://www.katacoda.com/pathaksaiyam/scenarios/kubescape)
|
||||
|
||||
## Tutorials
|
||||
|
||||
* [Overview](https://youtu.be/wdBkt_0Qhbg)
|
||||
* [How To Secure Kubernetes Clusters With Kubescape And Armo](https://youtu.be/ZATGiDIDBQk)
|
||||
* [Scanning Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
|
||||
* [Scan Kubescape on an air-gapped environment (offline support)](https://youtu.be/IGXL9s37smM)
|
||||
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
|
||||
|
||||
## Install on Windows
|
||||
|
||||
**Requires powershell v5.0+**
|
||||
@@ -68,79 +90,113 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser
|
||||
|
||||
## Flags
|
||||
|
||||
| flag | default | description | options |
|
||||
| --- | --- | --- | --- |
|
||||
| `-e`/`--exclude-namespaces` | Scan all namespaces | Namespaces to exclude from scanning. Recommended to exclude `kube-system` and `kube-public` namespaces |
|
||||
| `-s`/`--silent` | Display progress messages | Silent progress messages |
|
||||
| `-t`/`--fail-threshold` | `0` (do not fail) | fail command (return exit code 1) if result bellow threshold| `0` -> `100` |
|
||||
| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit` |
|
||||
| `-o`/`--output` | print to stdout | Save scan result in file |
|
||||
| `--use-from` | | Load local framework object from specified path. If not used will download latest |
|
||||
| `--use-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` |
|
||||
| `--exceptions` | | Path to an [exceptions obj](examples/exceptions.json). If not set will download exceptions from Armo management portal |
|
||||
| `--submit` | `false` | If set, Kubescape will 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 sent | `true`/`false`|
|
||||
| `--keep-local` | `false` | Kubescape will not send scan results to Armo management portal. Use this flag if you ran with the `--submit` flag in the past and you do not want to submit your current scan results | `true`/`false`|
|
||||
| `--account` | | Armo portal account ID. Default will load account ID from configMap or config file | |
|
||||
| flag | default | description | options |
|
||||
|-----------------------------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------|
|
||||
| `-e`/`--exclude-namespaces` | Scan all namespaces | Namespaces to exclude from scanning. Recommended to exclude `kube-system` and `kube-public` namespaces | |
|
||||
| `--include-namespaces` | Scan all namespaces | Scan specific namespaces | |
|
||||
| `-s`/`--silent` | Display progress messages | Silent progress messages | |
|
||||
| `-t`/`--fail-threshold` | `100` (do not fail) | fail command (return exit code 1) if result is above threshold | `0` -> `100` |
|
||||
| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit`/`prometheus` |
|
||||
| `-o`/`--output` | print to stdout | Save scan result in file | |
|
||||
| `--use-from` | | Load local framework object from specified path. If not used will download latest |
|
||||
| `--use-artifacts-from` | | Load artifacts (frameworks, control-config, exceptions) from local directory. If not used will download them | |
|
||||
| `--use-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` |
|
||||
| `--exceptions` | | Path to an [exceptions obj](examples/exceptions.json). If not set will download exceptions from Armo management portal |
|
||||
| `--controls-config` | | Path to a controls-config obj. If not set will download controls-config from ARMO management portal | |
|
||||
| `--submit` | `false` | If set, Kubescape will 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 sent | `true`/`false` |
|
||||
| `--keep-local` | `false` | Kubescape will not send scan results to Armo management portal. Use this flag if you ran with the `--submit` flag in the past and you do not want to submit your current scan results | `true`/`false` |
|
||||
| `--account` | | Armo portal account ID. Default will load account ID from configMap or config file | |
|
||||
| `--kube-context` | current-context | Cluster context to scan | |
|
||||
| `--verbose` | `false` | Display all of the input resources and not only failed resources | `true`/`false` |
|
||||
|
||||
|
||||
## Usage & Examples
|
||||
|
||||
### Examples
|
||||
|
||||
* Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework and submit results to [Armo portal](https://portal.armo.cloud/)
|
||||
#### 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://portal.armo.cloud/)
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --submit
|
||||
kubescape scan framework nsa --submit
|
||||
```
|
||||
|
||||
|
||||
* Scan a running Kubernetes cluster with [`mitre`](https://www.microsoft.com/security/blog/2020/04/02/attack-matrix-kubernetes/) framework and submit results to [Armo portal](https://portal.armo.cloud/)
|
||||
#### 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://portal.armo.cloud/)
|
||||
```
|
||||
kubescape scan framework mitre --exclude-namespaces kube-system,kube-public --submit
|
||||
kubescape scan framework mitre --submit
|
||||
```
|
||||
|
||||
* Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI)
|
||||
|
||||
#### Scan a running Kubernetes cluster with a specific control using the control name or control ID. [List of controls](https://hub.armo.cloud/docs/controls)
|
||||
```
|
||||
kubescape scan control "Privileged container"
|
||||
```
|
||||
|
||||
#### Scan specific namespaces
|
||||
```
|
||||
kubescape scan framework nsa --include-namespaces development,staging,production
|
||||
```
|
||||
|
||||
#### Scan cluster and exclude some namespaces
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
```
|
||||
|
||||
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI)
|
||||
```
|
||||
kubescape scan framework nsa *.yaml
|
||||
```
|
||||
|
||||
|
||||
* Scan `yaml`/`json` files from url
|
||||
#### Scan kubernetes manifest files from a public github repository
|
||||
```
|
||||
kubescape scan framework nsa https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/master/release/kubernetes-manifests.yaml
|
||||
kubescape scan framework nsa https://github.com/armosec/kubescape
|
||||
```
|
||||
|
||||
* Output in `json` format
|
||||
#### Display all scanned resources (including the resources who passed)
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format json --output results.json
|
||||
kubescape scan framework nsa --verbose
|
||||
```
|
||||
|
||||
* Output in `junit xml` format
|
||||
#### Output in `json` format
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format junit --output results.xml
|
||||
kubescape scan framework nsa --format json --output results.json
|
||||
```
|
||||
|
||||
* Scan with exceptions, objects with exceptions will be presented as `warning` and not `fail` <img src="docs/new-feature.svg">
|
||||
#### Output in `junit xml` format
|
||||
```
|
||||
kubescape scan framework nsa --exceptions examples/exceptions.json
|
||||
kubescape scan framework nsa --format junit --output results.xml
|
||||
```
|
||||
|
||||
### Helm Support
|
||||
#### Output in `prometheus` metrics format - Contributed by [@Joibel](https://github.com/Joibel)
|
||||
```
|
||||
kubescape scan framework nsa --format prometheus
|
||||
```
|
||||
|
||||
* Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout
|
||||
#### Scan with exceptions, objects with exceptions will be presented as `exclude` and not `fail`
|
||||
[Full documentation](examples/exceptions/README.md)
|
||||
```
|
||||
kubescape scan framework nsa --exceptions examples/exceptions/exclude-kube-namespaces.json
|
||||
```
|
||||
|
||||
#### Scan Helm charts - Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout
|
||||
```
|
||||
helm template [NAME] [CHART] [flags] --dry-run | kubescape scan framework nsa -
|
||||
```
|
||||
|
||||
for example:
|
||||
e.g.
|
||||
```
|
||||
helm template bitnami/mysql --generate-name --dry-run | kubescape scan framework nsa -
|
||||
```
|
||||
|
||||
|
||||
### Offline Support
|
||||
|
||||
[Video tutorial](https://youtu.be/IGXL9s37smM)
|
||||
|
||||
It is possible to run Kubescape offline!
|
||||
|
||||
First download the framework and then scan with `--use-from` flag
|
||||
|
||||
1. Download and save in file, if file name not specified, will store save to `~/.kubescape/<framework name>.json`
|
||||
1. Download and save in file, if file name not specified, will save in `~/.kubescape/<framework name>.json`
|
||||
```
|
||||
kubescape download framework nsa --output nsa.json
|
||||
```
|
||||
@@ -150,13 +206,53 @@ kubescape download framework nsa --output nsa.json
|
||||
kubescape scan framework nsa --use-from nsa.json
|
||||
```
|
||||
|
||||
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 themselves more robust and complete as Kubernetes develops.
|
||||
|
||||
|
||||
You can also download all artifacts to a local path and then load them using `--use-artifacts-from` flag
|
||||
|
||||
1. Download and save in local directory, if path not specified, will save all in `~/.kubescape`
|
||||
```
|
||||
kubescape download artifacts --output path/to/local/dir
|
||||
```
|
||||
|
||||
2. Scan using the downloaded artifacts
|
||||
```
|
||||
kubescape scan framework nsa --use-artifacts-from path/to/local/dir
|
||||
```
|
||||
|
||||
## Scan Periodically using Helm - Contributed by [@yonahd](https://github.com/yonahd)
|
||||
|
||||
You can scan your cluster periodically by adding a `CronJob` that will repeatedly trigger kubescape
|
||||
|
||||
```
|
||||
helm install kubescape examples/helm_chart/
|
||||
```
|
||||
|
||||
## Scan using docker image
|
||||
|
||||
Official Docker image `quay.io/armosec/kubescape`
|
||||
|
||||
```
|
||||
docker run -v "$(pwd)/example.yaml:/app/example.yaml quay.io/armosec/kubescape scan framework nsa /app/example.yaml
|
||||
```
|
||||
|
||||
# Submit data manually
|
||||
|
||||
Use the `submit` command if you wish to submit data manually
|
||||
|
||||
## Submit scan results manually
|
||||
|
||||
First, scan your cluster using the `json` format flag: `kubescape scan framework <name> --format json --output path/to/results.json`.
|
||||
|
||||
Now you can submit the results to the Kubaescape SaaS version -
|
||||
```
|
||||
kubescape submit results path/to/results.json
|
||||
```
|
||||
# How to build
|
||||
|
||||
## Build using python script
|
||||
## Build using python (3.7^) script
|
||||
|
||||
Kubescpae can be built using:
|
||||
Kubescape can be built using:
|
||||
|
||||
``` sh
|
||||
python build.py
|
||||
@@ -187,12 +283,14 @@ go build -o kubescape .
|
||||
|
||||
3. Run
|
||||
```
|
||||
./kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
./kubescape scan --submit --enable-host-scan
|
||||
```
|
||||
|
||||
4. Enjoy :zany_face:
|
||||
|
||||
## How to build in Docker
|
||||
## Docker Build
|
||||
|
||||
### Build your own Docker image
|
||||
|
||||
1. Clone Project
|
||||
```
|
||||
@@ -204,10 +302,11 @@ git clone https://github.com/armosec/kubescape.git kubescape && cd "$_"
|
||||
docker build -t kubescape -f build/Dockerfile .
|
||||
```
|
||||
|
||||
|
||||
# Under the hood
|
||||
|
||||
## Tests
|
||||
Kubescape is running the following tests according to what is defined by [Kubernetes Hardening Guidance by NSA and CISA](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
|
||||
Kubescape is running the following tests according to what is defined by [Kubernetes Hardening Guidance by NSA and CISA](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
|
||||
* Non-root containers
|
||||
* Immutable container filesystem
|
||||
* Privileged containers
|
||||
@@ -240,3 +339,9 @@ The tools retrieves Kubernetes objects from the API server and runs a set of [re
|
||||
The results by default printed in a pretty "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 themselves more robust and complete as Kubernetes develops.
|
||||
|
||||
## Thanks to all the contributors ❤️
|
||||
<a href = "https://github.com/armosec/kubescape/graphs/contributors">
|
||||
<img src = "https://contrib.rocks/image?repo=armosec/kubescape"/>
|
||||
</a>
|
||||
|
||||
|
||||
8
build.py
8
build.py
@@ -41,7 +41,7 @@ def main():
|
||||
|
||||
# Set some variables
|
||||
packageName = getPackageName()
|
||||
buildUrl = "github.com/armosec/kubescape/cmd.BuildNumber"
|
||||
buildUrl = "github.com/armosec/kubescape/cautils.BuildNumber"
|
||||
releaseVersion = os.getenv("RELEASE")
|
||||
ArmoBEServer = os.getenv("ArmoBEServer")
|
||||
ArmoERServer = os.getenv("ArmoERServer")
|
||||
@@ -59,8 +59,7 @@ def main():
|
||||
ER_SERVER_CONST, ArmoERServer, WEBSITE_CONST, ArmoWebsite)
|
||||
status = subprocess.call(["go", "build", "-o", "%s/%s" % (buildDir, packageName), "-ldflags" ,ldflags])
|
||||
checkStatus(status, "Failed to build kubescape")
|
||||
|
||||
|
||||
|
||||
sha1 = hashlib.sha1()
|
||||
with open(buildDir + "/" + packageName, "rb") as kube:
|
||||
sha1.update(kube.read())
|
||||
@@ -68,6 +67,7 @@ def main():
|
||||
kube_sha.write(sha1.hexdigest())
|
||||
|
||||
print("Build Done")
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
FROM golang:1.17-alpine as builder
|
||||
ENV GOPROXY=https://goproxy.io,direct
|
||||
ENV GO111MODULE=on
|
||||
#ENV GOPROXY=https://goproxy.io,direct
|
||||
|
||||
ARG run_number
|
||||
|
||||
ENV RELEASE=v1.0.${run_number}
|
||||
|
||||
ENV GO111MODULE=
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
# Install required python/pip
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
|
||||
RUN python3 -m ensurepip
|
||||
RUN pip3 install --no-cache --upgrade pip setuptools
|
||||
|
||||
WORKDIR /work
|
||||
ADD . .
|
||||
RUN GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w " -installsuffix cgo -o kubescape .
|
||||
|
||||
RUN python build.py
|
||||
|
||||
RUN ls -ltr build/ubuntu-latest
|
||||
RUN cat /work/build/ubuntu-latest/kubescape.sha1
|
||||
|
||||
FROM alpine
|
||||
COPY --from=builder /work/kubescape /usr/bin/kubescape
|
||||
COPY --from=builder /work/build/ubuntu-latest/kubescape /usr/bin/kubescape
|
||||
|
||||
# # Download the frameworks. Use the "--use-default" flag when running kubescape
|
||||
# RUN kubescape download framework nsa && kubescape download framework mitre
|
||||
|
||||
CMD ["kubescape"]
|
||||
ENTRYPOINT ["kubescape"]
|
||||
|
||||
@@ -4,21 +4,17 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
configMapName = "kubescape"
|
||||
configFileName = "config"
|
||||
)
|
||||
const configFileName = "config"
|
||||
|
||||
func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName + ".json") }
|
||||
|
||||
@@ -30,6 +26,7 @@ type ConfigObj struct {
|
||||
CustomerGUID string `json:"customerGUID"`
|
||||
Token string `json:"invitationParam"`
|
||||
CustomerAdminEMail string `json:"adminMail"`
|
||||
ClusterName string `json:"clusterName"`
|
||||
}
|
||||
|
||||
func (co *ConfigObj) Json() []byte {
|
||||
@@ -39,152 +36,103 @@ func (co *ConfigObj) Json() []byte {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
// Config - convert ConfigObj to config file
|
||||
func (co *ConfigObj) Config() []byte {
|
||||
clusterName := co.ClusterName
|
||||
co.ClusterName = "" // remove cluster name before saving to file
|
||||
b, err := json.Marshal(co)
|
||||
co.ClusterName = clusterName
|
||||
|
||||
if err == nil {
|
||||
return b
|
||||
}
|
||||
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// =============================== interface ============================================
|
||||
// ======================================================================================
|
||||
type IClusterConfig interface {
|
||||
// setters
|
||||
SetCustomerGUID(customerGUID string) error
|
||||
type ITenantConfig interface {
|
||||
// set
|
||||
SetTenant() error
|
||||
|
||||
// getters
|
||||
GetClusterName() string
|
||||
GetCustomerGUID() string
|
||||
GetConfigObj() *ConfigObj
|
||||
GetK8sAPI() *k8sinterface.KubernetesApi
|
||||
GetBackendAPI() getter.IBackend
|
||||
GetDefaultNS() string
|
||||
GenerateURL()
|
||||
}
|
||||
// GetBackendAPI() getter.IBackend
|
||||
// GenerateURL()
|
||||
|
||||
// ClusterConfigSetup - Setup the desired cluster behavior regarding submittion to the Armo BE
|
||||
func ClusterConfigSetup(scanInfo *ScanInfo, k8s *k8sinterface.KubernetesApi, beAPI getter.IBackend) IClusterConfig {
|
||||
/*
|
||||
|
||||
If "First run (local config not found)" -
|
||||
Default - Do not send report (local)
|
||||
Local - Do not send report
|
||||
Submit - Create tenant & Submit report
|
||||
|
||||
If "Submitted but not signed up" -
|
||||
Default - Delete local config & Do not send report (local)
|
||||
Local - Delete local config & Do not send report
|
||||
Submit - Submit report
|
||||
|
||||
If "Signed up user" -
|
||||
Default - Submit report (submit)
|
||||
Local - Do not send report
|
||||
Submit - Submit report
|
||||
|
||||
*/
|
||||
clusterConfig := NewClusterConfig(k8s, beAPI)
|
||||
clusterConfig.LoadConfig()
|
||||
|
||||
if !IsSubmitted(clusterConfig) {
|
||||
if scanInfo.Submit {
|
||||
return clusterConfig // submit - Create tenant & Submit report
|
||||
}
|
||||
return NewEmptyConfig() // local/default - Do not send report
|
||||
}
|
||||
if !IsRegistered(clusterConfig) {
|
||||
if scanInfo.Submit {
|
||||
return clusterConfig // submit/default - Submit report
|
||||
}
|
||||
DeleteConfig(k8s)
|
||||
return NewEmptyConfig() // local - Delete local config & Do not send report
|
||||
}
|
||||
if scanInfo.Local {
|
||||
return NewEmptyConfig() // local - Do not send report
|
||||
}
|
||||
return clusterConfig // submit/default - Submit report
|
||||
IsConfigFound() bool
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// ============================= Mock Config ============================================
|
||||
// ============================ Local Config ============================================
|
||||
// ======================================================================================
|
||||
type EmptyConfig struct {
|
||||
}
|
||||
|
||||
func NewEmptyConfig() *EmptyConfig { return &EmptyConfig{} }
|
||||
func (c *EmptyConfig) GetConfigObj() *ConfigObj { return &ConfigObj{} }
|
||||
func (c *EmptyConfig) SetCustomerGUID(customerGUID string) error { return nil }
|
||||
func (c *EmptyConfig) GetCustomerGUID() string { return "" }
|
||||
func (c *EmptyConfig) GetK8sAPI() *k8sinterface.KubernetesApi { return nil } // TODO: return mock obj
|
||||
func (c *EmptyConfig) GetDefaultNS() string { return k8sinterface.GetDefaultNamespace() }
|
||||
func (c *EmptyConfig) GetBackendAPI() getter.IBackend { return nil } // TODO: return mock obj
|
||||
func (c *EmptyConfig) GenerateURL() {
|
||||
message := fmt.Sprintf("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 registering here: https://%s", getter.ArmoFEURL)
|
||||
InfoTextDisplay(os.Stdout, message+"\n")
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// ========================== Cluster Config ============================================
|
||||
// ======================================================================================
|
||||
|
||||
type ClusterConfig struct {
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
defaultNS string
|
||||
// Config when scanning YAML files or URL but not a Kubernetes cluster
|
||||
type LocalConfig struct {
|
||||
backendAPI getter.IBackend
|
||||
configObj *ConfigObj
|
||||
}
|
||||
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend) *ClusterConfig {
|
||||
return &ClusterConfig{
|
||||
k8s: k8s,
|
||||
func NewLocalConfig(backendAPI getter.IBackend, customerGUID, clusterName string) *LocalConfig {
|
||||
var configObj *ConfigObj
|
||||
|
||||
lc := &LocalConfig{
|
||||
backendAPI: backendAPI,
|
||||
configObj: &ConfigObj{},
|
||||
defaultNS: k8sinterface.GetDefaultNamespace(),
|
||||
}
|
||||
// get from configMap
|
||||
if existsConfigFile() { // get from file
|
||||
configObj, _ = loadConfigFromFile()
|
||||
} else {
|
||||
configObj = &ConfigObj{}
|
||||
}
|
||||
if configObj != nil {
|
||||
lc.configObj = configObj
|
||||
}
|
||||
if customerGUID != "" {
|
||||
lc.configObj.CustomerGUID = customerGUID // override config customerGUID
|
||||
}
|
||||
if clusterName != "" {
|
||||
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
||||
}
|
||||
if lc.configObj.CustomerGUID != "" {
|
||||
if err := lc.SetTenant(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
return lc
|
||||
}
|
||||
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
|
||||
func (c *ClusterConfig) GetK8sAPI() *k8sinterface.KubernetesApi { return c.k8s }
|
||||
func (c *ClusterConfig) GetDefaultNS() string { return c.defaultNS }
|
||||
func (c *ClusterConfig) GetBackendAPI() getter.IBackend { return c.backendAPI }
|
||||
|
||||
func (c *ClusterConfig) GenerateURL() {
|
||||
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = getter.ArmoFEURL
|
||||
if c.configObj == nil {
|
||||
return
|
||||
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
|
||||
func (lc *LocalConfig) GetCustomerGUID() string { return lc.configObj.CustomerGUID }
|
||||
func (lc *LocalConfig) SetCustomerGUID(customerGUID string) { lc.configObj.CustomerGUID = customerGUID }
|
||||
func (lc *LocalConfig) GetClusterName() string { return lc.configObj.ClusterName }
|
||||
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
|
||||
func (lc *LocalConfig) SetTenant() error {
|
||||
// ARMO tenant GUID
|
||||
if err := getTenantConfigFromBE(lc.backendAPI, lc.configObj); err != nil {
|
||||
return err
|
||||
}
|
||||
message := fmt.Sprintf("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 registering here: %s", u.String())
|
||||
if c.configObj.CustomerAdminEMail != "" {
|
||||
InfoTextDisplay(os.Stdout, message+"\n")
|
||||
return
|
||||
}
|
||||
u.Path = "account/sign-up"
|
||||
q := u.Query()
|
||||
q.Add("invitationToken", c.configObj.Token)
|
||||
q.Add("customerGUID", c.configObj.CustomerGUID)
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
InfoTextDisplay(os.Stdout, message+"\n")
|
||||
updateConfigFile(lc.configObj)
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetCustomerGUID() string {
|
||||
if c.configObj != nil {
|
||||
return c.configObj.CustomerGUID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) SetCustomerGUID(customerGUID string) error {
|
||||
|
||||
if customerGUID != "" && c.GetCustomerGUID() != customerGUID {
|
||||
c.configObj.CustomerGUID = customerGUID // override config customerGUID
|
||||
}
|
||||
|
||||
customerGUID = c.GetCustomerGUID()
|
||||
func getTenantConfigFromBE(backendAPI getter.IBackend, configObj *ConfigObj) error {
|
||||
|
||||
// get from armoBE
|
||||
tenantResponse, err := c.backendAPI.GetCustomerGUID(customerGUID)
|
||||
backendAPI.SetCustomerGUID(configObj.CustomerGUID)
|
||||
tenantResponse, err := backendAPI.GetCustomerGUID()
|
||||
if err == nil && tenantResponse != nil {
|
||||
if tenantResponse.AdminMail != "" { // this customer already belongs to some user
|
||||
c.configObj.CustomerAdminEMail = tenantResponse.AdminMail
|
||||
} else {
|
||||
c.configObj.Token = tenantResponse.Token
|
||||
c.configObj.CustomerGUID = tenantResponse.TenantID
|
||||
if tenantResponse.AdminMail != "" { // registered tenant
|
||||
configObj.CustomerAdminEMail = tenantResponse.AdminMail
|
||||
} else { // new tenant
|
||||
configObj.Token = tenantResponse.Token
|
||||
configObj.CustomerGUID = tenantResponse.TenantID
|
||||
}
|
||||
} else {
|
||||
if err != nil && !strings.Contains(err.Error(), "already exists") {
|
||||
@@ -192,43 +140,110 @@ func (c *ClusterConfig) SetCustomerGUID(customerGUID string) error {
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// ========================== Cluster Config ============================================
|
||||
// ======================================================================================
|
||||
|
||||
// ClusterConfig configuration of specific cluster
|
||||
/*
|
||||
|
||||
Supported environments variables:
|
||||
KS_DEFAULT_CONFIGMAP_NAME // name of configmap, if not set default is 'kubescape'
|
||||
KS_DEFAULT_CONFIGMAP_NAMESPACE // configmap namespace, if not set default is 'default'
|
||||
|
||||
TODO - supprot:
|
||||
KS_ACCOUNT // Account ID
|
||||
KS_CACHE // path to cached files
|
||||
*/
|
||||
type ClusterConfig struct {
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
configMapName string
|
||||
configMapNamespace string
|
||||
backendAPI getter.IBackend
|
||||
configObj *ConfigObj
|
||||
}
|
||||
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, customerGUID, clusterName string) *ClusterConfig {
|
||||
var configObj *ConfigObj
|
||||
c := &ClusterConfig{
|
||||
k8s: k8s,
|
||||
backendAPI: backendAPI,
|
||||
configObj: &ConfigObj{},
|
||||
configMapName: getConfigMapName(),
|
||||
configMapNamespace: getConfigMapNamespace(),
|
||||
}
|
||||
|
||||
// get from configMap
|
||||
if c.existsConfigMap() {
|
||||
configObj, _ = c.loadConfigFromConfigMap()
|
||||
}
|
||||
if configObj == nil && existsConfigFile() { // get from file
|
||||
configObj, _ = loadConfigFromFile()
|
||||
}
|
||||
if configObj != nil {
|
||||
c.configObj = configObj
|
||||
}
|
||||
if customerGUID != "" {
|
||||
c.configObj.CustomerGUID = customerGUID // override config customerGUID
|
||||
}
|
||||
if clusterName != "" {
|
||||
c.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
||||
}
|
||||
if c.configObj.CustomerGUID != "" {
|
||||
if err := c.SetTenant(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
if c.configObj.ClusterName == "" {
|
||||
c.configObj.ClusterName = AdoptClusterName(k8sinterface.GetClusterName())
|
||||
} else { // override the cluster name if it has unwanted characters
|
||||
c.configObj.ClusterName = AdoptClusterName(c.configObj.ClusterName)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
|
||||
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
|
||||
func (c *ClusterConfig) GetCustomerGUID() string { return c.configObj.CustomerGUID }
|
||||
func (c *ClusterConfig) SetCustomerGUID(customerGUID string) { c.configObj.CustomerGUID = customerGUID }
|
||||
func (c *ClusterConfig) IsConfigFound() bool {
|
||||
return existsConfigFile() || c.existsConfigMap()
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) SetTenant() error {
|
||||
|
||||
// ARMO tenant GUID
|
||||
if err := getTenantConfigFromBE(c.backendAPI, c.configObj); err != nil {
|
||||
return err
|
||||
}
|
||||
// update/create config
|
||||
if c.existsConfigMap() {
|
||||
c.updateConfigMap()
|
||||
} else {
|
||||
c.createConfigMap()
|
||||
}
|
||||
if existsConfigFile() {
|
||||
c.updateConfigFile()
|
||||
} else {
|
||||
c.createConfigFile()
|
||||
}
|
||||
|
||||
updateConfigFile(c.configObj)
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) LoadConfig() {
|
||||
// get from configMap
|
||||
if c.existsConfigMap() {
|
||||
c.configObj, _ = c.loadConfigFromConfigMap()
|
||||
} else if existsConfigFile() { // get from file
|
||||
c.configObj, _ = loadConfigFromFile()
|
||||
} else {
|
||||
c.configObj = &ConfigObj{}
|
||||
}
|
||||
func (c *ClusterConfig) GetClusterName() string {
|
||||
return c.configObj.ClusterName
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) ToMapString() map[string]interface{} {
|
||||
m := map[string]interface{}{}
|
||||
bc, _ := json.Marshal(c.configObj)
|
||||
json.Unmarshal(bc, &m)
|
||||
if bc, err := json.Marshal(c.configObj); err == nil {
|
||||
json.Unmarshal(bc, &m)
|
||||
}
|
||||
return m
|
||||
}
|
||||
func (c *ClusterConfig) loadConfigFromConfigMap() (*ConfigObj, error) {
|
||||
if c.k8s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -240,14 +255,14 @@ func (c *ClusterConfig) loadConfigFromConfigMap() (*ConfigObj, error) {
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) existsConfigMap() bool {
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
// TODO - check if has customerGUID
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetValueByKeyFromConfigMap(key string) (string, error) {
|
||||
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -299,11 +314,11 @@ func SetKeyValueInConfigJson(key string, value string) error {
|
||||
|
||||
func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
|
||||
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
configMap = &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: configMapName,
|
||||
Name: c.configMapName,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -315,9 +330,9 @@ func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
|
||||
configMap.Data[key] = value
|
||||
|
||||
if err != nil {
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
} else {
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(configMap.Namespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -334,12 +349,12 @@ func (c *ClusterConfig) createConfigMap() error {
|
||||
}
|
||||
configMap := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: configMapName,
|
||||
Name: c.configMapName,
|
||||
},
|
||||
}
|
||||
c.updateConfigData(configMap)
|
||||
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -347,7 +362,7 @@ func (c *ClusterConfig) updateConfigMap() error {
|
||||
if c.k8s == nil {
|
||||
return nil
|
||||
}
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -355,19 +370,12 @@ func (c *ClusterConfig) updateConfigMap() error {
|
||||
|
||||
c.updateConfigData(configMap)
|
||||
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(configMap.Namespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigFile() error {
|
||||
if err := os.WriteFile(ConfigFileFullPath(), c.configObj.Json(), 0664); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) createConfigFile() error {
|
||||
if err := os.WriteFile(ConfigFileFullPath(), c.configObj.Json(), 0664); err != nil {
|
||||
func updateConfigFile(configObj *ConfigObj) error {
|
||||
if err := os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -398,21 +406,23 @@ func readConfig(dat []byte) (*ConfigObj, error) {
|
||||
return nil, nil
|
||||
}
|
||||
configObj := &ConfigObj{}
|
||||
err := json.Unmarshal(dat, configObj)
|
||||
|
||||
return configObj, err
|
||||
if err := json.Unmarshal(dat, configObj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configObj, nil
|
||||
}
|
||||
|
||||
// Check if the customer is submitted
|
||||
func IsSubmitted(clusterConfig *ClusterConfig) bool {
|
||||
func (clusterConfig *ClusterConfig) IsSubmitted() bool {
|
||||
return clusterConfig.existsConfigMap() || existsConfigFile()
|
||||
}
|
||||
|
||||
// Check if the customer is registered
|
||||
func IsRegistered(clusterConfig *ClusterConfig) bool {
|
||||
func (clusterConfig *ClusterConfig) IsRegistered() bool {
|
||||
|
||||
// get from armoBE
|
||||
tenantResponse, err := clusterConfig.backendAPI.GetCustomerGUID(clusterConfig.GetCustomerGUID())
|
||||
clusterConfig.backendAPI.SetCustomerGUID(clusterConfig.GetCustomerGUID())
|
||||
tenantResponse, err := clusterConfig.backendAPI.GetCustomerGUID()
|
||||
if err == nil && tenantResponse != nil {
|
||||
if tenantResponse.AdminMail != "" { // this customer already belongs to some user
|
||||
return true
|
||||
@@ -421,8 +431,8 @@ func IsRegistered(clusterConfig *ClusterConfig) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func DeleteConfig(k8s *k8sinterface.KubernetesApi) error {
|
||||
if err := DeleteConfigMap(k8s); err != nil {
|
||||
func (clusterConfig *ClusterConfig) DeleteConfig() error {
|
||||
if err := clusterConfig.DeleteConfigMap(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := DeleteConfigFile(); err != nil {
|
||||
@@ -430,10 +440,28 @@ func DeleteConfig(k8s *k8sinterface.KubernetesApi) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func DeleteConfigMap(k8s *k8sinterface.KubernetesApi) error {
|
||||
return k8s.KubernetesClient.CoreV1().ConfigMaps(k8sinterface.GetDefaultNamespace()).Delete(context.Background(), configMapName, metav1.DeleteOptions{})
|
||||
func (clusterConfig *ClusterConfig) DeleteConfigMap() error {
|
||||
return clusterConfig.k8s.KubernetesClient.CoreV1().ConfigMaps(clusterConfig.configMapNamespace).Delete(context.Background(), clusterConfig.configMapName, metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
func DeleteConfigFile() error {
|
||||
return os.Remove(ConfigFileFullPath())
|
||||
}
|
||||
|
||||
func AdoptClusterName(clusterName string) string {
|
||||
return strings.ReplaceAll(clusterName, "/", "-")
|
||||
}
|
||||
|
||||
func getConfigMapName() string {
|
||||
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAME"); n != "" {
|
||||
return n
|
||||
}
|
||||
return "kubescape"
|
||||
}
|
||||
|
||||
func getConfigMapNamespace() string {
|
||||
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAMESPACE"); n != "" {
|
||||
return n
|
||||
}
|
||||
return "default"
|
||||
}
|
||||
|
||||
@@ -2,23 +2,33 @@ package cautils
|
||||
|
||||
import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
|
||||
)
|
||||
|
||||
// K8SResources map[<api group>/<api version>/<resource>]<resource object>
|
||||
type K8SResources map[string]interface{}
|
||||
// K8SResources map[<api group>/<api version>/<resource>][]<resourceID>
|
||||
type K8SResources map[string][]string
|
||||
|
||||
type OPASessionObj struct {
|
||||
Frameworks []reporthandling.Framework
|
||||
K8SResources *K8SResources
|
||||
Exceptions []armotypes.PostureExceptionPolicy
|
||||
PostureReport *reporthandling.PostureReport
|
||||
K8SResources *K8SResources // input k8s objects
|
||||
Frameworks []reporthandling.Framework // list of frameworks to scan
|
||||
AllResources map[string]workloadinterface.IMetadata // all scanned resources, map[<rtesource ID>]<resource>
|
||||
ResourcesResult map[string]resourcesresults.Result // resources scan results, map[<rtesource ID>]<resource result>
|
||||
PostureReport *reporthandling.PostureReport // scan results v1
|
||||
Report *reporthandlingv2.PostureReport // scan results v2
|
||||
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
|
||||
RegoInputData RegoInputData // input passed to rgo for scanning. map[<control name>][<input arguments>]
|
||||
}
|
||||
|
||||
func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SResources) *OPASessionObj {
|
||||
return &OPASessionObj{
|
||||
Frameworks: frameworks,
|
||||
K8SResources: k8sResources,
|
||||
Report: &reporthandlingv2.PostureReport{},
|
||||
Frameworks: frameworks,
|
||||
K8SResources: k8sResources,
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result),
|
||||
PostureReport: &reporthandling.PostureReport{
|
||||
ClusterName: ClusterName,
|
||||
CustomerGUID: CustomerGUID,
|
||||
@@ -28,8 +38,11 @@ func NewOPASessionObj(frameworks []reporthandling.Framework, k8sResources *K8SRe
|
||||
|
||||
func NewOPASessionObjMock() *OPASessionObj {
|
||||
return &OPASessionObj{
|
||||
Frameworks: nil,
|
||||
K8SResources: nil,
|
||||
Frameworks: nil,
|
||||
K8SResources: nil,
|
||||
AllResources: make(map[string]workloadinterface.IMetadata),
|
||||
ResourcesResult: make(map[string]resourcesresults.Result),
|
||||
Report: &reporthandlingv2.PostureReport{},
|
||||
PostureReport: &reporthandling.PostureReport{
|
||||
ClusterName: "",
|
||||
CustomerGUID: "",
|
||||
@@ -49,3 +62,14 @@ type Exception struct {
|
||||
Namespaces []string `json:"namespaces"`
|
||||
Regex string `json:"regex"` // not supported
|
||||
}
|
||||
|
||||
type RegoInputData struct {
|
||||
PostureControlInputs map[string][]string `json:"postureControlInputs"`
|
||||
// ClusterName string `json:"clusterName"`
|
||||
// K8sConfig RegoK8sConfig `json:"k8sconfig"`
|
||||
}
|
||||
|
||||
type Policies struct {
|
||||
Frameworks []string
|
||||
Controls map[string]reporthandling.Control // map[<control ID>]<control>
|
||||
}
|
||||
|
||||
67
cautils/datastructuresmethods.go
Normal file
67
cautils/datastructuresmethods.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
pkgcautils "github.com/armosec/utils-go/utils"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
func NewPolicies() *Policies {
|
||||
return &Policies{
|
||||
Frameworks: make([]string, 0),
|
||||
Controls: make(map[string]reporthandling.Control),
|
||||
}
|
||||
}
|
||||
|
||||
func (policies *Policies) Set(frameworks []reporthandling.Framework, version string) {
|
||||
for i := range frameworks {
|
||||
if frameworks[i].Name != "" {
|
||||
policies.Frameworks = append(policies.Frameworks, frameworks[i].Name)
|
||||
}
|
||||
for j := range frameworks[i].Controls {
|
||||
compatibleRules := []reporthandling.PolicyRule{}
|
||||
for r := range frameworks[i].Controls[j].Rules {
|
||||
if !ruleWithArmoOpaDependency(frameworks[i].Controls[j].Rules[r].Attributes) && isRuleKubescapeVersionCompatible(frameworks[i].Controls[j].Rules[r].Attributes, version) {
|
||||
compatibleRules = append(compatibleRules, frameworks[i].Controls[j].Rules[r])
|
||||
}
|
||||
}
|
||||
if len(compatibleRules) > 0 {
|
||||
frameworks[i].Controls[j].Rules = compatibleRules
|
||||
policies.Controls[frameworks[i].Controls[j].ControlID] = frameworks[i].Controls[j]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ruleWithArmoOpaDependency(attributes map[string]interface{}) bool {
|
||||
if attributes == nil {
|
||||
return false
|
||||
}
|
||||
if s, ok := attributes["armoOpa"]; ok { // TODO - make global
|
||||
return pkgcautils.StringToBool(s.(string))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks that kubescape version is in range of use for this rule
|
||||
// In local build (BuildNumber = ""):
|
||||
// returns true only if rule doesn't have the "until" attribute
|
||||
func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version string) bool {
|
||||
if from, ok := attributes["useFromKubescapeVersion"]; ok && from != nil {
|
||||
if version != "" {
|
||||
if from.(string) > BuildNumber {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if until, ok := attributes["useUntilKubescapeVersion"]; ok && until != nil {
|
||||
if version != "" {
|
||||
if until.(string) <= BuildNumber {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -35,15 +35,15 @@ func ScanStartDisplay() {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
InfoDisplay(os.Stdout, "ARMO security scanner starting\n")
|
||||
InfoDisplay(os.Stderr, "ARMO security scanner starting\n")
|
||||
}
|
||||
|
||||
func SuccessTextDisplay(str string) {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
SuccessDisplay(os.Stdout, "[success] ")
|
||||
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
|
||||
SuccessDisplay(os.Stderr, "[success] ")
|
||||
SimpleDisplay(os.Stderr, fmt.Sprintf("%s\n", str))
|
||||
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ func ErrorDisplay(str string) {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
SuccessDisplay(os.Stdout, "[Error] ")
|
||||
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
|
||||
FailureDisplay(os.Stderr, "[Error] ")
|
||||
SimpleDisplay(os.Stderr, fmt.Sprintf("%s\n", str))
|
||||
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ func ProgressTextDisplay(str string) {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
InfoDisplay(os.Stdout, "[progress] ")
|
||||
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
|
||||
InfoDisplay(os.Stderr, "[progress] ")
|
||||
SimpleDisplay(os.Stderr, fmt.Sprintf("%s\n", str))
|
||||
|
||||
}
|
||||
func StartSpinner() {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package cautils
|
||||
|
||||
type DownloadInfo struct {
|
||||
Path string
|
||||
FrameworkName string
|
||||
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
|
||||
Account string // customerGUID
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
@@ -15,43 +19,122 @@ import (
|
||||
var (
|
||||
// ATTENTION!!!
|
||||
// Changes in this URLs variable names, or in the usage is affecting the build process! BE CAREFULL
|
||||
ArmoBEURL = "eggdashbe.eudev3.cyberarmorsoft.com"
|
||||
ArmoERURL = "report.eudev3.cyberarmorsoft.com"
|
||||
ArmoFEURL = "armoui.eudev3.cyberarmorsoft.com"
|
||||
// ArmoURL = "https://dashbe.euprod1.cyberarmorsoft.com"
|
||||
armoERURL = "report.armo.cloud"
|
||||
armoBEURL = "api.armo.cloud"
|
||||
armoFEURL = "portal.armo.cloud"
|
||||
|
||||
armoDevERURL = "report.eudev3.cyberarmorsoft.com"
|
||||
armoDevBEURL = "eggdashbe.eudev3.cyberarmorsoft.com"
|
||||
armoDevFEURL = "armoui-dev.eudev3.cyberarmorsoft.com"
|
||||
)
|
||||
|
||||
// Armo API for downloading policies
|
||||
type ArmoAPI struct {
|
||||
httpClient *http.Client
|
||||
httpClient *http.Client
|
||||
apiURL string
|
||||
erURL string
|
||||
feURL string
|
||||
customerGUID string
|
||||
}
|
||||
|
||||
func NewArmoAPI() *ArmoAPI {
|
||||
var globalArmoAPIConnecctor *ArmoAPI
|
||||
|
||||
func SetARMOAPIConnector(armoAPI *ArmoAPI) {
|
||||
globalArmoAPIConnecctor = armoAPI
|
||||
}
|
||||
|
||||
func GetArmoAPIConnector() *ArmoAPI {
|
||||
if globalArmoAPIConnecctor == nil {
|
||||
glog.Error("returning nil API connector")
|
||||
}
|
||||
return globalArmoAPIConnecctor
|
||||
}
|
||||
|
||||
func NewARMOAPIDev() *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.apiURL = armoDevBEURL
|
||||
apiObj.erURL = armoDevERURL
|
||||
apiObj.feURL = armoDevFEURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewARMOAPIProd() *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.apiURL = armoBEURL
|
||||
apiObj.erURL = armoERURL
|
||||
apiObj.feURL = armoFEURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func NewARMOAPICustomized(armoERURL, armoBEURL, armoFEURL string) *ArmoAPI {
|
||||
apiObj := newArmoAPI()
|
||||
|
||||
apiObj.erURL = armoERURL
|
||||
apiObj.apiURL = armoBEURL
|
||||
apiObj.feURL = armoFEURL
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
func newArmoAPI() *ArmoAPI {
|
||||
return &ArmoAPI{
|
||||
httpClient: &http.Client{},
|
||||
httpClient: &http.Client{Timeout: time.Duration(61) * time.Second},
|
||||
}
|
||||
}
|
||||
func (armoAPI *ArmoAPI) SetCustomerGUID(customerGUID string) {
|
||||
armoAPI.customerGUID = customerGUID
|
||||
|
||||
}
|
||||
func (armoAPI *ArmoAPI) GetFrontendURL() string {
|
||||
return armoAPI.feURL
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetReportReceiverURL() string {
|
||||
return armoAPI.erURL
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetFramework(name string) (*reporthandling.Framework, error) {
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getFrameworkURL(name))
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getFrameworkURL(name), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
framework := &reporthandling.Framework{}
|
||||
if err = JSONDecoder(respStr).Decode(framework); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
SaveFrameworkInFile(framework, GetDefaultPath(name+".json"))
|
||||
SaveInFile(framework, GetDefaultPath(name+".json"))
|
||||
|
||||
return framework, err
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
exceptions := []armotypes.PostureExceptionPolicy{}
|
||||
if customerGUID == "" {
|
||||
return exceptions, nil
|
||||
func (armoAPI *ArmoAPI) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getListFrameworkURL(), nil)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getExceptionsURL(customerGUID, clusterName))
|
||||
|
||||
frameworks := []reporthandling.Framework{}
|
||||
if err = JSONDecoder(respStr).Decode(&frameworks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// SaveInFile(framework, GetDefaultPath(name+".json"))
|
||||
|
||||
return frameworks, err
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetControl(policyName string) (*reporthandling.Control, error) {
|
||||
return nil, fmt.Errorf("control api is not public")
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
exceptions := []armotypes.PostureExceptionPolicy{}
|
||||
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getExceptionsURL(clusterName), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,12 +146,12 @@ func (armoAPI *ArmoAPI) GetExceptions(customerGUID, clusterName string) ([]armot
|
||||
return exceptions, nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetCustomerGUID(customerGUID string) (*TenantResponse, error) {
|
||||
func (armoAPI *ArmoAPI) GetCustomerGUID() (*TenantResponse, error) {
|
||||
url := armoAPI.getCustomerURL()
|
||||
if customerGUID != "" {
|
||||
url = fmt.Sprintf("%s?customerGUID=%s", url, customerGUID)
|
||||
if armoAPI.customerGUID != "" {
|
||||
url = fmt.Sprintf("%s?customerGUID=%s", url, armoAPI.customerGUID)
|
||||
}
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, url)
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -80,6 +163,79 @@ func (armoAPI *ArmoAPI) GetCustomerGUID(customerGUID string) (*TenantResponse, e
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
// ControlsInputs // map[<control name>][<input arguments>]
|
||||
func (armoAPI *ArmoAPI) GetAccountConfig(clusterName string) (*armotypes.CustomerConfig, error) {
|
||||
accountConfig := &armotypes.CustomerConfig{}
|
||||
if armoAPI.customerGUID == "" {
|
||||
return accountConfig, nil
|
||||
}
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getAccountConfig(clusterName), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = JSONDecoder(respStr).Decode(&accountConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return accountConfig, nil
|
||||
}
|
||||
|
||||
// ControlsInputs // map[<control name>][<input arguments>]
|
||||
func (armoAPI *ArmoAPI) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
accountConfig, err := armoAPI.GetAccountConfig(clusterName)
|
||||
if err == nil {
|
||||
return accountConfig.Settings.PostureControlInputs, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) ListCustomFrameworks() ([]string, error) {
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getListFrameworkURL(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frs := []reporthandling.Framework{}
|
||||
if err = json.Unmarshal([]byte(respStr), &frs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
frameworkList := []string{}
|
||||
for _, fr := range frs {
|
||||
if !isNativeFramework(fr.Name) {
|
||||
frameworkList = append(frameworkList, fr.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return frameworkList, nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) ListFrameworks() ([]string, error) {
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getListFrameworkURL(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frs := []reporthandling.Framework{}
|
||||
if err = json.Unmarshal([]byte(respStr), &frs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
frameworkList := []string{}
|
||||
for _, fr := range frs {
|
||||
if isNativeFramework(fr.Name) {
|
||||
frameworkList = append(frameworkList, strings.ToLower(fr.Name))
|
||||
} else {
|
||||
frameworkList = append(frameworkList, fr.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return frameworkList, nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) ListControls(l ListType) ([]string, error) {
|
||||
return nil, fmt.Errorf("control api is not public")
|
||||
}
|
||||
|
||||
type TenantResponse struct {
|
||||
TenantID string `json:"tenantId"`
|
||||
Token string `json:"token"`
|
||||
|
||||
@@ -5,28 +5,45 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var NativeFrameworks = []string{"nsa", "mitre", "armobest", "devopsbest"}
|
||||
|
||||
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = ArmoBEURL
|
||||
u.Path = "v1/armoFrameworks"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", "11111111-1111-1111-1111-111111111111")
|
||||
q.Add("frameworkName", strings.ToUpper(frameworkName))
|
||||
q.Add("getRules", "true")
|
||||
q.Add("customerGUID", armoAPI.customerGUID)
|
||||
if isNativeFramework(frameworkName) {
|
||||
q.Add("frameworkName", strings.ToUpper(frameworkName))
|
||||
} else {
|
||||
// For customer framework has to be the way it was added
|
||||
q.Add("frameworkName", frameworkName)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) string {
|
||||
func (armoAPI *ArmoAPI) getListFrameworkURL() string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = ArmoBEURL
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.customerGUID)
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoPostureExceptions"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", customerGUID)
|
||||
q.Add("customerGUID", armoAPI.customerGUID)
|
||||
// if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
// q.Add("clusterName", clusterName)
|
||||
// }
|
||||
@@ -35,10 +52,26 @@ func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) strin
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getAccountConfig(clusterName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/armoCustomerConfiguration"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", armoAPI.customerGUID)
|
||||
if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
q.Add("clusterName", clusterName)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getCustomerURL() string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = ArmoBEURL
|
||||
u.Host = armoAPI.apiURL
|
||||
u.Path = "api/v1/createTenant"
|
||||
return u.String()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/opa-utils/gitregostore"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
@@ -13,73 +11,82 @@ import (
|
||||
// ======================================== DownloadReleasedPolicy =======================================================
|
||||
// =======================================================================================================================
|
||||
|
||||
// Download released version
|
||||
// Use gitregostore to get policies from github release
|
||||
type DownloadReleasedPolicy struct {
|
||||
hostURL string
|
||||
httpClient *http.Client
|
||||
gs *gitregostore.GitRegoStore
|
||||
}
|
||||
|
||||
func NewDownloadReleasedPolicy() *DownloadReleasedPolicy {
|
||||
return &DownloadReleasedPolicy{
|
||||
hostURL: "",
|
||||
httpClient: &http.Client{},
|
||||
gs: gitregostore.NewDefaultGitRegoStore(-1),
|
||||
}
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetControl(policyName string) (*reporthandling.Control, error) {
|
||||
var control *reporthandling.Control
|
||||
var err error
|
||||
|
||||
control, err = drp.gs.GetOPAControl(policyName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return control, nil
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetFramework(name string) (*reporthandling.Framework, error) {
|
||||
if err := drp.setURL(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
respStr, err := HttpGetter(drp.httpClient, drp.hostURL)
|
||||
framework, err := drp.gs.GetOPAFrameworkByName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
framework := &reporthandling.Framework{}
|
||||
if err = JSONDecoder(respStr).Decode(framework); err != nil {
|
||||
return framework, err
|
||||
}
|
||||
|
||||
SaveFrameworkInFile(framework, GetDefaultPath(name+".json"))
|
||||
return framework, err
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) setURL(frameworkName string) error {
|
||||
|
||||
latestReleases := "https://api.github.com/repos/armosec/regolibrary/releases/latest"
|
||||
resp, err := http.Get(latestReleases)
|
||||
func (drp *DownloadReleasedPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
frameworks, err := drp.gs.GetOPAFrameworks()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get latest releases from '%s', reason: %s", latestReleases, err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || 301 < resp.StatusCode {
|
||||
return fmt.Errorf("failed to download file, status code: %s", resp.Status)
|
||||
return nil, err
|
||||
}
|
||||
return frameworks, err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response body from '%s', reason: %s", latestReleases, err.Error())
|
||||
}
|
||||
var data map[string]interface{}
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal response body from '%s', reason: %s", latestReleases, err.Error())
|
||||
}
|
||||
func (drp *DownloadReleasedPolicy) ListFrameworks() ([]string, error) {
|
||||
return drp.gs.GetOPAFrameworksNamesList()
|
||||
}
|
||||
|
||||
if assets, ok := data["assets"].([]interface{}); ok {
|
||||
for i := range assets {
|
||||
if asset, ok := assets[i].(map[string]interface{}); ok {
|
||||
if name, ok := asset["name"].(string); ok {
|
||||
if name == frameworkName {
|
||||
if url, ok := asset["browser_download_url"].(string); ok {
|
||||
drp.hostURL = url
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func (drp *DownloadReleasedPolicy) ListControls(listType ListType) ([]string, error) {
|
||||
switch listType {
|
||||
case ListID:
|
||||
return drp.gs.GetOPAControlsIDsList()
|
||||
default:
|
||||
return drp.gs.GetOPAControlsNamesList()
|
||||
}
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
defaultConfigInputs, err := drp.gs.GetDefaultConfigInputs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return defaultConfigInputs.Settings.PostureControlInputs, err
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) SetRegoObjects() error {
|
||||
fwNames, err := drp.gs.GetOPAFrameworksNamesList()
|
||||
if len(fwNames) != 0 && err == nil {
|
||||
return nil
|
||||
}
|
||||
return drp.gs.SetRegoObjects()
|
||||
}
|
||||
|
||||
func isNativeFramework(framework string) bool {
|
||||
return contains(NativeFrameworks, framework)
|
||||
}
|
||||
|
||||
func contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if strings.EqualFold(v, str) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("failed to download '%s' - not found", frameworkName)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -5,13 +5,29 @@ import (
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
// supported listing
|
||||
type ListType string
|
||||
|
||||
const ListID ListType = "id"
|
||||
const ListName ListType = "name"
|
||||
|
||||
type IPolicyGetter interface {
|
||||
GetFramework(name string) (*reporthandling.Framework, error)
|
||||
GetFrameworks() ([]reporthandling.Framework, error)
|
||||
GetControl(name string) (*reporthandling.Control, error)
|
||||
|
||||
ListFrameworks() ([]string, error)
|
||||
ListControls(ListType) ([]string, error)
|
||||
}
|
||||
|
||||
type IExceptionsGetter interface {
|
||||
GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error)
|
||||
GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error)
|
||||
}
|
||||
type IBackend interface {
|
||||
GetCustomerGUID(customerGUID string) (*TenantResponse, error)
|
||||
GetCustomerGUID() (*TenantResponse, error)
|
||||
SetCustomerGUID(customerGUID string)
|
||||
}
|
||||
|
||||
type IControlsInputsGetter interface {
|
||||
GetControlsInputs(clusterName string) (map[string][]string, error)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
func GetDefaultPath(name string) string {
|
||||
@@ -21,14 +20,26 @@ func GetDefaultPath(name string) string {
|
||||
return defaultfilePath
|
||||
}
|
||||
|
||||
func SaveFrameworkInFile(framework *reporthandling.Framework, path string) error {
|
||||
encodedData, err := json.Marshal(framework)
|
||||
func SaveInFile(policy interface{}, pathStr string) error {
|
||||
encodedData, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(path, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
|
||||
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
if os.IsNotExist(err) {
|
||||
pathDir := path.Dir(pathStr)
|
||||
if err := os.Mkdir(pathDir, 0744); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
|
||||
}
|
||||
err = os.WriteFile(pathStr, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -40,12 +51,14 @@ func JSONDecoder(origin string) *json.Decoder {
|
||||
return dec
|
||||
}
|
||||
|
||||
func HttpGetter(httpClient *http.Client, fullURL string) (string, error) {
|
||||
func HttpGetter(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) {
|
||||
|
||||
req, err := http.NewRequest("GET", fullURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
addHeaders(req, headers)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -57,6 +70,32 @@ func HttpGetter(httpClient *http.Client, fullURL string) (string, error) {
|
||||
return respStr, nil
|
||||
}
|
||||
|
||||
func HttpPost(httpClient *http.Client, fullURL string, headers map[string]string, body []byte) (string, error) {
|
||||
|
||||
req, err := http.NewRequest("POST", fullURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
addHeaders(req, headers)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
respStr, err := httpRespToString(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return respStr, nil
|
||||
}
|
||||
|
||||
func addHeaders(req *http.Request, headers map[string]string) {
|
||||
if len(headers) >= 0 { // might be nil
|
||||
for k, v := range headers {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HTTPRespToString parses the body as string and checks the HTTP status code, it closes the body reader at the end
|
||||
func httpRespToString(resp *http.Response) (string, error) {
|
||||
if resp == nil || resp.Body == nil {
|
||||
@@ -86,29 +125,3 @@ func httpRespToString(resp *http.Response) (string, error) {
|
||||
|
||||
return respStr, err
|
||||
}
|
||||
|
||||
// URLEncoder encode url
|
||||
func urlEncoder(oldURL string) string {
|
||||
fullURL := strings.Split(oldURL, "?")
|
||||
baseURL, err := url.Parse(fullURL[0])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Prepare Query Parameters
|
||||
if len(fullURL) > 1 {
|
||||
params := url.Values{}
|
||||
queryParams := strings.Split(fullURL[1], "&")
|
||||
for _, i := range queryParams {
|
||||
queryParam := strings.Split(i, "=")
|
||||
val := ""
|
||||
if len(queryParam) > 1 {
|
||||
val = queryParam[1]
|
||||
}
|
||||
params.Add(queryParam[0], val)
|
||||
}
|
||||
baseURL.RawQuery = params.Encode()
|
||||
}
|
||||
|
||||
return baseURL.String()
|
||||
}
|
||||
|
||||
@@ -17,34 +17,98 @@ const DefaultLocalStore = ".kubescape"
|
||||
|
||||
// Load policies from a local repository
|
||||
type LoadPolicy struct {
|
||||
filePath string
|
||||
filePaths []string
|
||||
}
|
||||
|
||||
func NewLoadPolicy(filePath string) *LoadPolicy {
|
||||
func NewLoadPolicy(filePaths []string) *LoadPolicy {
|
||||
return &LoadPolicy{
|
||||
filePath: filePath,
|
||||
filePaths: filePaths,
|
||||
}
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
|
||||
// Return control from file
|
||||
func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, error) {
|
||||
|
||||
framework := &reporthandling.Framework{}
|
||||
f, err := os.ReadFile(lp.filePath)
|
||||
control := &reporthandling.Control{}
|
||||
filePath := lp.filePath()
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(f, framework)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return control, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
|
||||
framework := &reporthandling.Framework{}
|
||||
var err error
|
||||
for _, filePath := range lp.filePaths {
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, framework); err != nil {
|
||||
return framework, err
|
||||
}
|
||||
if strings.EqualFold(frameworkName, framework.Name) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if frameworkName != "" && !strings.EqualFold(frameworkName, framework.Name) {
|
||||
|
||||
return nil, fmt.Errorf("framework from file not matching")
|
||||
}
|
||||
return framework, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
|
||||
frameworks := []reporthandling.Framework{}
|
||||
var err error
|
||||
return frameworks, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) ListFrameworks() ([]string, error) {
|
||||
fwNames := []string{}
|
||||
framework := &reporthandling.Framework{}
|
||||
for _, f := range lp.filePaths {
|
||||
file, err := os.ReadFile(f)
|
||||
if err == nil {
|
||||
if err := json.Unmarshal(file, framework); err == nil {
|
||||
if !contains(fwNames, framework.Name) {
|
||||
fwNames = append(fwNames, framework.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fwNames, nil
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) ListControls(listType ListType) ([]string, error) {
|
||||
// TODO - Support
|
||||
return []string{}, fmt.Errorf("loading controls list from file is not supported")
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
|
||||
filePath := lp.filePath()
|
||||
exception := []armotypes.PostureExceptionPolicy{}
|
||||
f, err := os.ReadFile(lp.filePath)
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -52,3 +116,25 @@ func (lp *LoadPolicy) GetExceptions(customerGUID, clusterName string) ([]armotyp
|
||||
err = json.Unmarshal(f, &exception)
|
||||
return exception, err
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
filePath := lp.filePath()
|
||||
accountConfig := &armotypes.CustomerConfig{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, &accountConfig.Settings.PostureControlInputs); err == nil {
|
||||
return accountConfig.Settings.PostureControlInputs, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// temporary support for a list of files
|
||||
func (lp *LoadPolicy) filePath() string {
|
||||
if len(lp.filePaths) > 0 {
|
||||
return lp.filePaths[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
13
cautils/getter/loadpolicy_test.go
Normal file
13
cautils/getter/loadpolicy_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var mockFrameworkBasePath = filepath.Join("examples", "mocks", "frameworks")
|
||||
|
||||
func MockNewLoadPolicy() *LoadPolicy {
|
||||
return &LoadPolicy{
|
||||
filePaths: []string{""},
|
||||
}
|
||||
}
|
||||
7
cautils/listpolicies.go
Normal file
7
cautils/listpolicies.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package cautils
|
||||
|
||||
type ListPolicies struct {
|
||||
Target string
|
||||
ListIDs bool
|
||||
Account string
|
||||
}
|
||||
125
cautils/rbac.go
Normal file
125
cautils/rbac.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/rbac-utils/rbacscanner"
|
||||
"github.com/armosec/rbac-utils/rbacutils"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
type RBACObjects struct {
|
||||
scanner *rbacscanner.RbacScannerFromK8sAPI
|
||||
}
|
||||
|
||||
func NewRBACObjects(scanner *rbacscanner.RbacScannerFromK8sAPI) *RBACObjects {
|
||||
return &RBACObjects{scanner: scanner}
|
||||
}
|
||||
|
||||
func (rbacObjects *RBACObjects) SetResourcesReport() (*reporthandling.PostureReport, error) {
|
||||
return &reporthandling.PostureReport{
|
||||
ReportID: uuid.NewV4().String(),
|
||||
ReportGenerationTime: time.Now().UTC(),
|
||||
CustomerGUID: rbacObjects.scanner.CustomerGUID,
|
||||
ClusterName: rbacObjects.scanner.ClusterName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rbacObjects *RBACObjects) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
|
||||
resources, err := rbacObjects.scanner.ListResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allresources, err := rbacObjects.rbacObjectsToResources(resources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return allresources, nil
|
||||
}
|
||||
|
||||
func (rbacObjects *RBACObjects) rbacObjectsToResources(resources *rbacutils.RbacObjects) (map[string]workloadinterface.IMetadata, error) {
|
||||
allresources := map[string]workloadinterface.IMetadata{}
|
||||
|
||||
/*
|
||||
************************************************************************************************************************
|
||||
This code is adding a non valid ID ->
|
||||
(github.com/armosec/rbac-utils v0.0.11): "//SA2WLIDmap/SA2WLIDmap"
|
||||
(github.com/armosec/rbac-utils v0.0.12): "armo.rbac.com/v0beta1//SAID2WLIDmap/SAID2WLIDmap"
|
||||
|
||||
Should be investigated
|
||||
************************************************************************************************************************
|
||||
*/
|
||||
|
||||
// wrap rbac aggregated objects in IMetadata and add to allresources
|
||||
// TODO - DEPRECATE SA2WLIDmap
|
||||
SA2WLIDmapIMeta, err := rbacutils.SA2WLIDmapIMetadataWrapper(resources.SA2WLIDmap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allresources[SA2WLIDmapIMeta.GetID()] = SA2WLIDmapIMeta
|
||||
|
||||
SAID2WLIDmapIMeta, err := rbacutils.SAID2WLIDmapIMetadataWrapper(resources.SAID2WLIDmap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allresources[SAID2WLIDmapIMeta.GetID()] = SAID2WLIDmapIMeta
|
||||
|
||||
// convert rbac k8s resources to IMetadata and add to allresources
|
||||
for _, cr := range resources.ClusterRoles.Items {
|
||||
crmap, err := convertToMap(cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
|
||||
crIMeta := workloadinterface.NewWorkloadObj(crmap)
|
||||
crIMeta.SetKind("ClusterRole")
|
||||
allresources[crIMeta.GetID()] = crIMeta
|
||||
}
|
||||
for _, cr := range resources.Roles.Items {
|
||||
crmap, err := convertToMap(cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
|
||||
crIMeta := workloadinterface.NewWorkloadObj(crmap)
|
||||
crIMeta.SetKind("Role")
|
||||
allresources[crIMeta.GetID()] = crIMeta
|
||||
}
|
||||
for _, cr := range resources.ClusterRoleBindings.Items {
|
||||
crmap, err := convertToMap(cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
|
||||
crIMeta := workloadinterface.NewWorkloadObj(crmap)
|
||||
crIMeta.SetKind("ClusterRoleBinding")
|
||||
allresources[crIMeta.GetID()] = crIMeta
|
||||
}
|
||||
for _, cr := range resources.RoleBindings.Items {
|
||||
crmap, err := convertToMap(cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crmap["apiVersion"] = "rbac.authorization.k8s.io/v1" // TODO - is the the correct apiVersion?
|
||||
crIMeta := workloadinterface.NewWorkloadObj(crmap)
|
||||
crIMeta.SetKind("RoleBinding")
|
||||
allresources[crIMeta.GetID()] = crIMeta
|
||||
}
|
||||
return allresources, nil
|
||||
}
|
||||
|
||||
func convertToMap(obj interface{}) (map[string]interface{}, error) {
|
||||
var inInterface map[string]interface{}
|
||||
inrec, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(inrec, &inInterface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inInterface, nil
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package cautils
|
||||
|
||||
const (
|
||||
ComponentIdentifier = "Posture"
|
||||
)
|
||||
154
cautils/reportv2tov1.go
Normal file
154
cautils/reportv2tov1.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
helpersv1 "github.com/armosec/opa-utils/reporthandling/helpers/v1"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/armosec/opa-utils/score"
|
||||
)
|
||||
|
||||
func ReportV2ToV1(opaSessionObj *OPASessionObj) {
|
||||
if len(opaSessionObj.PostureReport.FrameworkReports) > 0 {
|
||||
return // report already converted
|
||||
}
|
||||
|
||||
opaSessionObj.PostureReport.ClusterCloudProvider = opaSessionObj.Report.ClusterCloudProvider
|
||||
|
||||
frameworks := []reporthandling.FrameworkReport{}
|
||||
|
||||
if len(opaSessionObj.Report.SummaryDetails.Frameworks) > 0 {
|
||||
for _, fwv2 := range opaSessionObj.Report.SummaryDetails.Frameworks {
|
||||
fwv1 := reporthandling.FrameworkReport{}
|
||||
fwv1.Name = fwv2.GetName()
|
||||
fwv1.Score = fwv2.GetScore()
|
||||
|
||||
fwv1.ControlReports = append(fwv1.ControlReports, controlReportV2ToV1(opaSessionObj, fwv2.GetName(), fwv2.Controls)...)
|
||||
frameworks = append(frameworks, fwv1)
|
||||
|
||||
}
|
||||
} else {
|
||||
fwv1 := reporthandling.FrameworkReport{}
|
||||
fwv1.Name = ""
|
||||
fwv1.Score = 0
|
||||
|
||||
fwv1.ControlReports = append(fwv1.ControlReports, controlReportV2ToV1(opaSessionObj, "", opaSessionObj.Report.SummaryDetails.Controls)...)
|
||||
frameworks = append(frameworks, fwv1)
|
||||
}
|
||||
|
||||
// // remove unused data
|
||||
// opaSessionObj.Report = nil
|
||||
// opaSessionObj.ResourcesResult = nil
|
||||
|
||||
// setup counters and score
|
||||
for f := range frameworks {
|
||||
// // set exceptions
|
||||
// exceptions.SetFrameworkExceptions(frameworks, opap.Exceptions, cautils.ClusterName)
|
||||
|
||||
// set counters
|
||||
reporthandling.SetUniqueResourcesCounter(&frameworks[f])
|
||||
|
||||
// set default score
|
||||
reporthandling.SetDefaultScore(&frameworks[f])
|
||||
}
|
||||
|
||||
// update score
|
||||
scoreutil := score.NewScore(opaSessionObj.AllResources)
|
||||
scoreutil.Calculate(frameworks)
|
||||
|
||||
opaSessionObj.PostureReport.FrameworkReports = frameworks
|
||||
|
||||
// opaSessionObj.Report.SummaryDetails.Score = 0
|
||||
// for i := range frameworks {
|
||||
// for j := range frameworks[i].ControlReports {
|
||||
// // frameworks[i].ControlReports[j].Score
|
||||
// for w := range opaSessionObj.Report.SummaryDetails.Frameworks {
|
||||
// if opaSessionObj.Report.SummaryDetails.Frameworks[w].Name == frameworks[i].Name {
|
||||
// opaSessionObj.Report.SummaryDetails.Frameworks[w].Score = frameworks[i].Score
|
||||
// }
|
||||
// if c, ok := opaSessionObj.Report.SummaryDetails.Frameworks[w].Controls[frameworks[i].ControlReports[j].ControlID]; ok {
|
||||
// c.Score = frameworks[i].ControlReports[j].Score
|
||||
// opaSessionObj.Report.SummaryDetails.Frameworks[w].Controls[frameworks[i].ControlReports[j].ControlID] = c
|
||||
// }
|
||||
// }
|
||||
// if c, ok := opaSessionObj.Report.SummaryDetails.Controls[frameworks[i].ControlReports[j].ControlID]; ok {
|
||||
// c.Score = frameworks[i].ControlReports[j].Score
|
||||
// opaSessionObj.Report.SummaryDetails.Controls[frameworks[i].ControlReports[j].ControlID] = c
|
||||
// }
|
||||
// }
|
||||
// opaSessionObj.Report.SummaryDetails.Score += opaSessionObj.PostureReport.FrameworkReports[i].Score
|
||||
// }
|
||||
// opaSessionObj.Report.SummaryDetails.Score /= float32(len(opaSessionObj.Report.SummaryDetails.Frameworks))
|
||||
}
|
||||
|
||||
func controlReportV2ToV1(opaSessionObj *OPASessionObj, frameworkName string, controls map[string]reportsummary.ControlSummary) []reporthandling.ControlReport {
|
||||
controlRepors := []reporthandling.ControlReport{}
|
||||
for controlID, crv2 := range controls {
|
||||
crv1 := reporthandling.ControlReport{}
|
||||
crv1.ControlID = controlID
|
||||
crv1.BaseScore = crv2.ScoreFactor
|
||||
crv1.Name = crv2.GetName()
|
||||
crv1.Control_ID = controlID
|
||||
// crv1.Attributes = crv2.
|
||||
crv1.Score = crv2.GetScore()
|
||||
|
||||
// TODO - add fields
|
||||
crv1.Description = crv2.Description
|
||||
crv1.Remediation = crv2.Remediation
|
||||
|
||||
rulesv1 := map[string]reporthandling.RuleReport{}
|
||||
|
||||
for _, resourceID := range crv2.ListResourcesIDs().All() {
|
||||
if result, ok := opaSessionObj.ResourcesResult[resourceID]; ok {
|
||||
for _, rulev2 := range result.ListRulesOfControl(crv2.GetID(), "") {
|
||||
|
||||
if _, ok := rulesv1[rulev2.GetName()]; !ok {
|
||||
rulesv1[rulev2.GetName()] = reporthandling.RuleReport{
|
||||
Name: rulev2.GetName(),
|
||||
RuleStatus: reporthandling.RuleStatus{
|
||||
Status: "success",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
rulev1 := rulesv1[rulev2.GetName()]
|
||||
status := rulev2.GetStatus(&helpersv1.Filters{FrameworkNames: []string{frameworkName}})
|
||||
|
||||
if status.IsFailed() || status.IsExcluded() {
|
||||
|
||||
// rule response
|
||||
ruleResponse := reporthandling.RuleResponse{}
|
||||
ruleResponse.Rulename = rulev2.GetName()
|
||||
for i := range rulev2.Paths {
|
||||
ruleResponse.FailedPaths = append(ruleResponse.FailedPaths, rulev2.Paths[i].FailedPath)
|
||||
}
|
||||
ruleResponse.RuleStatus = string(status.Status())
|
||||
if len(rulev2.Exception) > 0 {
|
||||
ruleResponse.Exception = &rulev2.Exception[0]
|
||||
}
|
||||
|
||||
if fullRessource, ok := opaSessionObj.AllResources[resourceID]; ok {
|
||||
tmp := fullRessource.GetObject()
|
||||
workloadinterface.RemoveFromMap(tmp, "spec")
|
||||
ruleResponse.AlertObject.K8SApiObjects = append(ruleResponse.AlertObject.K8SApiObjects, tmp)
|
||||
}
|
||||
rulev1.RuleResponses = append(rulev1.RuleResponses, ruleResponse)
|
||||
}
|
||||
|
||||
rulev1.ListInputKinds = append(rulev1.ListInputKinds, resourceID)
|
||||
rulesv1[rulev2.GetName()] = rulev1
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(rulesv1) > 0 {
|
||||
for i := range rulesv1 {
|
||||
crv1.RuleReports = append(crv1.RuleReports, rulesv1[i])
|
||||
}
|
||||
}
|
||||
if len(crv1.RuleReports) == 0 {
|
||||
crv1.RuleReports = []reporthandling.RuleReport{}
|
||||
}
|
||||
controlRepors = append(controlRepors, crv1)
|
||||
}
|
||||
return controlRepors
|
||||
}
|
||||
@@ -1,67 +1,140 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
const (
|
||||
ScanCluster string = "cluster"
|
||||
ScanLocalFiles string = "yaml"
|
||||
localControlInputsFilename string = "controls-inputs.json"
|
||||
localExceptionsFilename string = "exceptions.json"
|
||||
)
|
||||
|
||||
type BoolPtrFlag struct {
|
||||
valPtr *bool
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) Type() string {
|
||||
return "bool"
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) String() string {
|
||||
if bpf.valPtr != nil {
|
||||
return fmt.Sprintf("%v", *bpf.valPtr)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (bpf *BoolPtrFlag) Get() *bool {
|
||||
return bpf.valPtr
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) SetBool(val bool) {
|
||||
bpf.valPtr = &val
|
||||
}
|
||||
|
||||
func (bpf *BoolPtrFlag) Set(val string) error {
|
||||
switch val {
|
||||
case "true":
|
||||
bpf.SetBool(true)
|
||||
case "false":
|
||||
bpf.SetBool(false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ScanInfo struct {
|
||||
Getters
|
||||
PolicyIdentifier reporthandling.PolicyIdentifier
|
||||
UseExceptions string // Load exceptions configuration
|
||||
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
|
||||
Format string // Format results (table, json, junit ...)
|
||||
Output string // Store results in an output file, Output file name
|
||||
ExcludedNamespaces string // DEPRECATED?
|
||||
InputPatterns []string // Yaml files input patterns
|
||||
Silent bool // Silent mode - Do not print progress logs
|
||||
FailThreshold uint16 // Failure score threshold
|
||||
DoNotSendResults bool // DEPRECATED
|
||||
Submit bool // Submit results to Armo BE
|
||||
Local bool // Do not submit results
|
||||
Account string // account ID
|
||||
PolicyIdentifier []reporthandling.PolicyIdentifier
|
||||
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
|
||||
Format string // Format results (table, json, junit ...)
|
||||
Output string // Store results in an output file, Output file name
|
||||
ExcludedNamespaces string // used for host sensor namespace
|
||||
IncludeNamespaces string // DEPRECATED?
|
||||
InputPatterns []string // Yaml files input patterns
|
||||
Silent bool // Silent mode - Do not print progress logs
|
||||
FailThreshold uint16 // Failure score threshold
|
||||
Submit bool // Submit results to Armo BE
|
||||
HostSensor BoolPtrFlag // Deploy ARMO K8s host sensor to collect data from certain controls
|
||||
Local bool // Do not submit results
|
||||
Account string // account ID
|
||||
KubeContext string // context name
|
||||
FrameworkScan bool // false if scanning control
|
||||
ScanAll bool // true if scan all frameworks
|
||||
}
|
||||
|
||||
type Getters struct {
|
||||
ExceptionsGetter getter.IExceptionsGetter
|
||||
PolicyGetter getter.IPolicyGetter
|
||||
ExceptionsGetter getter.IExceptionsGetter
|
||||
ControlsInputsGetter getter.IControlsInputsGetter
|
||||
PolicyGetter getter.IPolicyGetter
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) Init() {
|
||||
scanInfo.setUseFrom()
|
||||
scanInfo.setUseExceptions()
|
||||
scanInfo.setOutputFile()
|
||||
scanInfo.setGetter()
|
||||
scanInfo.setUseArtifactsFrom()
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setUseArtifactsFrom() {
|
||||
if scanInfo.UseArtifactsFrom == "" {
|
||||
return
|
||||
}
|
||||
// UseArtifactsFrom must be a path without a filename
|
||||
dir, file := filepath.Split(scanInfo.UseArtifactsFrom)
|
||||
if dir == "" {
|
||||
scanInfo.UseArtifactsFrom = file
|
||||
} else if strings.Contains(file, ".json") {
|
||||
scanInfo.UseArtifactsFrom = dir
|
||||
}
|
||||
// set frameworks files
|
||||
files, err := ioutil.ReadDir(scanInfo.UseArtifactsFrom)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
framework := &reporthandling.Framework{}
|
||||
for _, f := range files {
|
||||
filePath := filepath.Join(scanInfo.UseArtifactsFrom, f.Name())
|
||||
file, err := os.ReadFile(filePath)
|
||||
if err == nil {
|
||||
if err := json.Unmarshal(file, framework); err == nil {
|
||||
scanInfo.UseFrom = append(scanInfo.UseFrom, filepath.Join(scanInfo.UseArtifactsFrom, f.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
// set config-inputs file
|
||||
scanInfo.ControlsInputs = filepath.Join(scanInfo.UseArtifactsFrom, localControlInputsFilename)
|
||||
// set exceptions
|
||||
scanInfo.UseExceptions = filepath.Join(scanInfo.UseArtifactsFrom, localExceptionsFilename)
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setUseExceptions() {
|
||||
if scanInfo.UseExceptions != "" {
|
||||
// load exceptions from file
|
||||
scanInfo.ExceptionsGetter = getter.NewLoadPolicy(scanInfo.UseExceptions)
|
||||
scanInfo.ExceptionsGetter = getter.NewLoadPolicy([]string{scanInfo.UseExceptions})
|
||||
} else {
|
||||
scanInfo.ExceptionsGetter = getter.NewArmoAPI()
|
||||
scanInfo.ExceptionsGetter = getter.GetArmoAPIConnector()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) setUseFrom() {
|
||||
if scanInfo.UseFrom != "" {
|
||||
return
|
||||
}
|
||||
if scanInfo.UseDefault {
|
||||
scanInfo.UseFrom = getter.GetDefaultPath(scanInfo.PolicyIdentifier.Name + ".json")
|
||||
}
|
||||
|
||||
}
|
||||
func (scanInfo *ScanInfo) setGetter() {
|
||||
if scanInfo.UseFrom != "" {
|
||||
// load from file
|
||||
scanInfo.PolicyGetter = getter.NewLoadPolicy(scanInfo.UseFrom)
|
||||
} else {
|
||||
scanInfo.PolicyGetter = getter.NewDownloadReleasedPolicy()
|
||||
for _, policy := range scanInfo.PolicyIdentifier {
|
||||
scanInfo.UseFrom = append(scanInfo.UseFrom, getter.GetDefaultPath(policy.Name+".json"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,11 +154,29 @@ func (scanInfo *ScanInfo) setOutputFile() {
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) ScanRunningCluster() bool {
|
||||
return len(scanInfo.InputPatterns) == 0
|
||||
func (scanInfo *ScanInfo) GetScanningEnvironment() string {
|
||||
if len(scanInfo.InputPatterns) != 0 {
|
||||
return ScanLocalFiles
|
||||
}
|
||||
return ScanCluster
|
||||
}
|
||||
|
||||
// func (scanInfo *ScanInfo) ConnectedToCluster(k8s k8sinterface.) bool {
|
||||
// _, err := k8s.KubernetesClient.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
|
||||
// return err == nil
|
||||
// }
|
||||
func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind reporthandling.NotificationPolicyKind) {
|
||||
for _, policy := range policies {
|
||||
if !scanInfo.contains(policy) {
|
||||
newPolicy := reporthandling.PolicyIdentifier{}
|
||||
newPolicy.Kind = kind // reporthandling.KindFramework
|
||||
newPolicy.Name = policy
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) contains(policyName string) bool {
|
||||
for _, policy := range scanInfo.PolicyIdentifier {
|
||||
if policy.Name == policyName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
136
cautils/versioncheck.go
Normal file
136
cautils/versioncheck.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
pkgutils "github.com/armosec/utils-go/utils"
|
||||
)
|
||||
|
||||
const SKIP_VERSION_CHECK = "KUBESCAPE_SKIP_UPDATE_CHECK"
|
||||
|
||||
var BuildNumber string
|
||||
|
||||
const UnknownBuildNumber = "unknown"
|
||||
|
||||
type IVersionCheckHandler interface {
|
||||
CheckLatestVersion(*VersionCheckRequest) error
|
||||
}
|
||||
|
||||
func NewIVersionCheckHandler() IVersionCheckHandler {
|
||||
if BuildNumber == "" {
|
||||
WarningDisplay(os.Stderr, "Warning: unknown build number, this might affect your scan results. Please make sure you are updated to latest version.\n")
|
||||
}
|
||||
if v, ok := os.LookupEnv(SKIP_VERSION_CHECK); ok && pkgutils.StringToBool(v) {
|
||||
return NewVersionCheckHandlerMock()
|
||||
}
|
||||
return NewVersionCheckHandler()
|
||||
}
|
||||
|
||||
type VersionCheckHandlerMock struct {
|
||||
}
|
||||
|
||||
func NewVersionCheckHandlerMock() *VersionCheckHandlerMock {
|
||||
return &VersionCheckHandlerMock{}
|
||||
}
|
||||
|
||||
type VersionCheckHandler struct {
|
||||
versionURL string
|
||||
}
|
||||
type VersionCheckRequest struct {
|
||||
Client string `json:"client"` // kubescape
|
||||
ClientVersion string `json:"clientVersion"` // kubescape version
|
||||
Framework string `json:"framework"` // framework name
|
||||
FrameworkVersion string `json:"frameworkVersion"` // framework version
|
||||
ScanningTarget string `json:"target"` // scanning target- cluster/yaml
|
||||
}
|
||||
|
||||
type VersionCheckResponse struct {
|
||||
Client string `json:"client"` // kubescape
|
||||
ClientUpdate string `json:"clientUpdate"` // kubescape latest version
|
||||
Framework string `json:"framework"` // framework name
|
||||
FrameworkUpdate string `json:"frameworkUpdate"` // framework latest version
|
||||
Message string `json:"message"` // alert message
|
||||
}
|
||||
|
||||
func NewVersionCheckHandler() *VersionCheckHandler {
|
||||
return &VersionCheckHandler{
|
||||
versionURL: "https://us-central1-elated-pottery-310110.cloudfunctions.net/ksgf1v1",
|
||||
}
|
||||
}
|
||||
func NewVersionCheckRequest(buildNumber, frameworkName, frameworkVersion, scanningTarget string) *VersionCheckRequest {
|
||||
if buildNumber == "" {
|
||||
buildNumber = UnknownBuildNumber
|
||||
}
|
||||
if scanningTarget == "" {
|
||||
scanningTarget = "unknown"
|
||||
}
|
||||
return &VersionCheckRequest{
|
||||
Client: "kubescape",
|
||||
ClientVersion: buildNumber,
|
||||
Framework: frameworkName,
|
||||
FrameworkVersion: frameworkVersion,
|
||||
ScanningTarget: scanningTarget,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VersionCheckHandlerMock) CheckLatestVersion(versionData *VersionCheckRequest) error {
|
||||
fmt.Println("Skipping version check")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckRequest) error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
WarningDisplay(os.Stderr, "failed to get latest version\n")
|
||||
}
|
||||
}()
|
||||
|
||||
latestVersion, err := v.getLatestVersion(versionData)
|
||||
if err != nil || latestVersion == nil {
|
||||
return fmt.Errorf("failed to get latest version")
|
||||
}
|
||||
|
||||
if latestVersion.ClientUpdate != "" {
|
||||
if BuildNumber != "" && BuildNumber < latestVersion.ClientUpdate {
|
||||
WarningDisplay(os.Stderr, warningMessage(latestVersion.Client, latestVersion.ClientUpdate), "\n")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - Enable after supporting framework version
|
||||
// if latestVersion.FrameworkUpdate != "" {
|
||||
// fmt.Println(warningMessage(latestVersion.Framework, latestVersion.FrameworkUpdate))
|
||||
// }
|
||||
|
||||
if latestVersion.Message != "" {
|
||||
InfoDisplay(os.Stderr, latestVersion.Message, "\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VersionCheckHandler) getLatestVersion(versionData *VersionCheckRequest) (*VersionCheckResponse, error) {
|
||||
|
||||
reqBody, err := json.Marshal(*versionData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("in 'CheckLatestVersion' failed to json.Marshal, reason: %s", err.Error())
|
||||
}
|
||||
|
||||
resp, err := getter.HttpPost(http.DefaultClient, v.versionURL, map[string]string{"Content-Type": "application/json"}, reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vResp := &VersionCheckResponse{}
|
||||
if err = getter.JSONDecoder(resp).Decode(vResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vResp, nil
|
||||
}
|
||||
|
||||
func warningMessage(kind, release string) string {
|
||||
return fmt.Sprintf("Warning: '%s' is not updated to the latest release: '%s'", kind, release)
|
||||
}
|
||||
176
clihandler/clidownload.go
Normal file
176
clihandler/clidownload.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
)
|
||||
|
||||
var downloadFunc = map[string]func(*cautils.DownloadInfo) error{
|
||||
"controls-inputs": downloadConfigInputs,
|
||||
"exceptions": downloadExceptions,
|
||||
"control": downloadControl,
|
||||
"framework": downloadFramework,
|
||||
"artifacts": downloadArtifacts,
|
||||
}
|
||||
|
||||
func DownloadSupportCommands() []string {
|
||||
commands := []string{}
|
||||
for k := range downloadFunc {
|
||||
commands = append(commands, k)
|
||||
}
|
||||
return commands
|
||||
}
|
||||
|
||||
func CliDownload(downloadInfo *cautils.DownloadInfo) error {
|
||||
setPathandFilename(downloadInfo)
|
||||
if err := downloadArtifact(downloadInfo, downloadFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadArtifact(downloadInfo *cautils.DownloadInfo, downloadArtifactFunc map[string]func(*cautils.DownloadInfo) error) error {
|
||||
if f, ok := downloadArtifactFunc[downloadInfo.Target]; ok {
|
||||
if err := f(downloadInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown command to download")
|
||||
}
|
||||
|
||||
func setPathandFilename(downloadInfo *cautils.DownloadInfo) {
|
||||
if downloadInfo.Path == "" {
|
||||
downloadInfo.Path = getter.GetDefaultPath("")
|
||||
} else {
|
||||
dir, file := filepath.Split(downloadInfo.Path)
|
||||
if dir == "" {
|
||||
downloadInfo.Path = file
|
||||
} else if strings.Contains(file, ".json") {
|
||||
downloadInfo.Path = dir
|
||||
downloadInfo.FileName = file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func downloadArtifacts(downloadInfo *cautils.DownloadInfo) error {
|
||||
downloadInfo.FileName = ""
|
||||
var artifacts = map[string]func(*cautils.DownloadInfo) error{
|
||||
"controls-inputs": downloadConfigInputs,
|
||||
"exceptions": downloadExceptions,
|
||||
"framework": downloadFramework,
|
||||
}
|
||||
for artifact := range artifacts {
|
||||
if err := downloadArtifact(&cautils.DownloadInfo{Target: artifact, Path: downloadInfo.Path, FileName: fmt.Sprintf("%s.json", artifact)}, artifacts); err != nil {
|
||||
fmt.Printf("error downloading %s, error: %s", artifact, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadConfigInputs(downloadInfo *cautils.DownloadInfo) error {
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
controlsInputsGetter := getConfigInputsGetter(downloadInfo.Name, tenant.GetCustomerGUID(), nil)
|
||||
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetClusterName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
|
||||
}
|
||||
// save in file
|
||||
err = getter.SaveInFile(controlInputs, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadExceptions(downloadInfo *cautils.DownloadInfo) error {
|
||||
var err error
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
exceptionsGetter := getExceptionsGetter("")
|
||||
exceptions := []armotypes.PostureExceptionPolicy{}
|
||||
if tenant.GetCustomerGUID() != "" {
|
||||
exceptions, err = exceptionsGetter.GetExceptions(tenant.GetClusterName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Target)
|
||||
}
|
||||
// save in file
|
||||
err = getter.SaveInFile(exceptions, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadFramework(downloadInfo *cautils.DownloadInfo) error {
|
||||
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), true, nil)
|
||||
|
||||
if downloadInfo.Name == "" {
|
||||
// if framework name not specified - download all frameworks
|
||||
frameworks, err := g.GetFrameworks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fw := range frameworks {
|
||||
err = getter.SaveInFile(fw, filepath.Join(downloadInfo.Path, (strings.ToLower(fw.Name)+".json")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("'%s': '%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, fw.Name, filepath.Join(downloadInfo.Path, (strings.ToLower(fw.Name)+".json")))
|
||||
}
|
||||
// return fmt.Errorf("missing framework name")
|
||||
} else {
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Name)
|
||||
}
|
||||
framework, err := g.GetFramework(downloadInfo.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = getter.SaveInFile(framework, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadControl(downloadInfo *cautils.DownloadInfo) error {
|
||||
|
||||
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
|
||||
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), false, nil)
|
||||
|
||||
if downloadInfo.Name == "" {
|
||||
// TODO - support
|
||||
return fmt.Errorf("missing control name")
|
||||
}
|
||||
if downloadInfo.FileName == "" {
|
||||
downloadInfo.FileName = fmt.Sprintf("%s.json", downloadInfo.Name)
|
||||
}
|
||||
controls, err := g.GetControl(downloadInfo.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = getter.SaveInFile(controls, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("'%s' downloaded successfully and saved at: '%s'\n", downloadInfo.Target, filepath.Join(downloadInfo.Path, downloadInfo.FileName))
|
||||
return nil
|
||||
}
|
||||
19
clihandler/cliinterfaces/submit.go
Normal file
19
clihandler/cliinterfaces/submit.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package cliinterfaces
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
)
|
||||
|
||||
type ISubmitObjects interface {
|
||||
SetResourcesReport() (*reporthandling.PostureReport, error)
|
||||
ListAllResources() (map[string]workloadinterface.IMetadata, error)
|
||||
}
|
||||
|
||||
type SubmitInterfaces struct {
|
||||
SubmitObjects ISubmitObjects
|
||||
Reporter reporter.IReport
|
||||
ClusterConfig cautils.ITenantConfig
|
||||
}
|
||||
58
clihandler/clilist.go
Normal file
58
clihandler/clilist.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
)
|
||||
|
||||
var listFunc = map[string]func(*cautils.ListPolicies) ([]string, error){
|
||||
"controls": listControls,
|
||||
"frameworks": listFrameworks,
|
||||
}
|
||||
|
||||
func ListSupportCommands() []string {
|
||||
commands := []string{}
|
||||
for k := range listFunc {
|
||||
commands = append(commands, k)
|
||||
}
|
||||
return commands
|
||||
}
|
||||
func CliList(listPolicies *cautils.ListPolicies) error {
|
||||
if f, ok := listFunc[listPolicies.Target]; ok {
|
||||
policies, err := f(listPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Strings(policies)
|
||||
|
||||
sep := "\n * "
|
||||
usageCmd := strings.TrimSuffix(listPolicies.Target, "s")
|
||||
fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep))
|
||||
fmt.Printf("\nUseage:\n")
|
||||
fmt.Printf("$ kubescape scan %s \"name\"\n", usageCmd)
|
||||
fmt.Printf("$ kubescape scan %s \"name-0\",\"name-1\"\n\n", usageCmd)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown command to download")
|
||||
}
|
||||
|
||||
func listFrameworks(listPolicies *cautils.ListPolicies) ([]string, error) {
|
||||
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
|
||||
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), true, nil)
|
||||
|
||||
return listFrameworksNames(g), nil
|
||||
}
|
||||
|
||||
func listControls(listPolicies *cautils.ListPolicies) ([]string, error) {
|
||||
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
|
||||
g := getPolicyGetter(nil, tenant.GetCustomerGUID(), false, nil)
|
||||
l := getter.ListName
|
||||
if listPolicies.ListIDs {
|
||||
l = getter.ListID
|
||||
}
|
||||
return g.ListControls(l)
|
||||
}
|
||||
@@ -11,10 +11,9 @@ import (
|
||||
)
|
||||
|
||||
var getCmd = &cobra.Command{
|
||||
Use: "get <key>",
|
||||
Short: "Get configuration in cluster",
|
||||
Long: ``,
|
||||
ValidArgs: supportedFrameworks,
|
||||
Use: "get <key>",
|
||||
Short: "Get configuration in cluster",
|
||||
Long: ``,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 || len(args) > 1 {
|
||||
return fmt.Errorf("requires one argument")
|
||||
@@ -31,7 +30,7 @@ var getCmd = &cobra.Command{
|
||||
key := keyValue[0]
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
clusterConfig := cautils.NewClusterConfig(k8s, getter.NewArmoAPI())
|
||||
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, "")
|
||||
val, err := clusterConfig.GetValueByKeyFromConfigMap(key)
|
||||
if err != nil {
|
||||
if err.Error() == "value does not exist." {
|
||||
@@ -30,7 +30,7 @@ var setCmd = &cobra.Command{
|
||||
data := keyValue[1]
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
clusterConfig := cautils.NewClusterConfig(k8s, getter.NewArmoAPI())
|
||||
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, "")
|
||||
if err := clusterConfig.SetKeyValueInConfigmap(key, data); err != nil {
|
||||
return err
|
||||
}
|
||||
122
clihandler/cmd/control.go
Normal file
122
clihandler/cmd/control.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
controlExample = `
|
||||
# Scan the 'privileged container' control
|
||||
kubescape scan control "privileged container"
|
||||
|
||||
# Scan list of controls separated with a comma
|
||||
kubescape scan control "privileged container","allowed hostpath"
|
||||
|
||||
# Scan list of controls using the control ID separated with a comma
|
||||
kubescape scan control C-0058,C-0057
|
||||
|
||||
Run 'kubescape list controls' for the list of supported controls
|
||||
|
||||
Control documentation:
|
||||
https://hub.armo.cloud/docs/controls
|
||||
`
|
||||
)
|
||||
|
||||
// controlCmd represents the control command
|
||||
var controlCmd = &cobra.Command{
|
||||
Use: "control <control names list>/<control ids list>",
|
||||
Short: "The controls you wish to use. Run 'kubescape list controls' for the list of supported controls",
|
||||
Example: controlExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
controls := strings.Split(args[0], ",")
|
||||
if len(controls) > 1 {
|
||||
if controls[1] == "" {
|
||||
return fmt.Errorf("usage: <control-0>,<control-1>")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("requires at least one control name")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
flagValidationControl()
|
||||
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
|
||||
|
||||
if len(args) == 0 {
|
||||
// scanInfo.SetPolicyIdentifiers(getter.NativeFrameworks, reporthandling.KindFramework)
|
||||
scanInfo.ScanAll = true
|
||||
} else { // expected control or list of control sepparated by ","
|
||||
|
||||
// Read controls from input args
|
||||
scanInfo.SetPolicyIdentifiers(strings.Split(args[0], ","), reporthandling.KindControl)
|
||||
|
||||
if len(args) > 1 {
|
||||
if len(args[1:]) == 0 || args[1] != "-" {
|
||||
scanInfo.InputPatterns = args[1:]
|
||||
} else { // store stdin to file - do NOT move to separate function !!
|
||||
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
|
||||
return err
|
||||
}
|
||||
scanInfo.InputPatterns = []string{tempFile.Name()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scanInfo.FrameworkScan = false
|
||||
scanInfo.Init()
|
||||
cautils.SetSilentMode(scanInfo.Silent)
|
||||
err := clihandler.ScanCliSetup(&scanInfo)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
scanInfo = cautils.ScanInfo{}
|
||||
scanCmd.AddCommand(controlCmd)
|
||||
}
|
||||
|
||||
func flagValidationControl() {
|
||||
if 100 < scanInfo.FailThreshold {
|
||||
fmt.Println("bad argument: out of range threshold")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func setScanForFirstControl(controls []string) []reporthandling.PolicyIdentifier {
|
||||
newPolicy := reporthandling.PolicyIdentifier{}
|
||||
newPolicy.Kind = reporthandling.KindControl
|
||||
newPolicy.Name = controls[0]
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
return scanInfo.PolicyIdentifier
|
||||
}
|
||||
|
||||
func SetScanForGivenControls(controls []string) []reporthandling.PolicyIdentifier {
|
||||
for _, control := range controls {
|
||||
control := strings.TrimLeft(control, " ")
|
||||
newPolicy := reporthandling.PolicyIdentifier{}
|
||||
newPolicy.Kind = reporthandling.KindControl
|
||||
newPolicy.Name = control
|
||||
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
}
|
||||
return scanInfo.PolicyIdentifier
|
||||
}
|
||||
75
clihandler/cmd/download.go
Normal file
75
clihandler/cmd/download.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var downloadInfo = cautils.DownloadInfo{}
|
||||
|
||||
var (
|
||||
downloadExample = `
|
||||
# Download all artifacts and save them in the default path (~/.kubescape)
|
||||
kubescape download artifacts
|
||||
|
||||
# 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 frameworks 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 configured exceptions
|
||||
kubescape download exceptions
|
||||
|
||||
# Download the configured controls-inputs
|
||||
kubescape download controls-inputs
|
||||
|
||||
`
|
||||
)
|
||||
var downloadCmd = &cobra.Command{
|
||||
Use: "download <policy> <policy name>",
|
||||
Short: fmt.Sprintf("Download %s", strings.Join(clihandler.DownloadSupportCommands(), ",")),
|
||||
Long: ``,
|
||||
Example: downloadExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
supported := strings.Join(clihandler.DownloadSupportCommands(), ",")
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("policy type required, supported: %v", supported)
|
||||
}
|
||||
if cautils.StringInSlice(clihandler.DownloadSupportCommands(), args[0]) == cautils.ValueNotFound {
|
||||
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
downloadInfo.Target = args[0]
|
||||
if len(args) >= 2 {
|
||||
downloadInfo.Name = args[1]
|
||||
}
|
||||
if err := clihandler.CliDownload(&downloadInfo); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// cobra.OnInitialize(initConfig)
|
||||
|
||||
rootCmd.AddCommand(downloadCmd)
|
||||
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
|
||||
}
|
||||
132
clihandler/cmd/framework.go
Normal file
132
clihandler/cmd/framework.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
frameworkExample = `
|
||||
# Scan all frameworks and submit the results
|
||||
kubescape scan --submit
|
||||
|
||||
# Scan the NSA framework
|
||||
kubescape scan framework nsa
|
||||
|
||||
# Scan the NSA and MITRE framework
|
||||
kubescape scan framework nsa,mitre
|
||||
|
||||
# Scan all frameworks
|
||||
kubescape scan framework all
|
||||
|
||||
# Scan kubernetes YAML manifest files
|
||||
kubescape scan framework nsa *.yaml
|
||||
|
||||
# Scan and save the results in the JSON format
|
||||
kubescape scan --format json --output results.json
|
||||
|
||||
# Save scan results in JSON format
|
||||
kubescape scan --format json --output results.json
|
||||
|
||||
# Display all resources
|
||||
kubescape scan --verbose
|
||||
|
||||
Run 'kubescape list frameworks' for the list of supported frameworks
|
||||
`
|
||||
)
|
||||
var frameworkCmd = &cobra.Command{
|
||||
Use: "framework <framework names list> [`<glob pattern>`/`-`] [flags]",
|
||||
Short: "The framework you wish to use. Run 'kubescape list frameworks' for the list of supported frameworks",
|
||||
Example: frameworkExample,
|
||||
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
|
||||
// ValidArgs: getter.NativeFrameworks,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
frameworks := strings.Split(args[0], ",")
|
||||
if len(frameworks) > 1 {
|
||||
if frameworks[1] == "" {
|
||||
return fmt.Errorf("usage: <framework-0>,<framework-1>")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("requires at least one framework name")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
flagValidationFramework()
|
||||
var frameworks []string
|
||||
|
||||
if len(args) == 0 { // scan all frameworks
|
||||
// frameworks = getter.NativeFrameworks
|
||||
scanInfo.ScanAll = true
|
||||
} else {
|
||||
// Read frameworks from input args
|
||||
frameworks = strings.Split(args[0], ",")
|
||||
if cautils.StringInSlice(frameworks, "all") != cautils.ValueNotFound {
|
||||
scanInfo.ScanAll = true
|
||||
frameworks = []string{}
|
||||
}
|
||||
if len(args) > 1 {
|
||||
if len(args[1:]) == 0 || args[1] != "-" {
|
||||
scanInfo.InputPatterns = args[1:]
|
||||
} else { // store stdin to file - do NOT move to separate function !!
|
||||
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
|
||||
return err
|
||||
}
|
||||
scanInfo.InputPatterns = []string{tempFile.Name()}
|
||||
}
|
||||
}
|
||||
}
|
||||
scanInfo.FrameworkScan = true
|
||||
|
||||
scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
|
||||
|
||||
scanInfo.Init()
|
||||
cautils.SetSilentMode(scanInfo.Silent)
|
||||
err := clihandler.ScanCliSetup(&scanInfo)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
scanCmd.AddCommand(frameworkCmd)
|
||||
scanInfo = cautils.ScanInfo{}
|
||||
scanInfo.FrameworkScan = true
|
||||
}
|
||||
|
||||
// func SetScanForFirstFramework(frameworks []string) []reporthandling.PolicyIdentifier {
|
||||
// newPolicy := reporthandling.PolicyIdentifier{}
|
||||
// newPolicy.Kind = reporthandling.KindFramework
|
||||
// newPolicy.Name = frameworks[0]
|
||||
// scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
|
||||
// return scanInfo.PolicyIdentifier
|
||||
// }
|
||||
|
||||
func flagValidationFramework() {
|
||||
if scanInfo.Submit && scanInfo.Local {
|
||||
fmt.Println("You can use `keep-local` or `submit`, but not both")
|
||||
os.Exit(1)
|
||||
}
|
||||
if 100 < scanInfo.FailThreshold {
|
||||
fmt.Println("bad argument: out of range threshold")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
66
clihandler/cmd/list.go
Normal file
66
clihandler/cmd/list.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
listExample = `
|
||||
# List default supported frameworks names
|
||||
kubescape list frameworks
|
||||
|
||||
# List all supported frameworks names
|
||||
kubescape list frameworks --account <account id>
|
||||
|
||||
# List all supported controls names
|
||||
kubescape list controls
|
||||
|
||||
# List all supported controls ids
|
||||
kubescape list controls --id
|
||||
|
||||
Control documentation:
|
||||
https://hub.armo.cloud/docs/controls
|
||||
`
|
||||
)
|
||||
var listPolicies = cautils.ListPolicies{}
|
||||
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list <policy> [flags]",
|
||||
Short: "List frameworks/controls will list the supported frameworks and controls",
|
||||
Long: ``,
|
||||
Example: listExample,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
supported := strings.Join(clihandler.ListSupportCommands(), ",")
|
||||
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("policy type requeued, supported: %s", supported)
|
||||
}
|
||||
if cautils.StringInSlice(clihandler.ListSupportCommands(), args[0]) == cautils.ValueNotFound {
|
||||
return fmt.Errorf("invalid parameter '%s'. Supported parameters: %s", args[0], supported)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
listPolicies.Target = args[0]
|
||||
|
||||
if err := clihandler.CliList(&listPolicies); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// cobra.OnInitialize(initConfig)
|
||||
|
||||
rootCmd.AddCommand(listCmd)
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
listCmd.PersistentFlags().BoolVarP(&listPolicies.ListIDs, "id", "", false, "List control ID's instead of controls names")
|
||||
}
|
||||
53
clihandler/cmd/rbac.go
Normal file
53
clihandler/cmd/rbac.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliinterfaces"
|
||||
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
|
||||
"github.com/armosec/rbac-utils/rbacscanner"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// rabcCmd represents the RBAC command
|
||||
var rabcCmd = &cobra.Command{
|
||||
Use: "rbac \nExample:\n$ kubescape submit rbac",
|
||||
Short: "Submit cluster's Role-Based Access Control(RBAC)",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig, err := getSubmittedClusterConfig(k8s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// list RBAC
|
||||
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetCustomerGUID(), clusterConfig.GetClusterName()))
|
||||
|
||||
// submit resources
|
||||
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
SubmitObjects: rbacObjects,
|
||||
Reporter: r,
|
||||
}
|
||||
|
||||
if err := clihandler.Submit(submitInterfaces); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
submitCmd.AddCommand(rabcCmd)
|
||||
}
|
||||
107
clihandler/cmd/results.go
Normal file
107
clihandler/cmd/results.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
"github.com/armosec/kubescape/clihandler"
|
||||
"github.com/armosec/kubescape/clihandler/cliinterfaces"
|
||||
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type ResultsObject struct {
|
||||
filePath string
|
||||
customerGUID string
|
||||
clusterName string
|
||||
}
|
||||
|
||||
func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject {
|
||||
return &ResultsObject{
|
||||
filePath: filePath,
|
||||
customerGUID: customerGUID,
|
||||
clusterName: clusterName,
|
||||
}
|
||||
}
|
||||
|
||||
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandling.PostureReport, error) {
|
||||
// load framework results from json file
|
||||
frameworkReports, err := loadResultsFromFile(resultsObject.filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &reporthandling.PostureReport{
|
||||
FrameworkReports: frameworkReports,
|
||||
ReportID: uuid.NewV4().String(),
|
||||
ReportGenerationTime: time.Now().UTC(),
|
||||
CustomerGUID: resultsObject.customerGUID,
|
||||
ClusterName: resultsObject.clusterName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
|
||||
return map[string]workloadinterface.IMetadata{}, nil
|
||||
}
|
||||
|
||||
var resultsCmd = &cobra.Command{
|
||||
Use: "results <json file>\nExample:\n$ kubescape submit results path/to/results.json",
|
||||
Short: "Submit a pre scanned results file. The file must be in json format",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("missing results file")
|
||||
}
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig, err := getSubmittedClusterConfig(k8s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resultsObjects := NewResultsObject(clusterConfig.GetCustomerGUID(), clusterConfig.GetClusterName(), args[0])
|
||||
|
||||
// submit resources
|
||||
r := reporterv1.NewReportEventReceiver(clusterConfig.GetConfigObj())
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
SubmitObjects: resultsObjects,
|
||||
Reporter: r,
|
||||
}
|
||||
|
||||
if err := clihandler.Submit(submitInterfaces); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
submitCmd.AddCommand(resultsCmd)
|
||||
}
|
||||
|
||||
func loadResultsFromFile(filePath string) ([]reporthandling.FrameworkReport, error) {
|
||||
frameworkReports := []reporthandling.FrameworkReport{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(f, &frameworkReports); err != nil {
|
||||
frameworkReport := reporthandling.FrameworkReport{}
|
||||
if err = json.Unmarshal(f, &frameworkReport); err != nil {
|
||||
return frameworkReports, err
|
||||
}
|
||||
frameworkReports = append(frameworkReports, frameworkReport)
|
||||
}
|
||||
return frameworkReports, nil
|
||||
}
|
||||
64
clihandler/cmd/root.go
Normal file
64
clihandler/cmd/root.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
var armoBEURLs = ""
|
||||
|
||||
const envFlagUsage = "Send report results to specific URL. Format:<ReportReceiver>,<Backend>,<Frontend>.\n\t\tExample:report.armo.cloud,api.armo.cloud,portal.armo.cloud"
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "kubescape",
|
||||
Short: "Kubescape is a tool for testing Kubernetes security posture",
|
||||
Long: `Kubescape is a tool for testing Kubernetes security posture based on NSA \ MITRE ATT&CK® specifications.`,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
flag.Parse()
|
||||
InitArmoBEConnector()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
rootCmd.Execute()
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&scanInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
flag.CommandLine.StringVar(&armoBEURLs, "environment", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().StringVar(&armoBEURLs, "environment", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().MarkHidden("environment")
|
||||
|
||||
}
|
||||
|
||||
func InitArmoBEConnector() {
|
||||
urlSlices := strings.Split(armoBEURLs, ",")
|
||||
if len(urlSlices) > 3 {
|
||||
glog.Errorf("Too many URLs")
|
||||
os.Exit(1)
|
||||
}
|
||||
switch len(urlSlices) {
|
||||
case 1:
|
||||
switch urlSlices[0] {
|
||||
case "dev":
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPIDev())
|
||||
case "":
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPIProd())
|
||||
default:
|
||||
glog.Errorf("--environment flag usage: %s", envFlagUsage)
|
||||
os.Exit(1)
|
||||
}
|
||||
case 2:
|
||||
glog.Errorf("--environment flag usage: %s", envFlagUsage)
|
||||
os.Exit(1)
|
||||
case 3:
|
||||
getter.SetARMOAPIConnector(getter.NewARMOAPICustomized(urlSlices[0], urlSlices[1], urlSlices[2]))
|
||||
}
|
||||
}
|
||||
64
clihandler/cmd/scan.go
Normal file
64
clihandler/cmd/scan.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var scanInfo cautils.ScanInfo
|
||||
|
||||
// scanCmd represents the scan command
|
||||
var scanCmd = &cobra.Command{
|
||||
Use: "scan <command>",
|
||||
Short: "Scan the current running cluster or yaml files",
|
||||
Long: `The action you want to perform`,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
if !strings.EqualFold(args[0], "framework") && !strings.EqualFold(args[0], "control") {
|
||||
return fmt.Errorf("invalid parameter '%s'. Supported parameters: framework, control", args[0])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
scanInfo.ScanAll = true
|
||||
// frameworks := getter.NativeFrameworks
|
||||
// frameworkArgs := []string{strings.Join(frameworks, ",")}
|
||||
frameworkCmd.RunE(cmd, []string{"all"})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func frameworkInitConfig() {
|
||||
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(frameworkInitConfig)
|
||||
|
||||
rootCmd.AddCommand(scanCmd)
|
||||
rootCmd.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().Uint16VarP(&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"`)
|
||||
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().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
|
||||
scanCmd.PersistentFlags().BoolVar(&scanInfo.VerboseMode, "verbose", false, "Display all of the input resources and not only failed resources")
|
||||
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.Silent, "silent", "s", false, "Silent progress messages")
|
||||
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")
|
||||
|
||||
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensor, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valueable data from cluster nodes for certain controls")
|
||||
hostF.NoOptDefVal = "true"
|
||||
hostF.DefValue = "false, for no TTY in stdin"
|
||||
}
|
||||
31
clihandler/cmd/submit.go
Normal file
31
clihandler/cmd/submit.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var submitCmd = &cobra.Command{
|
||||
Use: "submit <command>",
|
||||
Short: "Submit an object to the Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(submitCmd)
|
||||
}
|
||||
|
||||
func getSubmittedClusterConfig(k8s *k8sinterface.KubernetesApi) (*cautils.ClusterConfig, error) {
|
||||
clusterConfig := cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), scanInfo.Account, scanInfo.KubeContext) // TODO - support none cluster env submit
|
||||
if clusterConfig.GetCustomerGUID() != "" {
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
return clusterConfig, err
|
||||
}
|
||||
}
|
||||
|
||||
return clusterConfig, nil
|
||||
}
|
||||
24
clihandler/cmd/version.go
Normal file
24
clihandler/cmd/version.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Get current version",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := cautils.NewIVersionCheckHandler()
|
||||
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
|
||||
fmt.Println("Your current version is: " + cautils.BuildNumber)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
214
clihandler/initcli.go
Normal file
214
clihandler/initcli.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
printerv1 "github.com/armosec/kubescape/resultshandling/printer/v1"
|
||||
|
||||
// printerv2 "github.com/armosec/kubescape/resultshandling/printer/v2"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/clihandler/cliinterfaces"
|
||||
"github.com/armosec/kubescape/hostsensorutils"
|
||||
"github.com/armosec/kubescape/opaprocessor"
|
||||
"github.com/armosec/kubescape/policyhandler"
|
||||
"github.com/armosec/kubescape/resourcehandler"
|
||||
"github.com/armosec/kubescape/resultshandling"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
type componentInterfaces struct {
|
||||
tenantConfig cautils.ITenantConfig
|
||||
resourceHandler resourcehandler.IResourceHandler
|
||||
report reporter.IReport
|
||||
printerHandler printer.IPrinter
|
||||
hostSensorHandler hostsensorutils.IHostSensor
|
||||
}
|
||||
|
||||
func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
|
||||
|
||||
var k8s *k8sinterface.KubernetesApi
|
||||
if scanInfo.GetScanningEnvironment() == cautils.ScanCluster {
|
||||
k8s = getKubernetesApi()
|
||||
if k8s == nil {
|
||||
fmt.Println("Failed connecting to Kubernetes cluster")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
tenantConfig := getTenantConfig(scanInfo.Account, scanInfo.KubeContext, k8s)
|
||||
|
||||
// Set submit behavior AFTER loading tenant config
|
||||
setSubmitBehavior(scanInfo, tenantConfig)
|
||||
|
||||
hostSensorHandler := getHostSensorHandler(scanInfo, k8s)
|
||||
if err := hostSensorHandler.Init(); err != nil {
|
||||
errMsg := "failed to init host sensor"
|
||||
if scanInfo.VerboseMode {
|
||||
errMsg = fmt.Sprintf("%s: %v", errMsg, err)
|
||||
}
|
||||
cautils.ErrorDisplay(errMsg)
|
||||
hostSensorHandler = &hostsensorutils.HostSensorHandlerMock{}
|
||||
}
|
||||
// excluding hostsensor namespace
|
||||
if len(scanInfo.IncludeNamespaces) == 0 && hostSensorHandler.GetNamespace() != "" {
|
||||
scanInfo.ExcludedNamespaces = fmt.Sprintf("%s,%s", scanInfo.ExcludedNamespaces, hostSensorHandler.GetNamespace())
|
||||
}
|
||||
|
||||
resourceHandler := getResourceHandler(scanInfo, tenantConfig, k8s, hostSensorHandler)
|
||||
|
||||
// reporting behavior - setup reporter
|
||||
reportHandler := getReporter(tenantConfig, scanInfo.Submit)
|
||||
|
||||
v := cautils.NewIVersionCheckHandler()
|
||||
v.CheckLatestVersion(cautils.NewVersionCheckRequest(cautils.BuildNumber, policyIdentifierNames(scanInfo.PolicyIdentifier), "", scanInfo.GetScanningEnvironment()))
|
||||
|
||||
// setup printer
|
||||
printerHandler := printerv1.GetPrinter(scanInfo.Format, scanInfo.VerboseMode)
|
||||
// printerHandler = printerv2.GetPrinter(scanInfo.Format, scanInfo.VerboseMode)
|
||||
printerHandler.SetWriter(scanInfo.Output)
|
||||
|
||||
return componentInterfaces{
|
||||
tenantConfig: tenantConfig,
|
||||
resourceHandler: resourceHandler,
|
||||
report: reportHandler,
|
||||
printerHandler: printerHandler,
|
||||
hostSensorHandler: hostSensorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func ScanCliSetup(scanInfo *cautils.ScanInfo) error {
|
||||
cautils.ScanStartDisplay()
|
||||
|
||||
interfaces := getInterfaces(scanInfo)
|
||||
// setPolicyGetter(scanInfo, interfaces.clusterConfig.GetCustomerGUID())
|
||||
|
||||
processNotification := make(chan *cautils.OPASessionObj)
|
||||
reportResults := make(chan *cautils.OPASessionObj)
|
||||
|
||||
cautils.ClusterName = interfaces.tenantConfig.GetClusterName() // TODO - Deprecated
|
||||
cautils.CustomerGUID = interfaces.tenantConfig.GetCustomerGUID() // TODO - Deprecated
|
||||
interfaces.report.SetClusterName(interfaces.tenantConfig.GetClusterName())
|
||||
interfaces.report.SetCustomerGUID(interfaces.tenantConfig.GetCustomerGUID())
|
||||
|
||||
downloadReleasedPolicy := getter.NewDownloadReleasedPolicy() // download config inputs from github release
|
||||
|
||||
// set policy getter only after setting the customerGUID
|
||||
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetCustomerGUID(), scanInfo.FrameworkScan, downloadReleasedPolicy)
|
||||
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(scanInfo.ControlsInputs, interfaces.tenantConfig.GetCustomerGUID(), downloadReleasedPolicy)
|
||||
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(scanInfo.UseExceptions)
|
||||
|
||||
// TODO - list supported frameworks/controls
|
||||
if scanInfo.ScanAll {
|
||||
scanInfo.SetPolicyIdentifiers(listFrameworksNames(scanInfo.Getters.PolicyGetter), reporthandling.KindFramework)
|
||||
}
|
||||
|
||||
//
|
||||
defer func() {
|
||||
if err := interfaces.hostSensorHandler.TearDown(); err != nil {
|
||||
errMsg := "failed to tear down host sensor"
|
||||
if scanInfo.VerboseMode {
|
||||
errMsg = fmt.Sprintf("%s: %v", errMsg, err)
|
||||
}
|
||||
cautils.ErrorDisplay(errMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
// cli handler setup
|
||||
go func() {
|
||||
// policy handler setup
|
||||
policyHandler := policyhandler.NewPolicyHandler(&processNotification, interfaces.resourceHandler)
|
||||
|
||||
if err := Scan(policyHandler, scanInfo); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// processor setup - rego run
|
||||
go func() {
|
||||
opaprocessorObj := opaprocessor.NewOPAProcessorHandler(&processNotification, &reportResults)
|
||||
opaprocessorObj.ProcessRulesListenner()
|
||||
}()
|
||||
|
||||
resultsHandling := resultshandling.NewResultsHandler(&reportResults, interfaces.report, interfaces.printerHandler)
|
||||
score := resultsHandling.HandleResults(scanInfo)
|
||||
|
||||
// print report url
|
||||
interfaces.report.DisplayReportURL()
|
||||
|
||||
if score > float32(scanInfo.FailThreshold) {
|
||||
return fmt.Errorf("scan risk-score %.2f is above permitted threshold %d", score, scanInfo.FailThreshold)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Scan(policyHandler *policyhandler.PolicyHandler, scanInfo *cautils.ScanInfo) error {
|
||||
policyNotification := &reporthandling.PolicyNotification{
|
||||
NotificationType: reporthandling.TypeExecPostureScan,
|
||||
Rules: scanInfo.PolicyIdentifier,
|
||||
Designators: armotypes.PortalDesignator{},
|
||||
}
|
||||
switch policyNotification.NotificationType {
|
||||
case reporthandling.TypeExecPostureScan:
|
||||
if err := policyHandler.HandleNotificationRequest(policyNotification, scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error {
|
||||
|
||||
// list resources
|
||||
postureReport, err := submitInterfaces.SubmitObjects.SetResourcesReport()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allresources, err := submitInterfaces.SubmitObjects.ListAllResources()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// report
|
||||
if err := submitInterfaces.Reporter.ActionSendReport(&cautils.OPASessionObj{PostureReport: postureReport, AllResources: allresources}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("\nData has been submitted successfully")
|
||||
submitInterfaces.Reporter.DisplayReportURL()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func askUserForHostSensor() bool {
|
||||
return false
|
||||
|
||||
if !isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
return false
|
||||
}
|
||||
if ssss, err := os.Stdin.Stat(); err == nil {
|
||||
// fmt.Printf("Found stdin type: %s\n", ssss.Mode().Type())
|
||||
if ssss.Mode().Type()&(fs.ModeDevice|fs.ModeCharDevice) > 0 { //has TTY
|
||||
fmt.Printf("Would you like to scan K8s nodes? [y/N]. This is required to collect valuable data for certain controls\n")
|
||||
fmt.Printf("Use --enable-host-scan flag to suppress this message\n")
|
||||
var b []byte = make([]byte, 1)
|
||||
if n, err := os.Stdin.Read(b); err == nil {
|
||||
if n > 0 && len(b) > 0 && (b[0] == 'y' || b[0] == 'Y') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
221
clihandler/initcliutils.go
Normal file
221
clihandler/initcliutils.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package clihandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/hostsensorutils"
|
||||
"github.com/armosec/kubescape/resourcehandler"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
reporterv1 "github.com/armosec/kubescape/resultshandling/reporter/v1"
|
||||
reporterv2 "github.com/armosec/kubescape/resultshandling/reporter/v2"
|
||||
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/rbac-utils/rbacscanner"
|
||||
)
|
||||
|
||||
// getKubernetesApi
|
||||
func getKubernetesApi() *k8sinterface.KubernetesApi {
|
||||
if !k8sinterface.IsConnectedToCluster() {
|
||||
return nil
|
||||
}
|
||||
return k8sinterface.NewKubernetesApi()
|
||||
}
|
||||
func getTenantConfig(Account, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), Account, clusterName)
|
||||
}
|
||||
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), Account, clusterName)
|
||||
}
|
||||
|
||||
func getExceptionsGetter(useExceptions string) getter.IExceptionsGetter {
|
||||
if useExceptions != "" {
|
||||
// load exceptions from file
|
||||
return getter.NewLoadPolicy([]string{useExceptions})
|
||||
} else {
|
||||
return getter.GetArmoAPIConnector()
|
||||
}
|
||||
}
|
||||
|
||||
func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, submit bool) *cautils.RBACObjects {
|
||||
if submit {
|
||||
return cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, tenantConfig.GetCustomerGUID(), tenantConfig.GetClusterName()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getReporter(tenantConfig cautils.ITenantConfig, submit bool) reporter.IReport {
|
||||
if submit {
|
||||
// return reporterv1.NewReportEventReceiver(tenantConfig.GetConfigObj())
|
||||
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj())
|
||||
}
|
||||
return reporterv1.NewReportMock()
|
||||
}
|
||||
|
||||
func getResourceHandler(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor) resourcehandler.IResourceHandler {
|
||||
if len(scanInfo.InputPatterns) > 0 || k8s == nil {
|
||||
return resourcehandler.NewFileResourceHandler(scanInfo.InputPatterns)
|
||||
}
|
||||
rbacObjects := getRBACHandler(tenantConfig, k8s, scanInfo.Submit)
|
||||
return resourcehandler.NewK8sResourceHandler(k8s, getFieldSelector(scanInfo), hostSensorHandler, rbacObjects)
|
||||
}
|
||||
|
||||
func getHostSensorHandler(scanInfo *cautils.ScanInfo, k8s *k8sinterface.KubernetesApi) hostsensorutils.IHostSensor {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return &hostsensorutils.HostSensorHandlerMock{}
|
||||
}
|
||||
|
||||
hasHostSensorControls := true
|
||||
// we need to determined which controls needs host sensor
|
||||
if scanInfo.HostSensor.Get() == nil && hasHostSensorControls {
|
||||
scanInfo.HostSensor.SetBool(askUserForHostSensor())
|
||||
cautils.WarningDisplay(os.Stderr, "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\n")
|
||||
}
|
||||
if hostSensorVal := scanInfo.HostSensor.Get(); hostSensorVal != nil && *hostSensorVal {
|
||||
hostSensorHandler, err := hostsensorutils.NewHostSensorHandler(k8s)
|
||||
if err != nil {
|
||||
cautils.WarningDisplay(os.Stderr, fmt.Sprintf("Warning: failed to create host sensor: %v\n", err.Error()))
|
||||
return &hostsensorutils.HostSensorHandlerMock{}
|
||||
}
|
||||
return hostSensorHandler
|
||||
}
|
||||
return &hostsensorutils.HostSensorHandlerMock{}
|
||||
}
|
||||
func getFieldSelector(scanInfo *cautils.ScanInfo) resourcehandler.IFieldSelector {
|
||||
if scanInfo.IncludeNamespaces != "" {
|
||||
return resourcehandler.NewIncludeSelector(scanInfo.IncludeNamespaces)
|
||||
}
|
||||
if scanInfo.ExcludedNamespaces != "" {
|
||||
return resourcehandler.NewExcludeSelector(scanInfo.ExcludedNamespaces)
|
||||
}
|
||||
|
||||
return &resourcehandler.EmptySelector{}
|
||||
}
|
||||
|
||||
func policyIdentifierNames(pi []reporthandling.PolicyIdentifier) string {
|
||||
policiesNames := ""
|
||||
for i := range pi {
|
||||
policiesNames += pi[i].Name
|
||||
if i+1 < len(pi) {
|
||||
policiesNames += ","
|
||||
}
|
||||
}
|
||||
if policiesNames == "" {
|
||||
policiesNames = "all"
|
||||
}
|
||||
return policiesNames
|
||||
}
|
||||
|
||||
// setSubmitBehavior - Setup the desired cluster behavior regarding submittion to the Armo 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 "Submitted" -
|
||||
keep-local - Do not send report
|
||||
Default/Submit - Submit report
|
||||
|
||||
*/
|
||||
|
||||
// do not submit control scanning
|
||||
if !scanInfo.FrameworkScan {
|
||||
scanInfo.Submit = false
|
||||
return
|
||||
}
|
||||
|
||||
if tenantConfig.IsConfigFound() { // config found in cache (submitted)
|
||||
if !scanInfo.Local {
|
||||
// Submit report
|
||||
scanInfo.Submit = true
|
||||
}
|
||||
} else { // config not found in cache (not submitted)
|
||||
if scanInfo.Submit {
|
||||
// submit - Create tenant & Submit report
|
||||
if err := tenantConfig.SetTenant(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setPolicyGetter set the policy getter - local file/github release/ArmoAPI
|
||||
func getPolicyGetter(loadPoliciesFromFile []string, accountID string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
|
||||
if len(loadPoliciesFromFile) > 0 {
|
||||
return getter.NewLoadPolicy(loadPoliciesFromFile)
|
||||
}
|
||||
if accountID != "" && frameworkScope {
|
||||
g := getter.GetArmoAPIConnector() // download policy from ARMO backend
|
||||
g.SetCustomerGUID(accountID)
|
||||
return g
|
||||
}
|
||||
if downloadReleasedPolicy == nil {
|
||||
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy()
|
||||
}
|
||||
return getDownloadReleasedPolicy(downloadReleasedPolicy)
|
||||
|
||||
}
|
||||
|
||||
// func setGetArmoAPIConnector(scanInfo *cautils.ScanInfo, customerGUID string) {
|
||||
// g := getter.GetArmoAPIConnector() // download policy from ARMO backend
|
||||
// g.SetCustomerGUID(customerGUID)
|
||||
// scanInfo.PolicyGetter = g
|
||||
// if scanInfo.ScanAll {
|
||||
// frameworks, err := g.ListCustomFrameworks(customerGUID)
|
||||
// if err != nil {
|
||||
// glog.Error("failed to get custom frameworks") // handle error
|
||||
// return
|
||||
// }
|
||||
// scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
|
||||
// }
|
||||
// }
|
||||
|
||||
// setConfigInputsGetter sets the config input getter - local file/github release/ArmoAPI
|
||||
func getConfigInputsGetter(ControlsInputs string, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IControlsInputsGetter {
|
||||
if len(ControlsInputs) > 0 {
|
||||
return getter.NewLoadPolicy([]string{ControlsInputs})
|
||||
}
|
||||
if accountID != "" {
|
||||
g := getter.GetArmoAPIConnector() // download config from ARMO backend
|
||||
g.SetCustomerGUID(accountID)
|
||||
return g
|
||||
}
|
||||
if downloadReleasedPolicy == nil {
|
||||
downloadReleasedPolicy = getter.NewDownloadReleasedPolicy()
|
||||
}
|
||||
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull config inputs, fallback to BE
|
||||
cautils.WarningDisplay(os.Stderr, "Warning: failed to get config inputs from github release, this may affect the scanning results\n")
|
||||
}
|
||||
return downloadReleasedPolicy
|
||||
}
|
||||
|
||||
func getDownloadReleasedPolicy(downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
|
||||
if err := downloadReleasedPolicy.SetRegoObjects(); err != nil { // if failed to pull policy, fallback to cache
|
||||
cautils.WarningDisplay(os.Stderr, "Warning: failed to get policies from github release, loading policies from cache\n")
|
||||
return getter.NewLoadPolicy(getDefaultFrameworksPaths())
|
||||
} else {
|
||||
return downloadReleasedPolicy
|
||||
}
|
||||
}
|
||||
|
||||
func getDefaultFrameworksPaths() []string {
|
||||
fwPaths := []string{}
|
||||
for i := range getter.NativeFrameworks {
|
||||
fwPaths = append(fwPaths, getter.GetDefaultPath(getter.NativeFrameworks[i]))
|
||||
}
|
||||
return fwPaths
|
||||
}
|
||||
|
||||
func listFrameworksNames(policyGetter getter.IPolicyGetter) []string {
|
||||
fw, err := policyGetter.ListFrameworks()
|
||||
if err != nil {
|
||||
fw = getDefaultFrameworksPaths()
|
||||
}
|
||||
return fw
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var downloadInfo cautils.DownloadInfo
|
||||
|
||||
var downloadCmd = &cobra.Command{
|
||||
Use: fmt.Sprintf("Download framework <framework-name> [flags]\nSupported frameworks: %s", validFrameworks),
|
||||
Short: "Download framework controls",
|
||||
Long: ``,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("requires two arguments : framework <framework-name>")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
downloadInfo.FrameworkName = args[1]
|
||||
g := getter.NewDownloadReleasedPolicy()
|
||||
if downloadInfo.Path == "" {
|
||||
downloadInfo.Path = getter.GetDefaultPath(downloadInfo.FrameworkName + ".json")
|
||||
}
|
||||
frameworks, err := g.GetFramework(downloadInfo.FrameworkName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = getter.SaveFrameworkInFile(frameworks, downloadInfo.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(downloadCmd)
|
||||
downloadInfo = cautils.DownloadInfo{}
|
||||
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If specified, will store save to `~/.kubescape/<framework name>.json`")
|
||||
}
|
||||
203
cmd/framework.go
203
cmd/framework.go
@@ -1,203 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/kubescape/cautils/getter"
|
||||
"github.com/armosec/kubescape/opaprocessor"
|
||||
"github.com/armosec/kubescape/policyhandler"
|
||||
"github.com/armosec/kubescape/resultshandling"
|
||||
"github.com/armosec/kubescape/resultshandling/printer"
|
||||
"github.com/armosec/kubescape/resultshandling/reporter"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var scanInfo cautils.ScanInfo
|
||||
var supportedFrameworks = []string{"nsa", "mitre"}
|
||||
var validFrameworks = strings.Join(supportedFrameworks, ", ")
|
||||
|
||||
type CLIHandler struct {
|
||||
policyHandler *policyhandler.PolicyHandler
|
||||
scanInfo *cautils.ScanInfo
|
||||
}
|
||||
|
||||
var frameworkCmd = &cobra.Command{
|
||||
|
||||
Use: fmt.Sprintf("framework <framework name> [`<glob patter>`/`-`] [flags]\nSupported frameworks: %s", validFrameworks),
|
||||
Short: fmt.Sprintf("The framework you wish to use. Supported frameworks: %s", strings.Join(supportedFrameworks, ", ")),
|
||||
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
|
||||
ValidArgs: supportedFrameworks,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 && !(cmd.Flags().Lookup("use-from").Changed) {
|
||||
return fmt.Errorf("requires at least one argument")
|
||||
} else if len(args) > 0 {
|
||||
if !isValidFramework(strings.ToLower(args[0])) {
|
||||
return fmt.Errorf(fmt.Sprintf("supported frameworks: %s", strings.Join(supportedFrameworks, ", ")))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
scanInfo.PolicyIdentifier = reporthandling.PolicyIdentifier{}
|
||||
scanInfo.PolicyIdentifier.Kind = reporthandling.KindFramework
|
||||
|
||||
if !(cmd.Flags().Lookup("use-from").Changed) {
|
||||
scanInfo.PolicyIdentifier.Name = strings.ToLower(args[0])
|
||||
}
|
||||
if len(args) > 0 {
|
||||
if len(args[1:]) == 0 || args[1] != "-" {
|
||||
scanInfo.InputPatterns = args[1:]
|
||||
} else { // store stout to file
|
||||
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
|
||||
return err
|
||||
}
|
||||
scanInfo.InputPatterns = []string{tempFile.Name()}
|
||||
}
|
||||
}
|
||||
scanInfo.Init()
|
||||
cautils.SetSilentMode(scanInfo.Silent)
|
||||
err := CliSetup()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func isValidFramework(framework string) bool {
|
||||
return cautils.StringInSlice(supportedFrameworks, framework) != cautils.ValueNotFound
|
||||
}
|
||||
|
||||
func init() {
|
||||
scanCmd.AddCommand(frameworkCmd)
|
||||
scanInfo = cautils.ScanInfo{}
|
||||
frameworkCmd.Flags().StringVar(&scanInfo.UseFrom, "use-from", "", "Load local framework object from specified path. If not used will download latest")
|
||||
frameworkCmd.Flags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local framework object from default path. If not used will download latest")
|
||||
frameworkCmd.Flags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from Armo management portal")
|
||||
frameworkCmd.Flags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system, kube-public")
|
||||
frameworkCmd.Flags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer"/"json"/"junit"`)
|
||||
frameworkCmd.Flags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
|
||||
frameworkCmd.Flags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
|
||||
frameworkCmd.Flags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 0, "Failure threshold is the percent bellow which the command fails and returns exit code 1")
|
||||
frameworkCmd.Flags().BoolVarP(&scanInfo.DoNotSendResults, "results-locally", "", false, "Deprecated. Please use `--keep-local` instead")
|
||||
frameworkCmd.Flags().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")
|
||||
frameworkCmd.Flags().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")
|
||||
frameworkCmd.Flags().StringVarP(&scanInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
|
||||
|
||||
}
|
||||
|
||||
func CliSetup() error {
|
||||
flag.Parse()
|
||||
flagValidation()
|
||||
|
||||
var k8s *k8sinterface.KubernetesApi
|
||||
var clusterConfig cautils.IClusterConfig
|
||||
if !scanInfo.ScanRunningCluster() {
|
||||
k8sinterface.ConnectedToCluster = false
|
||||
clusterConfig = cautils.NewEmptyConfig()
|
||||
} else {
|
||||
k8s = k8sinterface.NewKubernetesApi()
|
||||
// setup cluster config
|
||||
clusterConfig = cautils.ClusterConfigSetup(&scanInfo, k8s, getter.NewArmoAPI())
|
||||
}
|
||||
|
||||
processNotification := make(chan *cautils.OPASessionObj)
|
||||
reportResults := make(chan *cautils.OPASessionObj)
|
||||
|
||||
// policy handler setup
|
||||
policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s)
|
||||
|
||||
if err := clusterConfig.SetCustomerGUID(scanInfo.Account); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
cautils.CustomerGUID = clusterConfig.GetCustomerGUID()
|
||||
cautils.ClusterName = k8sinterface.GetClusterName()
|
||||
|
||||
// cli handler setup
|
||||
go func() {
|
||||
cli := NewCLIHandler(policyHandler)
|
||||
if err := cli.Scan(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// processor setup - rego run
|
||||
go func() {
|
||||
opaprocessorObj := opaprocessor.NewOPAProcessorHandler(&processNotification, &reportResults)
|
||||
opaprocessorObj.ProcessRulesListenner()
|
||||
}()
|
||||
|
||||
resultsHandling := resultshandling.NewResultsHandler(&reportResults, reporter.NewReportEventReceiver(), printer.NewPrinter(scanInfo.Format, scanInfo.Output))
|
||||
score := resultsHandling.HandleResults()
|
||||
|
||||
// print report url
|
||||
clusterConfig.GenerateURL()
|
||||
|
||||
adjustedFailThreshold := float32(scanInfo.FailThreshold) / 100
|
||||
if score < adjustedFailThreshold {
|
||||
return fmt.Errorf("Scan score is bellow threshold")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewCLIHandler(policyHandler *policyhandler.PolicyHandler) *CLIHandler {
|
||||
return &CLIHandler{
|
||||
scanInfo: &scanInfo,
|
||||
policyHandler: policyHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (clihandler *CLIHandler) Scan() error {
|
||||
cautils.ScanStartDisplay()
|
||||
policyNotification := &reporthandling.PolicyNotification{
|
||||
NotificationType: reporthandling.TypeExecPostureScan,
|
||||
Rules: []reporthandling.PolicyIdentifier{
|
||||
clihandler.scanInfo.PolicyIdentifier,
|
||||
},
|
||||
Designators: armotypes.PortalDesignator{},
|
||||
}
|
||||
switch policyNotification.NotificationType {
|
||||
case reporthandling.TypeExecPostureScan:
|
||||
//
|
||||
if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, clihandler.scanInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func flagValidation() {
|
||||
if scanInfo.DoNotSendResults {
|
||||
fmt.Println("Deprecated. Please use `--keep-local` instead")
|
||||
}
|
||||
|
||||
if scanInfo.Submit && scanInfo.Local {
|
||||
fmt.Println("You can use `keep-local` or `submit`, but not both")
|
||||
os.Exit(1)
|
||||
}
|
||||
if 100 < scanInfo.FailThreshold {
|
||||
fmt.Println("bad argument: out of range threshold")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
25
cmd/root.go
25
cmd/root.go
@@ -1,25 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "kubescape",
|
||||
Short: "Kubescape is a tool for testing Kubernetes security posture",
|
||||
Long: `Kubescape is a tool for testing Kubernetes security posture based on NSA specifications.`,
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
rootCmd.Execute()
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
}
|
||||
18
cmd/scan.go
18
cmd/scan.go
@@ -1,18 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// scanCmd represents the scan command
|
||||
var scanCmd = &cobra.Command{
|
||||
Use: "scan",
|
||||
Short: "Scan the current running cluster or yaml files",
|
||||
Long: `The action you want to perform`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(scanCmd)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var BuildNumber string
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Get current version",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fmt.Println("Your current version is: " + BuildNumber)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func GetLatestVersion() (string, error) {
|
||||
latestVersion := "https://api.github.com/repos/armosec/kubescape/releases/latest"
|
||||
resp, err := http.Get(latestVersion)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get latest releases from '%s', reason: %s", latestVersion, err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || 301 < resp.StatusCode {
|
||||
return "", fmt.Errorf("failed to download file, status code: %s", resp.Status)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read response body from '%s', reason: %s", latestVersion, err.Error())
|
||||
}
|
||||
var data map[string]interface{}
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to unmarshal response body from '%s', reason: %s", latestVersion, err.Error())
|
||||
}
|
||||
return fmt.Sprintf("%v", data["tag_name"]), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 53 KiB |
176
docs/proposals/container-image-vulnerability-adaptor.md
Normal file
176
docs/proposals/container-image-vulnerability-adaptor.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Container image vulnerability adaptor interface proposal
|
||||
|
||||
## Rationale
|
||||
|
||||
source #287
|
||||
|
||||
### Big picture
|
||||
|
||||
* Kubescape team is planning to create controls which take into account image vulnerabilities, example: looking for public internet facing workloads with critical vulnerabilities. These are seriously effecting the security health of a cluster and therefore we think it is important to cover it. We think that most container registries are/will support image scanning like Harbor and therefore the ability to get information from them is important.
|
||||
* There are information in the image repository which is important for existing controls as well. They are incomplete without it, example see this issue: Non-root containers check is broken #19 . These are not necessarily image vulnerability related. Can be information in the image manifest (like the issue before), but it can be the image BOM related.
|
||||
|
||||
### Relation to this proposal
|
||||
|
||||
There are multiple changes and design decisions needs to be made before Kubescape will support the before outlined controls. However, a focal point the whole picutre is the ability to access vulnerabilty databases of container images. We anticiapte that most container image repositories will support image vulnerabilty scanning, some major players are already do. Since there is no a single API available which all of these data sources support it is important to create an adaption layer within Kubescape so different datasources can serve Kubescape's goals.
|
||||
|
||||
## High level design of Kubescape
|
||||
|
||||
### 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
|
||||
* Cloud Image Vulnerability adaption interface: the subject of this proposal, it gives a common interface for different registry/vulnerabilty vendors to adapt to.
|
||||
* CIV adaptors: specific implementation of the CIV interface, example Harbor adaption
|
||||
```
|
||||
-----------------------
|
||||
| Controls/Rules (rego) |
|
||||
-----------------------
|
||||
|
|
||||
-----------------------
|
||||
| OPA engine |
|
||||
-----------------------
|
||||
|
|
||||
-----------------------
|
||||
| Rules processor |
|
||||
-----------------------
|
||||
|
|
||||
-----------------------
|
||||
| Data sources |
|
||||
-----------------------
|
||||
|
|
||||
=======================
|
||||
| CIV adaption interface| <- Adding this layer in this proposal
|
||||
=======================
|
||||
|
|
||||
-----------------------
|
||||
| Specific CIV adaptors | <- will be implemented based on this proposal
|
||||
-----------------------
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Functionalities to cover
|
||||
|
||||
The interface needs to cover the following functionalities:
|
||||
|
||||
* Authentication against the information source (abstracted login)
|
||||
* Triggering image scan (if applicable, the source might store vulnerabilities for images but cannot scan alone)
|
||||
* Reading image scan status (with last scan date and etc.)
|
||||
* Getting vulnerability information for a given image
|
||||
* Getting image information
|
||||
* Image manifests
|
||||
* Image BOMs (bill of material)
|
||||
|
||||
## Go API proposal
|
||||
|
||||
```
|
||||
|
||||
/*type ContainerImageRegistryCredentials struct {
|
||||
map[string]string
|
||||
Password string
|
||||
Tag string
|
||||
Hash string
|
||||
}*/
|
||||
|
||||
type ContainerImageIdentifier struct {
|
||||
Registry string
|
||||
Repository string
|
||||
Tag string
|
||||
Hash string
|
||||
}
|
||||
|
||||
type ContainerImageScanStatus struct {
|
||||
ImageID ContainerImageIdentifier
|
||||
IsScanAvailable bool
|
||||
IsBomAvailable bool
|
||||
LastScanDate time.Time
|
||||
}
|
||||
|
||||
type ContainerImageVulnerability struct {
|
||||
ImageID ContainerImageIdentifier
|
||||
// TBD
|
||||
}
|
||||
|
||||
type ContainerImageInformation struct {
|
||||
ImageID ContainerImageIdentifier
|
||||
Bom []string
|
||||
ImageManifest Manifest // will use here Docker package definition
|
||||
}
|
||||
|
||||
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"}
|
||||
Login(registry string, credentials map[string]string) error
|
||||
|
||||
// For "help" purposes
|
||||
DescribeAdaptor() string
|
||||
|
||||
GetImagesScanStatus(imageIDs []ContainerImageIdentifier) ([]ContainerImageScanStatus, error)
|
||||
|
||||
GetImagesVulnerabilties(imageIDs []ContainerImageIdentifier) ([]ContainerImageVulnerability, error)
|
||||
|
||||
GetImagesInformation(imageIDs []ContainerImageIdentifier) ([]ContainerImageInformation, error)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
# Integration
|
||||
|
||||
# Input
|
||||
|
||||
The objects received from the interface will be converted to an Imetadata compatible objects as following
|
||||
|
||||
```
|
||||
{
|
||||
"apiVersion": "image.vulnscan.com/v1",
|
||||
"kind": "VulnScan",
|
||||
"metadata": {
|
||||
"name": "nginx:latest"
|
||||
},
|
||||
"data": {
|
||||
// returned by the adaptor API (structure like our backend gives for an image
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# Output
|
||||
|
||||
The rego results will be a combination of the k8s artifact and the list of relevant CVEs for the control
|
||||
|
||||
```
|
||||
{
|
||||
"apiVersion": "result.vulnscan.com/v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "nginx"
|
||||
},
|
||||
"relatedObjects": [
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "nginx"
|
||||
},
|
||||
"spec": {
|
||||
// podSpec
|
||||
},
|
||||
},
|
||||
{
|
||||
"apiVersion": "container.vulnscan.com/v1",
|
||||
"kind": "VulnScan",
|
||||
"metadata": {
|
||||
"name": "nginx:latest",
|
||||
},
|
||||
"data": {
|
||||
|
||||
// returned by the adaptor API (structure like our backend gives for an image
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
23
docs/roadmap.md
Normal file
23
docs/roadmap.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Kubescape project roadmap
|
||||
|
||||
|
||||
## Proposals
|
||||
* [Container registry integration](/docs/proposals/container-image-vulnerability-adaptor.md)
|
||||
|
||||
## Planed features
|
||||
* Image vulnerablity scanning based controls
|
||||
* Assited remidiation (telling where/what to fix)
|
||||
* Git integration for pull requests
|
||||
* Integration with container registries
|
||||
* Custom controls and regos
|
||||
* API server configuration validation
|
||||
* Kubelet configuration validation
|
||||
|
||||
## Completed features
|
||||
* Integration with Prometheus
|
||||
* Confiugration of controls (customizing rules for a given environment)
|
||||
* Installation in the cluster for continous monitoring
|
||||
* Host scanner
|
||||
* Cloud vendor API integration
|
||||
* Custom exceptions
|
||||
* Custom frameworks
|
||||
@@ -13,7 +13,7 @@ kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
| --- | --- | --- | --- |
|
||||
| `-e`/`--exclude-namespaces` | Scan all namespaces | Namespaces to exclude from scanning. Recommended to exclude `kube-system` and `kube-public` namespaces |
|
||||
| `-s`/`--silent` | Display progress messages | Silent progress messages |
|
||||
| `-t`/`--fail-threshold` | `0` (do not fail) | fail command (return exit code 1) if result bellow threshold| `0` -> `100` |
|
||||
| `-t`/`--fail-threshold` | `0` (do not fail) | fail command (return exit code 1) if result is below threshold| `0` -> `100` |
|
||||
| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit` |
|
||||
| `-o`/`--output` | print to stdout | Save scan result in file |
|
||||
| `--use-from` | | Load local framework object from specified path. If not used will download latest |
|
||||
@@ -25,7 +25,7 @@ kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
|
||||
### Examples
|
||||
|
||||
* Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework
|
||||
* 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 --exclude-namespaces kube-system,kube-public
|
||||
```
|
||||
@@ -51,7 +51,7 @@ kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --form
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format junit --output results.xml
|
||||
```
|
||||
|
||||
* Scan with exceptions, objects with exceptions will be presented as `warning` and not `fail` <img src="docs/new-feature.svg">
|
||||
* Scan with exceptions, objects with exceptions will be presented as `warning` and not `fail`
|
||||
```
|
||||
kubescape scan framework nsa --exceptions examples/exceptions.json
|
||||
```
|
||||
@@ -85,5 +85,3 @@ kubescape scan framework nsa --use-from nsa.json
|
||||
```
|
||||
|
||||
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 themselves more robust and complete as Kubernetes develops.
|
||||
|
||||
|
||||
|
||||
BIN
docs/summary.png
BIN
docs/summary.png
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 60 KiB |
68
examples/cloud-vendor-integration/aws.sh
Executable file
68
examples/cloud-vendor-integration/aws.sh
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
|
||||
# AWS
|
||||
# Attach the Kubescape service account to an AWS IAM role with the described cluster permission
|
||||
|
||||
# Prerequisites:
|
||||
# eksctl, awscli v2
|
||||
|
||||
# Set environment variables
|
||||
echo 'Set environment variables'
|
||||
export kubescape_namespace=armo-system
|
||||
export kubescape_serviceaccount=armo-kubescape-service-account
|
||||
|
||||
# Get current context
|
||||
echo 'Get current context'
|
||||
export context=$(kubectl config current-context)
|
||||
|
||||
# Get cluster arn
|
||||
echo 'Get cluster arn'
|
||||
export cluster_arn=$(kubectl config view -o jsonpath="{.contexts[?(@.name == \"$context\")].context.cluster}")
|
||||
|
||||
# Get cluster name
|
||||
echo 'Get cluster name'
|
||||
export cluster_name=$(echo "$cluster_arn" | awk -F'/' '{print $NF}')
|
||||
|
||||
# Get cluster region
|
||||
echo 'Get cluster region'
|
||||
export cluster_region=$(echo "$cluster_arn" | awk -F':' '{print $4}')
|
||||
|
||||
# First step, Create IAM OIDC provider for the cluster (Not required if the third step runs as is):
|
||||
echo 'Create IAM OIDC provider for the cluster'
|
||||
eksctl utils associate-iam-oidc-provider --cluster $cluster_name --approve
|
||||
|
||||
# Second step, Create a policy and service account role:
|
||||
# Create a kubescape policy
|
||||
echo 'Create a kubescape policy'
|
||||
export kubescape_policy_arn=$(aws iam create-policy \
|
||||
--output yaml \
|
||||
--query 'Policy.Arn' \
|
||||
--policy-name kubescape \
|
||||
--policy-document \
|
||||
"$(cat <<EOF
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "eks:DescribeCluster",
|
||||
"Resource": "$cluster_arn"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
)")
|
||||
|
||||
# Create Kubernetes Kubescape service account, and AWS IAM attachment role
|
||||
echo 'Create Kubernetes Kubescape service account, and AWS IAM attachment role'
|
||||
eksctl create iamserviceaccount \
|
||||
--name $kubescape_serviceaccount \
|
||||
--namespace $kubescape_namespace \
|
||||
--cluster $cluster_name \
|
||||
--attach-policy-arn $kubescape_policy_arn \
|
||||
--approve \
|
||||
--override-existing-serviceaccounts
|
||||
|
||||
# Install/Upgrade Kubescape chart
|
||||
echo 'Install/Upgrade Kubescape chart'
|
||||
helm upgrade --install armo armo-components/ -n armo-system --create-namespace --set clusterName=$cluster_name --set cloud_provider_engine=eks --set createKubescapeServiceAccount=false --set cloudRegion=$cluster_region
|
||||
49
examples/cloud-vendor-integration/gcp.sh
Executable file
49
examples/cloud-vendor-integration/gcp.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
|
||||
# GCP
|
||||
# Attach the Kubescape service account to a GCP service account with the get cluster permission
|
||||
|
||||
# Prerequisites:
|
||||
# gcloud
|
||||
# Workload Identity enabled on the cluster.
|
||||
# Node pool with --workload-metadata=GKE_METADATA where the pod can run.
|
||||
# CLUSTER_NAME and CLUSTER_REGION environment variables.
|
||||
|
||||
[ -z $CLUSTER_NAME ] && >&2 echo "Please set the CLUSTER_NAME environment variable" && exit 1
|
||||
[ -z $CLUSTER_REGION ] && >&2 echo "Please set the CLUSTER_REGION environment variable" && exit 1
|
||||
|
||||
# Create GCP service account
|
||||
gcloud iam service-accounts create kubescape --display-name=kubescape
|
||||
|
||||
# Set environment variables
|
||||
echo 'Set environment variables'
|
||||
export kubescape_namespace=armo-system
|
||||
export kubescape_serviceaccount=armo-kubescape-service-account
|
||||
|
||||
# Get current GCP project
|
||||
echo 'Get current GCP project'
|
||||
export gcp_project=$(gcloud config get-value project)
|
||||
|
||||
sleep 5
|
||||
# Get service account email
|
||||
echo 'Get service account email'
|
||||
export gcp_service_account=$(gcloud iam service-accounts list --filter="email ~ kubescape@" --format="value(email)")
|
||||
|
||||
# Create custome cluster.get role
|
||||
echo 'Create custome cluster.get role'
|
||||
export custom_role_name=$(gcloud iam roles create kubescape --project=$gcp_project --title='Armo kubernetes' --description='Allow clusters.get to Kubernetes armo service account' --permissions=container.clusters.get --stage=GA --format='value(name)')
|
||||
|
||||
# Attach policies to the service account
|
||||
echo 'Attach policies to the service account'
|
||||
gcloud --quiet projects add-iam-policy-binding $gcp_project --member serviceAccount:$gcp_service_account --role $custom_role_name >/dev/null
|
||||
gcloud --quiet projects add-iam-policy-binding $gcp_project --member serviceAccount:$gcp_service_account --role roles/storage.objectViewer >/dev/null
|
||||
|
||||
# If there are missing permissions, use this role instead
|
||||
# gcloud --quiet projects add-iam-policy-binding $gcp_project --member serviceAccount:$gcp_service_account --role roles/container.clusterViewer
|
||||
|
||||
# Bind the GCP kubescape service account to kubescape kubernetes service account
|
||||
gcloud iam service-accounts add-iam-policy-binding $gcp_service_account --role roles/iam.workloadIdentityUser --member "serviceAccount:${gcp_project}.svc.id.goog[${kubescape_namespace}/${kubescape_serviceaccount}]"
|
||||
|
||||
# Install/Upgrade Kubescape chart
|
||||
echo 'Install/Upgrade Kubescape chart'
|
||||
helm upgrade --install armo armo-components/ -n armo-system --create-namespace --set cloud_provider_engine=gke --set gke_service_account=$gcp_service_account --set cloudRegion=$CLUSTER_REGION --set clusterName=$CLUSTER_NAME --set gkeProject=$gcp_project
|
||||
85
examples/cronJob-support/README.md
Normal file
85
examples/cronJob-support/README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Periodically Kubescape Scanning
|
||||
|
||||
You can scan your cluster periodically by adding a `CronJob` that will repeatedly trigger kubescape
|
||||
|
||||
* Setup [scanning & submitting](#scanning-and-submitting)
|
||||
* Setup [scanning without submitting](#scanning-without-submitting)
|
||||
|
||||
## Scanning And Submitting
|
||||
|
||||
If you wish to periodically scan and submit the result to the [Kubescape SaaS version](https://portal.armo.cloud/) where you can benefit the features the SaaS version provides, please follow this instructions ->
|
||||
|
||||
1. Apply kubescape namespace
|
||||
```
|
||||
kubectl apply ks-namespace.yaml
|
||||
```
|
||||
|
||||
2. Apply serviceAccount and roles
|
||||
```
|
||||
kubectl apply ks-serviceAccount.yaml
|
||||
```
|
||||
|
||||
3. Setup and apply configMap
|
||||
|
||||
Before you apply the configMap you need to set the account ID and cluster name in the `ks-configMap.yaml` file.
|
||||
|
||||
* Set cluster name:
|
||||
Run `kubectl config current-context` and set the result in the `data.clusterName` field
|
||||
* Set account ID:
|
||||
1. Navigate to the [Kubescape SaaS version](https://portal.armo.cloud/) and login/sign up for free
|
||||
2. Click the `Add Cluster` button on the top right of the page
|
||||
</br>
|
||||
<img src="screenshots/add-cluster.png" alt="add-cluster">
|
||||
3. Copy the value of `--account` and set it in the `data.customerGUID` field
|
||||
</br>
|
||||
<img src="screenshots/account.png" alt="account">
|
||||
|
||||
Make sure the configMap looks as following;
|
||||
```
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: kubescape
|
||||
labels:
|
||||
app: kubescape
|
||||
namespace: kubescape
|
||||
data:
|
||||
config.json: |
|
||||
{
|
||||
"customerGUID": "XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
||||
"clusterName": "my-awesome-cluster-name"
|
||||
}
|
||||
```
|
||||
|
||||
Finally, apply the configMap
|
||||
```
|
||||
kubectl apply ks-configMap.yaml
|
||||
```
|
||||
|
||||
4. Apply CronJob
|
||||
|
||||
Before you apply the cronJob, make sure the scanning frequency suites your needs
|
||||
```
|
||||
kubectl apply ks-cronJob-submit.yaml
|
||||
```
|
||||
|
||||
## Scanning Without Submitting
|
||||
|
||||
If you wish to periodically scan but not submit the scan results, follow this instructions ->
|
||||
|
||||
1. Apply kubescape namespace
|
||||
```
|
||||
kubectl apply ks-namespace.yaml
|
||||
```
|
||||
|
||||
2. Apply serviceAccount and roles
|
||||
```
|
||||
kubectl apply ks-serviceAccount.yaml
|
||||
```
|
||||
|
||||
3. Apply CronJob
|
||||
|
||||
Before you apply the cronJob, make sure the scanning frequency suites your needs
|
||||
```
|
||||
kubectl apply ks-cronJob-non-submit.yaml
|
||||
```
|
||||
14
examples/cronJob-support/ks-configMap.yaml
Normal file
14
examples/cronJob-support/ks-configMap.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# ------------------- Kubescape User/Customer ID ------------------- #
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: kubescape
|
||||
labels:
|
||||
app: kubescape
|
||||
namespace: kubescape
|
||||
data:
|
||||
config.json: |
|
||||
{
|
||||
"customerGUID": "<ID>",
|
||||
"clusterName": "<cluster name>"
|
||||
}
|
||||
32
examples/cronJob-support/ks-cronJob-non-submit.yaml
Normal file
32
examples/cronJob-support/ks-cronJob-non-submit.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: kubescape
|
||||
labels:
|
||||
app: kubescape
|
||||
namespace: kubescape
|
||||
spec:
|
||||
# ┌────────────────── timezone (optional)
|
||||
# | ┌───────────── minute (0 - 59)
|
||||
# | │ ┌───────────── hour (0 - 23)
|
||||
# | │ │ ┌───────────── day of the month (1 - 31)
|
||||
# | │ │ │ ┌───────────── month (1 - 12)
|
||||
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
|
||||
# | │ │ │ │ │ 7 is also Sunday on some systems)
|
||||
# | │ │ │ │ │
|
||||
# | │ │ │ │ │
|
||||
# CRON_TZ=UTC * * * * *
|
||||
schedule: "0 0 1 * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: kubescape
|
||||
image: quay.io/armosec/kubescape:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/bin/sh","-c"]
|
||||
args:
|
||||
- kubescape scan framework nsa
|
||||
restartPolicy: OnFailure
|
||||
serviceAccountName: kubescape-discovery
|
||||
40
examples/cronJob-support/ks-cronJob-submit.yaml
Normal file
40
examples/cronJob-support/ks-cronJob-submit.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: kubescape
|
||||
labels:
|
||||
app: kubescape
|
||||
namespace: kubescape
|
||||
spec:
|
||||
# ┌────────────────── timezone (optional)
|
||||
# | ┌───────────── minute (0 - 59)
|
||||
# | │ ┌───────────── hour (0 - 23)
|
||||
# | │ │ ┌───────────── day of the month (1 - 31)
|
||||
# | │ │ │ ┌───────────── month (1 - 12)
|
||||
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
|
||||
# | │ │ │ │ │ 7 is also Sunday on some systems)
|
||||
# | │ │ │ │ │
|
||||
# | │ │ │ │ │
|
||||
# CRON_TZ=UTC * * * * *
|
||||
schedule: "0 0 1 * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: kubescape
|
||||
image: quay.io/armosec/kubescape:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/bin/sh","-c"]
|
||||
args:
|
||||
- kubescape scan framework nsa --submit
|
||||
volumeMounts:
|
||||
- name: kubescape-config-volume
|
||||
mountPath: /root/.kubescape/config.json
|
||||
subPath: config.json
|
||||
restartPolicy: OnFailure
|
||||
serviceAccountName: kubescape-discovery
|
||||
volumes:
|
||||
- name: kubescape-config-volume
|
||||
configMap:
|
||||
name: kubescape
|
||||
7
examples/cronJob-support/ks-namespace.yaml
Normal file
7
examples/cronJob-support/ks-namespace.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# ------------------- Kubescape User/Customer ID ------------------- #
|
||||
kind: Namespace
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: kubescape
|
||||
labels:
|
||||
app: kubescape
|
||||
61
examples/cronJob-support/ks-serviceAccount.yaml
Normal file
61
examples/cronJob-support/ks-serviceAccount.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
# ------------------- Kubescape Service Account ------------------- #
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: kubescape
|
||||
name: kubescape-discovery
|
||||
namespace: kubescape
|
||||
|
||||
---
|
||||
# ------------------- Kubescape Role & Role Binding ------------------- #
|
||||
kind: Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubescape-discovery-role
|
||||
namespace: kubescape
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs: ["get", "list", "describe"]
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: kubescape-discovery-binding
|
||||
namespace: kubescape
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: kubescape-discovery-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubescape-discovery
|
||||
|
||||
---
|
||||
# ------------------- Kubescape Cluster Role & Cluster Role Binding ------------------- #
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubescape-discovery-clusterroles
|
||||
# "namespace" omitted since ClusterRoles are not namespaced
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs: ["get", "list", "describe"]
|
||||
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubescape-discovery-role-binding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: kubescape-discovery-clusterroles
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubescape-discovery
|
||||
namespace: kubescape
|
||||
BIN
examples/cronJob-support/screenshots/account.png
Normal file
BIN
examples/cronJob-support/screenshots/account.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
BIN
examples/cronJob-support/screenshots/add-cluster.png
Normal file
BIN
examples/cronJob-support/screenshots/add-cluster.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
130
examples/cronjob/ks-cronjob.yaml
Normal file
130
examples/cronjob/ks-cronjob.yaml
Normal file
@@ -0,0 +1,130 @@
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
#
|
||||
# This file is DEPRECATE, please navigate to the official docs ->
|
||||
# https://github.com/armosec/kubescape/tree/master/examples/cronJob-support/README.md
|
||||
#
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
---
|
||||
# ------------------- Kubescape Service Account ------------------- #
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: kubescape
|
||||
name: kubescape-discovery
|
||||
namespace: kubescape
|
||||
|
||||
---
|
||||
# ------------------- Kubescape Role & Role Binding ------------------- #
|
||||
kind: Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubescape-discovery-role
|
||||
namespace: kubescape
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs: ["get", "list", "describe"]
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: kubescape-discovery-binding
|
||||
namespace: kubescape
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: kubescape-discovery-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubescape-discovery
|
||||
|
||||
---
|
||||
# ------------------- Kubescape Cluster Role & Cluster Role Binding ------------------- #
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubescape-discovery-clusterroles
|
||||
# "namespace" omitted since ClusterRoles are not namespaced
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs: ["get", "list", "describe"]
|
||||
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubescape-discovery-role-binding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: kubescape-discovery-clusterroles
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubescape-discovery
|
||||
namespace: kubescape
|
||||
|
||||
---
|
||||
# ------------------- Kubescape User/Customer GUID ------------------- #
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: kubescape-configmap
|
||||
labels:
|
||||
app: kubescape
|
||||
namespace: kubescape
|
||||
data:
|
||||
config.json: |
|
||||
{
|
||||
"customerGUID": <MyGUID>,
|
||||
"clusterName": <MyK8sClusterName>
|
||||
}
|
||||
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: kubescape
|
||||
labels:
|
||||
app: kubescape
|
||||
namespace: kubescape
|
||||
spec:
|
||||
# ┌────────────────── timezone (optional)
|
||||
# | ┌───────────── minute (0 - 59)
|
||||
# | │ ┌───────────── hour (0 - 23)
|
||||
# | │ │ ┌───────────── day of the month (1 - 31)
|
||||
# | │ │ │ ┌───────────── month (1 - 12)
|
||||
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
|
||||
# | │ │ │ │ │ 7 is also Sunday on some systems)
|
||||
# | │ │ │ │ │
|
||||
# | │ │ │ │ │
|
||||
# CRON_TZ=UTC * * * * *
|
||||
schedule: "0 0 1 * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: kubescape
|
||||
image: quay.io/armosec/kubescape:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/bin/sh","-c"]
|
||||
args:
|
||||
- kubescape scan framework nsa --submit
|
||||
volumeMounts:
|
||||
- name: kubescape-config-volume
|
||||
mountPath: /root/.kubescape/config.json
|
||||
subPath: config.json
|
||||
restartPolicy: OnFailure
|
||||
serviceAccountName: kubescape-discovery
|
||||
volumes:
|
||||
- name: kubescape-config-volume
|
||||
configMap:
|
||||
name: kubescape-configmap
|
||||
|
||||
---
|
||||
|
||||
|
||||
179
examples/exceptions/README.md
Normal file
179
examples/exceptions/README.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Kubescape Exceptions
|
||||
|
||||
Kubescape Exceptions is the proper way of excluding failed resources from effecting the risk score.
|
||||
|
||||
e.g. When a `kube-system` resource fails and it is ok, simply add the resource to the exceptions configurations.
|
||||
|
||||
## Definitions
|
||||
|
||||
|
||||
* `name`- Exception name - unique name representing the exception
|
||||
* `policyType`- Do not change
|
||||
* `actions`- List of available actions. Currently alertOnly is supported
|
||||
* `resources`- List of resources to apply this exception on
|
||||
* `designatorType: Attributes`- An attribute-based declaration {key: value}
|
||||
Supported keys:
|
||||
* `name`: k8s resource name (case-sensitive, regex supported)
|
||||
* `kind`: k8s resource kind (case-sensitive, regex supported)
|
||||
* `namespace`: k8s resource namespace (case-sensitive, regex supported)
|
||||
* `cluster`: k8s cluster name (usually it is the `current-context`) (case-sensitive, regex supported)
|
||||
* resource labels as key value (case-sensitive, regex NOT supported)
|
||||
* `posturePolicies`- An attribute-based declaration {key: value}
|
||||
* `frameworkName` - Framework names can be find [here](https://github.com/armosec/regolibrary/tree/master/frameworks)
|
||||
* `controlName` - Control names can be find [here](https://github.com/armosec/regolibrary/tree/master/controls)
|
||||
* `controlID` - Not yet supported
|
||||
* `ruleName` - Rule names can be find [here](https://github.com/armosec/regolibrary/tree/master/rules)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
The `resources` list and `posturePolicies` list are design to be a combination of the resources and policies to exclude
|
||||
> You must declare at least one resource and one policy
|
||||
|
||||
e.g. If you wish to exclude all namespaces with the label `"environment": "dev"`, the resource list should look as following:
|
||||
```
|
||||
"resources": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"namespace": ".*",
|
||||
"environment": "dev"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
But if you wish to exclude all namespaces **OR** any resource with the label `"environment": "dev"`, the resource list should look as following:
|
||||
```
|
||||
"resources": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"namespace": ".*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"environment": "dev"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Same works with the `posturePolicies` list ->
|
||||
|
||||
e.g. If you wish to exclude the resources declared in the `resources` list that failed when scanning the `NSA` framework **AND** failed the `Allowed hostPath` control, the `posturePolicies` list should look as following:
|
||||
```
|
||||
"posturePolicies": [
|
||||
{
|
||||
"frameworkName": "NSA",
|
||||
"controlName": "Allowed hostPath"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
But if you wish to exclude the resources declared in the `resources` list that failed when scanning the `NSA` framework **OR** failed the `Allowed hostPath` control, the `posturePolicies` list should look as following:
|
||||
```
|
||||
"posturePolicies": [
|
||||
{
|
||||
"frameworkName": "NSA"
|
||||
},
|
||||
{
|
||||
"controlName": "Allowed hostPath"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Here are some examples demonstrating the different ways the exceptions file can be configured
|
||||
|
||||
|
||||
### Exclude control
|
||||
|
||||
Exclude the ["Allowed hostPath" control](https://github.com/armosec/regolibrary/blob/master/controls/allowedhostpath.json#L2) by declaring the control in the `"posturePolicies"` section.
|
||||
|
||||
The resources
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
"name": "exclude-allowed-hostPath-control",
|
||||
"policyType": "postureExceptionPolicy",
|
||||
"actions": [
|
||||
"alertOnly"
|
||||
],
|
||||
"resources": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"kind": ".*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"posturePolicies": [
|
||||
{
|
||||
"controlName": "Allowed hostPath"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Exclude deployments in the default namespace that failed the "Allowed hostPath" control
|
||||
```
|
||||
[
|
||||
{
|
||||
"name": "exclude-deployments-in-ns-default",
|
||||
"policyType": "postureExceptionPolicy",
|
||||
"actions": [
|
||||
"alertOnly"
|
||||
],
|
||||
"resources": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"namespace": "default",
|
||||
"kind": "Deployment"
|
||||
}
|
||||
}
|
||||
],
|
||||
"posturePolicies": [
|
||||
{
|
||||
"controlName": "Allowed hostPath"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Exclude resources with label "app=nginx" running in a minikube cluster that failed the "NSA" or "MITRE" framework
|
||||
```
|
||||
[
|
||||
{
|
||||
"name": "exclude-nginx-minikube",
|
||||
"policyType": "postureExceptionPolicy",
|
||||
"actions": [
|
||||
"alertOnly"
|
||||
],
|
||||
"resources": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"cluster": "minikube",
|
||||
"app": "nginx"
|
||||
}
|
||||
}
|
||||
],
|
||||
"posturePolicies": [
|
||||
{
|
||||
"frameworkName": "NSA"
|
||||
},
|
||||
{
|
||||
"frameworkName": "MITRE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
22
examples/exceptions/exclude-allowed-hostPath-control.json
Normal file
22
examples/exceptions/exclude-allowed-hostPath-control.json
Normal file
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"name": "exclude-allowed-hostPath-control",
|
||||
"policyType": "postureExceptionPolicy",
|
||||
"actions": [
|
||||
"alertOnly"
|
||||
],
|
||||
"resources": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"kind": ".*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"posturePolicies": [
|
||||
{
|
||||
"controlName": "Allowed hostPath"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
23
examples/exceptions/exclude-deployments-in-ns-default.json
Normal file
23
examples/exceptions/exclude-deployments-in-ns-default.json
Normal file
@@ -0,0 +1,23 @@
|
||||
[
|
||||
{
|
||||
"name": "exclude-deployments-in-ns-default",
|
||||
"policyType": "postureExceptionPolicy",
|
||||
"actions": [
|
||||
"alertOnly"
|
||||
],
|
||||
"resources": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"namespace": "default",
|
||||
"kind": "Deployment"
|
||||
}
|
||||
}
|
||||
],
|
||||
"posturePolicies": [
|
||||
{
|
||||
"controlName": "Allowed hostPath"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -28,6 +28,12 @@
|
||||
"posturePolicies": [
|
||||
{
|
||||
"frameworkName": "NSA"
|
||||
},
|
||||
{
|
||||
"frameworkName": "MITRE"
|
||||
},
|
||||
{
|
||||
"frameworkName": "ArmoBest"
|
||||
}
|
||||
]
|
||||
}
|
||||
26
examples/exceptions/exclude-nginx-in-minikube.json
Normal file
26
examples/exceptions/exclude-nginx-in-minikube.json
Normal file
@@ -0,0 +1,26 @@
|
||||
[
|
||||
{
|
||||
"name": "exclude-nginx-in-minikube",
|
||||
"policyType": "postureExceptionPolicy",
|
||||
"actions": [
|
||||
"alertOnly"
|
||||
],
|
||||
"resources": [
|
||||
{
|
||||
"designatorType": "Attributes",
|
||||
"attributes": {
|
||||
"cluster": "minikube",
|
||||
"app": "nginx"
|
||||
}
|
||||
}
|
||||
],
|
||||
"posturePolicies": [
|
||||
{
|
||||
"frameworkName": "NSA"
|
||||
},
|
||||
{
|
||||
"frameworkName": "MITRE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
29
examples/helm_chart/Chart.yaml
Normal file
29
examples/helm_chart/Chart.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
apiVersion: v2
|
||||
name: kubescape
|
||||
description:
|
||||
Kubescape is the first open-source tool for testing if Kubernetes is deployed securely according to multiple frameworks
|
||||
regulatory, customized company policies and DevSecOps best practices, such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo) and the [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) .
|
||||
Kubescape scans K8s clusters, YAML files, and HELM charts, and detect misconfigurations and software vulnerabilities at early stages of the CI/CD pipeline and provides a risk score instantly and risk trends over time.
|
||||
Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI and Github workflows.
|
||||
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 1.0.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "v1.0.128"
|
||||
27
examples/helm_chart/README.md
Normal file
27
examples/helm_chart/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# kubescape
|
||||
|
||||
  
|
||||
|
||||
Kubescape is the first open-source tool for testing if Kubernetes is deployed securely according to multiple frameworks regulatory, customized company policies and DevSecOps best practices, such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo) and the [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) . Kubescape scans K8s clusters, YAML files, and HELM charts, and detect misconfigurations and software vulnerabilities at early stages of the CI/CD pipeline and provides a risk score instantly and risk trends over time. Kubescape integrates natively with other DevOps tools, including Jenkins, CircleCI and Github workflows.
|
||||
|
||||
## Values
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| affinity | object | `{}` | |
|
||||
| configMap | object | `{"create":true,"params":{"clusterName":"<MyK8sClusterName>","customerGUID":"<MyGUID>,"}}` | ARMO customer information |
|
||||
| fullnameOverride | string | `""` | |
|
||||
| image | object | `{"imageName":"kubescape","pullPolicy":"IfNotPresent","repository":"quay.io/armosec","tag":"latest"}` | Image and version to deploy |
|
||||
| imagePullSecrets | list | `[]` | |
|
||||
| nameOverride | string | `""` | |
|
||||
| nodeSelector | object | `{}` | |
|
||||
| podAnnotations | object | `{}` | |
|
||||
| podSecurityContext | object | `{}` | |
|
||||
| resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"200m","memory":"256Mi"}}` | Default resources for running the service in cluster |
|
||||
| schedule | string | `"0 0 * * *"` | Frequency of running the scan |
|
||||
| securityContext | object | `{}` | |
|
||||
| serviceAccount | object | `{"annotations":{},"create":true,"name":"kubescape-discovery"}` | Service account that runs the scan and has permissions to view the cluster |
|
||||
| tolerations | list | `[]` | |
|
||||
|
||||
----------------------------------------------
|
||||
Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0)
|
||||
62
examples/helm_chart/templates/_helpers.tpl
Normal file
62
examples/helm_chart/templates/_helpers.tpl
Normal file
@@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "kubescape.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "kubescape.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "kubescape.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "kubescape.labels" -}}
|
||||
helm.sh/chart: {{ include "kubescape.chart" . }}
|
||||
{{ include "kubescape.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "kubescape.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "kubescape.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "kubescape.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "kubescape.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
11
examples/helm_chart/templates/clusterrole.yaml
Normal file
11
examples/helm_chart/templates/clusterrole.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "kubescape.fullname" . }}
|
||||
labels:
|
||||
{{- include "kubescape.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs: ["get", "list", "describe"]
|
||||
|
||||
16
examples/helm_chart/templates/clusterrolebinding.yaml
Normal file
16
examples/helm_chart/templates/clusterrolebinding.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ include "kubescape.fullname" . }}
|
||||
labels:
|
||||
{{- include "kubescape.labels" . | nindent 4 }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ include "kubescape.fullname" . }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "kubescape.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
|
||||
|
||||
14
examples/helm_chart/templates/configmap.yaml
Normal file
14
examples/helm_chart/templates/configmap.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
{{- if .Values.configMap.create -}}
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ include "kubescape.fullname" . }}-configmap
|
||||
labels:
|
||||
{{- include "kubescape.labels" . | nindent 4 }}
|
||||
data:
|
||||
config.json: |
|
||||
{
|
||||
"customerGUID": "{{ .Values.configMap.params.customerGUID }}",
|
||||
"clusterName": "{{ .Values.configMap.params.clusterName }}"
|
||||
}
|
||||
{{- end }}
|
||||
28
examples/helm_chart/templates/cronjob.yaml
Normal file
28
examples/helm_chart/templates/cronjob.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: {{ include "kubescape.fullname" . }}
|
||||
labels:
|
||||
{{- include "kubescape.labels" . | nindent 4 }}
|
||||
spec:
|
||||
schedule: "{{ .Values.schedule }}"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}/{{ .Values.image.imageName }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
command: ["/bin/sh", "-c"]
|
||||
args: ["kubescape scan framework nsa --submit"]
|
||||
volumeMounts:
|
||||
- name: kubescape-config-volume
|
||||
mountPath: /root/.kubescape/config.json
|
||||
subPath: config.json
|
||||
restartPolicy: OnFailure
|
||||
serviceAccountName: {{ include "kubescape.serviceAccountName" . }}
|
||||
volumes:
|
||||
- name: kubescape-config-volume
|
||||
configMap:
|
||||
name: {{ include "kubescape.fullname" . }}-configmap
|
||||
11
examples/helm_chart/templates/role.yaml
Normal file
11
examples/helm_chart/templates/role.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: {{ include "kubescape.fullname" . }}
|
||||
labels:
|
||||
{{- include "kubescape.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs: ["get", "list", "describe"]
|
||||
|
||||
16
examples/helm_chart/templates/rolebinding.yaml
Normal file
16
examples/helm_chart/templates/rolebinding.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: {{ include "kubescape.fullname" . }}
|
||||
labels:
|
||||
{{- include "kubescape.labels" . | nindent 4 }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ include "kubescape.fullname" . }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "kubescape.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
|
||||
|
||||
12
examples/helm_chart/templates/serviceaccount.yaml
Normal file
12
examples/helm_chart/templates/serviceaccount.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "kubescape.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "kubescape.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
74
examples/helm_chart/values.yaml
Normal file
74
examples/helm_chart/values.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
# Default values for kubescape.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
# -- Frequency of running the scan
|
||||
# ┌────────────── timezone (optional)
|
||||
# | ┌───────────── minute (0 - 59)
|
||||
# | │ ┌───────────── hour (0 - 23)
|
||||
# | │ │ ┌───────────── day of the month (1 - 31)
|
||||
# | │ │ │ ┌───────────── month (1 - 12)
|
||||
# | │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
|
||||
# | │ │ │ │ │ 7 is also Sunday on some systems)
|
||||
# | │ │ │ │ │
|
||||
# | │ │ │ │ │
|
||||
# UTC * * * * *
|
||||
schedule: "* * 1 * *"
|
||||
|
||||
# -- Image and version to deploy
|
||||
image:
|
||||
repository: quay.io/armosec
|
||||
imageName: kubescape
|
||||
pullPolicy: Always
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: latest
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
# -- Service account that runs the scan and has permissions to view the cluster
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: "kubescape-discovery"
|
||||
|
||||
# -- ARMO customer information
|
||||
configMap:
|
||||
create: false
|
||||
params:
|
||||
customerGUID: <MyGUID>
|
||||
clusterName: <MyK8sClusterName>
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
# -- Default resources for running the service in cluster
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
4026
examples/output_mocks/prometheus-verbose-flag.txt
Normal file
4026
examples/output_mocks/prometheus-verbose-flag.txt
Normal file
File diff suppressed because it is too large
Load Diff
1326
examples/output_mocks/prometheus.txt
Normal file
1326
examples/output_mocks/prometheus.txt
Normal file
File diff suppressed because it is too large
Load Diff
59
go.mod
59
go.mod
@@ -3,37 +3,28 @@ module github.com/armosec/kubescape
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go v1.41.1 // indirect
|
||||
github.com/briandowns/spinner v1.16.0
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.9+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/armosec/armoapi-go v0.0.41
|
||||
github.com/armosec/k8s-interface v0.0.56
|
||||
github.com/armosec/opa-utils v0.0.99
|
||||
github.com/armosec/rbac-utils v0.0.12
|
||||
github.com/armosec/utils-go v0.0.3
|
||||
github.com/briandowns/spinner v1.18.0
|
||||
github.com/enescakir/emoji v1.0.0
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/gofrs/uuid v4.1.0+incompatible
|
||||
github.com/golang/glog v1.0.0
|
||||
github.com/mattn/go-isatty v0.0.13
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/open-policy-agent/opa v0.33.1
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/spf13/cobra v1.2.1
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.uber.org/zap v1.19.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.22.2
|
||||
k8s.io/apimachinery v0.22.2
|
||||
k8s.io/client-go v0.22.2
|
||||
sigs.k8s.io/controller-runtime v0.10.2 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/armosec/armoapi-go v0.0.7
|
||||
github.com/armosec/k8s-interface v0.0.2
|
||||
github.com/armosec/opa-utils v0.0.6
|
||||
github.com/armosec/utils-go v0.0.3
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -45,48 +36,66 @@ require (
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
||||
github.com/armosec/armo-interfaces v0.0.3 // indirect
|
||||
github.com/armosec/utils-k8s-go v0.0.1 // indirect
|
||||
github.com/aws/aws-sdk-go v1.41.11 // indirect
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/docker v20.10.9+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-gota/gota v0.12.0 // indirect
|
||||
github.com/go-logr/logr v0.4.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pquerna/cachecontrol v0.1.0 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.19.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 // indirect
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
gonum.org/v1/gonum v0.9.1 // indirect
|
||||
google.golang.org/api v0.44.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
|
||||
google.golang.org/grpc v1.38.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
k8s.io/klog/v2 v2.9.0 // indirect
|
||||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect
|
||||
sigs.k8s.io/controller-runtime v0.10.2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
||||
|
||||
100
go.sum
100
go.sum
@@ -44,6 +44,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
@@ -70,6 +71,7 @@ github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8
|
||||
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -81,21 +83,32 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armosec/armo-interfaces v0.0.3 h1:kG4mJIPgWBJvQFDDy8JzdqX3ASbyl8t32IuJYqB31Pk=
|
||||
github.com/armosec/armo-interfaces v0.0.3/go.mod h1:7XYefhcBCFYoF5LflCZHWuUHu+JrSJbmzk0zoNv2WlU=
|
||||
github.com/armosec/armoapi-go v0.0.2/go.mod h1:vIK17yoKbJRQyZXWWLe3AqfqCRITxW8qmSkApyq5xFs=
|
||||
github.com/armosec/armoapi-go v0.0.7 h1:SN13+iYrIkxgatU+MwuWnSlhxP1G7rZP7dC8us2I7v0=
|
||||
github.com/armosec/armoapi-go v0.0.7/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
|
||||
github.com/armosec/k8s-interface v0.0.2 h1:Xw7HbQLNO9DN4NlD486VgXPwVpMFFxxwTlrVkcpsn5M=
|
||||
github.com/armosec/k8s-interface v0.0.2/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
|
||||
github.com/armosec/opa-utils v0.0.6 h1:TmCLuaNjahiSj1RD3uSGfibAYbWsx2gcwDhORnnmn7I=
|
||||
github.com/armosec/opa-utils v0.0.6/go.mod h1:93RAwZk1jhtXzT5+wqYENCvSx/bMyD3eTN277f4l2gs=
|
||||
github.com/armosec/armoapi-go v0.0.23/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
|
||||
github.com/armosec/armoapi-go v0.0.41 h1:iMkaCsME+zhE6vnCOMaqfqc0cp7pste8QFHojeGKfGg=
|
||||
github.com/armosec/armoapi-go v0.0.41/go.mod h1:exk1O3rK6V+X8SSyxc06lwb0j9ILQuKAoIdz9hs6Ndw=
|
||||
github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
|
||||
github.com/armosec/k8s-interface v0.0.37/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
|
||||
github.com/armosec/k8s-interface v0.0.50/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
|
||||
github.com/armosec/k8s-interface v0.0.56 h1:7dOgc3qZaI7ReLRZcJa2JZKk0rliyYi05l1vuHc6gcE=
|
||||
github.com/armosec/k8s-interface v0.0.56/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
|
||||
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
|
||||
github.com/armosec/opa-utils v0.0.99 h1:ZuoIPg6vbgO4J09xJZDO/yIRD59odwmK2Bm55uTvkU8=
|
||||
github.com/armosec/opa-utils v0.0.99/go.mod h1:BNTjeianyXlflJMz3bZM0GimBWqmzirUf1whWR6Os04=
|
||||
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
|
||||
github.com/armosec/rbac-utils v0.0.12 h1:uJpMGDyLAX129PrKHp6NPNB6lVRhE0OZIwV6ywzSDrs=
|
||||
github.com/armosec/rbac-utils v0.0.12/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=
|
||||
github.com/armosec/utils-go v0.0.2/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo=
|
||||
github.com/armosec/utils-go v0.0.3 h1:uyQI676yRciQM0sSN9uPoqHkbspTxHO0kmzXhBeE/xU=
|
||||
github.com/armosec/utils-go v0.0.3/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo=
|
||||
github.com/armosec/utils-k8s-go v0.0.1 h1:Ay3y7fW+4+FjVc0+obOWm8YsnEvM31vPAVoKTyTAFRk=
|
||||
github.com/armosec/utils-k8s-go v0.0.1/go.mod h1:qrU4pmY2iZsOb39Eltpm0sTTNM3E4pmeyWx4dgDUC2U=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/aws/aws-sdk-go v1.41.1 h1:TR9j7i73tzV8ELPMc0LkImSRLljRJ+gQeArKBC7IfVE=
|
||||
github.com/aws/aws-sdk-go v1.41.1/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
github.com/aws/aws-sdk-go v1.41.11 h1:QLouWsiYQ8i22kD8k58Dpdhio1A0MpT7bg9ZNXqEjuI=
|
||||
github.com/aws/aws-sdk-go v1.41.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
@@ -106,9 +119,10 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/briandowns/spinner v1.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs=
|
||||
github.com/briandowns/spinner v1.16.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
|
||||
github.com/briandowns/spinner v1.18.0 h1:SJs0maNOs4FqhBwiJ3Gr7Z1D39/rukIVGQvpNZVHVcM=
|
||||
github.com/briandowns/spinner v1.18.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/bytecodealliance/wasmtime-go v0.30.0 h1:WfYpr4WdqInt8m5/HvYinf+HrSEAIhItKIcth+qb1h4=
|
||||
github.com/bytecodealliance/wasmtime-go v0.30.0/go.mod h1:q320gUxqyI8yB+ZqRuaJOEnGkAnHh6WtJjMaT2CW4wI=
|
||||
@@ -183,10 +197,12 @@ github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMi
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
|
||||
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c=
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
@@ -203,12 +219,19 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
|
||||
github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
|
||||
github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
|
||||
github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gota/gota v0.12.0 h1:T5BDg1hTf5fZ/CO+T/N0E+DDqUhvoKBl+UVckgcAAQg=
|
||||
github.com/go-gota/gota v0.12.0/go.mod h1:UT+NsWpZC/FhaOyWb9Hui0jXg0Iq8e/YugZHTbyW/34=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
@@ -229,13 +252,15 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.1.0+incompatible h1:sIa2eCvUTwgjbqXrPLfNwUf9S3i3mpH1O1atV+iL/Wk=
|
||||
github.com/gofrs/uuid v4.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
@@ -315,9 +340,11 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
||||
github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
|
||||
@@ -379,6 +406,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
@@ -407,13 +436,13 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -467,8 +496,9 @@ github.com/open-policy-agent/opa v0.33.1 h1:EJe00U5H82iMsemgxcNm9RFwjW8zPyRMvL+0
|
||||
github.com/open-policy-agent/opa v0.33.1/go.mod h1:Zb+IdRe0s7M++Rv/KgyuB0qvxO3CUpQ+ZW5v+w/cRUo=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
@@ -476,6 +506,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
||||
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
|
||||
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -518,6 +550,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
@@ -666,20 +699,33 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@@ -750,6 +796,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@@ -841,6 +888,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -849,6 +897,7 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -875,11 +924,13 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
@@ -894,6 +945,7 @@ golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -940,6 +992,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/gonum v0.9.1 h1:HCWmqqNoELL0RAQeKBXWtkp04mGk8koafcB4He6+uhc=
|
||||
gonum.org/v1/gonum v0.9.1/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
@@ -964,6 +1024,7 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA=
|
||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -1021,6 +1082,7 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
@@ -1045,6 +1107,7 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
@@ -1126,6 +1189,7 @@ k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2R
|
||||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g=
|
||||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
|
||||
|
||||
63
hostsensorutils/hostsensor.yaml
Normal file
63
hostsensorutils/hostsensor.yaml
Normal file
@@ -0,0 +1,63 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
app: host-sensor
|
||||
kubernetes.io/metadata.name: armo-kube-host-sensor
|
||||
tier: armo-kube-host-sensor-control-plane
|
||||
name: armo-kube-host-sensor
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: host-sensor
|
||||
namespace: armo-kube-host-sensor
|
||||
labels:
|
||||
k8s-app: armo-kube-host-sensor
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
name: host-sensor
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: host-sensor
|
||||
spec:
|
||||
tolerations:
|
||||
# this toleration is to have the daemonset runnable on master nodes
|
||||
# remove it if your masters can't run pods
|
||||
- key: node-role.kubernetes.io/master
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: host-sensor
|
||||
image: quay.io/armosec/kube-host-sensor:latest
|
||||
securityContext:
|
||||
privileged: true
|
||||
readOnlyRootFilesystem: true
|
||||
procMount: Unmasked
|
||||
ports:
|
||||
- name: http
|
||||
hostPort: 7888
|
||||
containerPort: 7888
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1m
|
||||
memory: 200Mi
|
||||
requests:
|
||||
cpu: 1m
|
||||
memory: 200Mi
|
||||
volumeMounts:
|
||||
- mountPath: /host_fs
|
||||
name: host-filesystem
|
||||
terminationGracePeriodSeconds: 120
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
automountServiceAccountToken: false
|
||||
volumes:
|
||||
- hostPath:
|
||||
path: /
|
||||
type: Directory
|
||||
name: host-filesystem
|
||||
hostNetwork: true
|
||||
hostPID: true
|
||||
hostIPC: true
|
||||
214
hostsensorutils/hostsensordeploy.go
Normal file
214
hostsensorutils/hostsensordeploy.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package hostsensorutils
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
appsapplyv1 "k8s.io/client-go/applyconfigurations/apps/v1"
|
||||
coreapplyv1 "k8s.io/client-go/applyconfigurations/core/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed hostsensor.yaml
|
||||
hostSensorYAML string
|
||||
)
|
||||
|
||||
type HostSensorHandler struct {
|
||||
HostSensorPort int32
|
||||
HostSensorPodNames map[string]string //map from pod names to node names
|
||||
IsReady <-chan bool //readonly chan
|
||||
k8sObj *k8sinterface.KubernetesApi
|
||||
DaemonSet *appsv1.DaemonSet
|
||||
podListLock sync.RWMutex
|
||||
gracePeriod int64
|
||||
}
|
||||
|
||||
func NewHostSensorHandler(k8sObj *k8sinterface.KubernetesApi) (*HostSensorHandler, error) {
|
||||
|
||||
if k8sObj == nil {
|
||||
return nil, fmt.Errorf("nil k8s interface received")
|
||||
}
|
||||
hsh := &HostSensorHandler{
|
||||
k8sObj: k8sObj,
|
||||
HostSensorPodNames: map[string]string{},
|
||||
gracePeriod: int64(15),
|
||||
}
|
||||
// Don't deploy on cluster with no nodes. Some cloud providers prevents termination of K8s objects for cluster with no nodes!!!
|
||||
if nodeList, err := k8sObj.KubernetesClient.CoreV1().Nodes().List(k8sObj.Context, metav1.ListOptions{}); err != nil || len(nodeList.Items) == 0 {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("no nodes to scan")
|
||||
}
|
||||
return hsh, fmt.Errorf("in NewHostSensorHandler, failed to get nodes list: %v", err)
|
||||
}
|
||||
|
||||
return hsh, nil
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) Init() error {
|
||||
// deploy the YAML
|
||||
// store namespace + port
|
||||
// store pod names
|
||||
// make sure all pods are running, after X seconds treat has running anyway, and log an error on the pods not running yet
|
||||
cautils.ProgressTextDisplay("Installing host sensor")
|
||||
cautils.StartSpinner()
|
||||
defer cautils.StopSpinner()
|
||||
if err := hsh.applyYAML(); err != nil {
|
||||
return fmt.Errorf("in HostSensorHandler init failed to apply YAML: %v", err)
|
||||
}
|
||||
hsh.populatePodNamesToNodeNames()
|
||||
if err := hsh.checkPodForEachNode(); err != nil {
|
||||
fmt.Printf("failed to validate host-sensor pods status: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) applyYAML() error {
|
||||
dec := yaml.NewDocumentDecoder(io.NopCloser(strings.NewReader(hostSensorYAML)))
|
||||
// apply namespace
|
||||
singleYAMLBytes := make([]byte, 4096)
|
||||
if readLen, err := dec.Read(singleYAMLBytes); err != nil {
|
||||
return fmt.Errorf("failed to read YAML of namespace: %v", err)
|
||||
} else {
|
||||
singleYAMLBytes = singleYAMLBytes[:readLen]
|
||||
}
|
||||
namespaceAC := &coreapplyv1.NamespaceApplyConfiguration{}
|
||||
if err := yaml.Unmarshal(singleYAMLBytes, namespaceAC); err != nil {
|
||||
return fmt.Errorf("failed to Unmarshal YAML of namespace: %v", err)
|
||||
}
|
||||
namespaceName := ""
|
||||
|
||||
if ns, err := hsh.k8sObj.KubernetesClient.CoreV1().Namespaces().Apply(hsh.k8sObj.Context, namespaceAC, metav1.ApplyOptions{
|
||||
FieldManager: "kubescape",
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to apply YAML of namespace: %v", err)
|
||||
} else {
|
||||
namespaceName = ns.Name
|
||||
}
|
||||
// apply DaemonSet
|
||||
daemonAC := &appsapplyv1.DaemonSetApplyConfiguration{}
|
||||
singleYAMLBytes = make([]byte, 4096)
|
||||
if readLen, err := dec.Read(singleYAMLBytes); err != nil {
|
||||
if erra := hsh.tearDownNamesapce(namespaceName); erra != nil {
|
||||
err = fmt.Errorf("%v; In addidtion %v", err, erra)
|
||||
}
|
||||
return fmt.Errorf("failed to read YAML of DaemonSet: %v", err)
|
||||
} else {
|
||||
singleYAMLBytes = singleYAMLBytes[:readLen]
|
||||
}
|
||||
if err := yaml.Unmarshal(singleYAMLBytes, daemonAC); err != nil {
|
||||
if erra := hsh.tearDownNamesapce(namespaceName); erra != nil {
|
||||
err = fmt.Errorf("%v; In addidtion %v", err, erra)
|
||||
}
|
||||
return fmt.Errorf("failed to Unmarshal YAML of DaemonSet: %v", err)
|
||||
}
|
||||
daemonAC.Namespace = &namespaceName
|
||||
if ds, err := hsh.k8sObj.KubernetesClient.AppsV1().DaemonSets(namespaceName).Apply(hsh.k8sObj.Context, daemonAC, metav1.ApplyOptions{
|
||||
FieldManager: "kubescape",
|
||||
}); err != nil {
|
||||
if erra := hsh.tearDownNamesapce(namespaceName); erra != nil {
|
||||
err = fmt.Errorf("%v; In addidtion %v", err, erra)
|
||||
}
|
||||
return fmt.Errorf("failed to apply YAML of DaemonSet: %v", err)
|
||||
} else {
|
||||
hsh.HostSensorPort = ds.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort
|
||||
hsh.DaemonSet = ds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) checkPodForEachNode() error {
|
||||
deadline := time.Now().Add(time.Second * 100)
|
||||
for {
|
||||
nodesList, err := hsh.k8sObj.KubernetesClient.CoreV1().Nodes().List(hsh.k8sObj.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("in checkPodsForEveryNode, failed to get nodes list: %v", nodesList)
|
||||
}
|
||||
hsh.podListLock.RLock()
|
||||
podsNum := len(hsh.HostSensorPodNames)
|
||||
hsh.podListLock.RUnlock()
|
||||
if len(nodesList.Items) == podsNum {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
return fmt.Errorf("host-sensor pods number (%d) differ than nodes number (%d) after deadline exceded", podsNum, len(nodesList.Items))
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// initiating routine to keep pod list updated
|
||||
func (hsh *HostSensorHandler) populatePodNamesToNodeNames() {
|
||||
|
||||
go func() {
|
||||
watchRes, err := hsh.k8sObj.KubernetesClient.CoreV1().Pods(hsh.DaemonSet.Namespace).Watch(hsh.k8sObj.Context, metav1.ListOptions{
|
||||
Watch: true,
|
||||
LabelSelector: fmt.Sprintf("name=%s", hsh.DaemonSet.Spec.Template.Labels["name"]),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to watch over daemonset pods")
|
||||
}
|
||||
for eve := range watchRes.ResultChan() {
|
||||
pod, ok := eve.Object.(*corev1.Pod)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
go hsh.updatePodInListAtomic(eve.Type, pod)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) updatePodInListAtomic(eventType watch.EventType, podObj *corev1.Pod) {
|
||||
hsh.podListLock.Lock()
|
||||
defer hsh.podListLock.Unlock()
|
||||
|
||||
switch eventType {
|
||||
case watch.Added, watch.Modified:
|
||||
if podObj.Status.Phase == corev1.PodRunning {
|
||||
hsh.HostSensorPodNames[podObj.ObjectMeta.Name] = podObj.Spec.NodeName
|
||||
} else {
|
||||
delete(hsh.HostSensorPodNames, podObj.ObjectMeta.Name)
|
||||
}
|
||||
default:
|
||||
delete(hsh.HostSensorPodNames, podObj.ObjectMeta.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) tearDownNamesapce(namespace string) error {
|
||||
|
||||
if err := hsh.k8sObj.KubernetesClient.CoreV1().Namespaces().Delete(hsh.k8sObj.Context, namespace, metav1.DeleteOptions{GracePeriodSeconds: &hsh.gracePeriod}); err != nil {
|
||||
return fmt.Errorf("failed to delete host-sensor namespace: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) TearDown() error {
|
||||
namespace := hsh.GetNamespace()
|
||||
if err := hsh.k8sObj.KubernetesClient.AppsV1().DaemonSets(hsh.GetNamespace()).Delete(hsh.k8sObj.Context, hsh.DaemonSet.Name, metav1.DeleteOptions{GracePeriodSeconds: &hsh.gracePeriod}); err != nil {
|
||||
return fmt.Errorf("failed to delete host-sensor daemonset: %v", err)
|
||||
}
|
||||
if err := hsh.tearDownNamesapce(namespace); err != nil {
|
||||
return fmt.Errorf("failed to delete host-sensor daemonset: %v", err)
|
||||
}
|
||||
// TODO: wait for termination? may take up to 120 seconds!!!
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) GetNamespace() string {
|
||||
if hsh.DaemonSet == nil {
|
||||
return ""
|
||||
}
|
||||
return hsh.DaemonSet.Namespace
|
||||
}
|
||||
198
hostsensorutils/hostsensorgetfrompod.go
Normal file
198
hostsensorutils/hostsensorgetfrompod.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package hostsensorutils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes/hostsensor"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func (hsh *HostSensorHandler) getPodList() (res map[string]string, err error) {
|
||||
hsh.podListLock.RLock()
|
||||
jsonBytes, err := json.Marshal(hsh.HostSensorPodNames)
|
||||
hsh.podListLock.RUnlock()
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("failed to marshal pod list: %v", err)
|
||||
}
|
||||
err = json.Unmarshal(jsonBytes, &res)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("failed to unmarshal pod list: %v", err)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) HTTPGetToPod(podName, path string) ([]byte, error) {
|
||||
// send the request to the port
|
||||
|
||||
restProxy := hsh.k8sObj.KubernetesClient.CoreV1().Pods(hsh.DaemonSet.Namespace).ProxyGet("http", podName, fmt.Sprintf("%d", hsh.HostSensorPort), path, map[string]string{})
|
||||
return restProxy.DoRaw(hsh.k8sObj.Context)
|
||||
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) ForwardToPod(podName, path string) ([]byte, error) {
|
||||
// NOT IN USE:
|
||||
// ---
|
||||
// spawn port forwarding
|
||||
// req := hsh.k8sObj.KubernetesClient.CoreV1().RESTClient().Post()
|
||||
// req = req.Name(podName)
|
||||
// req = req.Namespace(hsh.DaemonSet.Namespace)
|
||||
// req = req.Resource("pods")
|
||||
// req = req.SubResource("portforward")
|
||||
// ----
|
||||
// https://github.com/gianarb/kube-port-forward
|
||||
// fullPath := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward",
|
||||
// hsh.DaemonSet.Namespace, podName)
|
||||
// transport, upgrader, err := spdy.RoundTripperFor(hsh.k8sObj.KubernetesClient.config)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// hostIP := strings.TrimLeft(req.RestConfig.Host, "htps:/")
|
||||
// dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, http.MethodPost, &url.URL{Scheme: "http", Path: path, Host: hostIP})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// sendAllPodsHTTPGETRequest fills the raw byte response in the envelope and the node name, but not the GroupVersionKind
|
||||
// so the caller is responsible to convert the raw data to some structured data and add the GroupVersionKind details
|
||||
func (hsh *HostSensorHandler) sendAllPodsHTTPGETRequest(path, requestKind string) ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
podList, err := hsh.getPodList()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sendAllPodsHTTPGETRequest: %v", err)
|
||||
}
|
||||
res := make([]hostsensor.HostSensorDataEnvelope, 0, len(podList))
|
||||
resLock := sync.Mutex{}
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(podList))
|
||||
for podName := range podList {
|
||||
go func(podName, path string) {
|
||||
defer wg.Done()
|
||||
resBytes, err := hsh.HTTPGetToPod(podName, path)
|
||||
if err != nil {
|
||||
fmt.Printf("In sendAllPodsHTTPGETRequest failed to get data '%s' from pod '%s': %v", path, podName, err)
|
||||
} else {
|
||||
resLock.Lock()
|
||||
defer resLock.Unlock()
|
||||
hostSensorDataEnvelope := hostsensor.HostSensorDataEnvelope{}
|
||||
hostSensorDataEnvelope.SetApiVersion(k8sinterface.JoinGroupVersion(hostsensor.GroupHostSensor, hostsensor.Version))
|
||||
hostSensorDataEnvelope.SetKind(requestKind)
|
||||
hostSensorDataEnvelope.SetName(podList[podName])
|
||||
hostSensorDataEnvelope.SetData(resBytes)
|
||||
res = append(res, hostSensorDataEnvelope)
|
||||
}
|
||||
|
||||
}(podName, path)
|
||||
}
|
||||
wg.Wait()
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// return list of OpenPortsList
|
||||
func (hsh *HostSensorHandler) GetOpenPortsList() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
// loop over pods and port-forward it to each of them
|
||||
return hsh.sendAllPodsHTTPGETRequest("/openedPorts", "OpenPortsList")
|
||||
}
|
||||
|
||||
// return list of LinuxSecurityHardeningStatus
|
||||
func (hsh *HostSensorHandler) GetLinuxSecurityHardeningStatus() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
// loop over pods and port-forward it to each of them
|
||||
return hsh.sendAllPodsHTTPGETRequest("/linuxSecurityHardening", "LinuxSecurityHardeningStatus")
|
||||
}
|
||||
|
||||
// return list of KubeletCommandLine
|
||||
func (hsh *HostSensorHandler) GetKubeletCommandLine() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
// loop over pods and port-forward it to each of them
|
||||
resps, err := hsh.sendAllPodsHTTPGETRequest("/kubeletCommandLine", "KubeletCommandLine")
|
||||
if err != nil {
|
||||
return resps, err
|
||||
}
|
||||
for resp := range resps {
|
||||
var data = make(map[string]interface{})
|
||||
data["fullCommand"] = string(resps[resp].Data)
|
||||
resBytesMarshal, err := json.Marshal(data)
|
||||
// TODO catch error
|
||||
if err == nil {
|
||||
resps[resp].Data = json.RawMessage(resBytesMarshal)
|
||||
}
|
||||
}
|
||||
|
||||
return resps, nil
|
||||
|
||||
}
|
||||
|
||||
// return list of
|
||||
func (hsh *HostSensorHandler) GetKernelVersion() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
// loop over pods and port-forward it to each of them
|
||||
return hsh.sendAllPodsHTTPGETRequest("/kernelVersion", "KernelVersion")
|
||||
}
|
||||
|
||||
// return list of
|
||||
func (hsh *HostSensorHandler) GetOsReleaseFile() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
// loop over pods and port-forward it to each of them
|
||||
return hsh.sendAllPodsHTTPGETRequest("/osRelease", "OsReleaseFile")
|
||||
}
|
||||
|
||||
// return list of
|
||||
func (hsh *HostSensorHandler) GetKubeletConfigurations() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
// loop over pods and port-forward it to each of them
|
||||
res, err := hsh.sendAllPodsHTTPGETRequest("/kubeletConfigurations", "KubeletConfiguration") // empty kind, will be overridden
|
||||
for resIdx := range res {
|
||||
jsonBytes, err := yaml.YAMLToJSON(res[resIdx].Data)
|
||||
if err != nil {
|
||||
fmt.Printf("In GetKubeletConfigurations failed to YAMLToJSON: %v;\n%v", err, res[resIdx])
|
||||
continue
|
||||
}
|
||||
res[resIdx].SetData(jsonBytes)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (hsh *HostSensorHandler) CollectResources() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
res := make([]hostsensor.HostSensorDataEnvelope, 0)
|
||||
if hsh.DaemonSet == nil {
|
||||
return res, nil
|
||||
}
|
||||
cautils.ProgressTextDisplay("Accessing host sensor")
|
||||
cautils.StartSpinner()
|
||||
defer cautils.StopSpinner()
|
||||
kcData, err := hsh.GetKubeletConfigurations()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
//
|
||||
kcData, err = hsh.GetKubeletCommandLine()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
//
|
||||
kcData, err = hsh.GetOsReleaseFile()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
//
|
||||
kcData, err = hsh.GetKernelVersion()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
//
|
||||
kcData, err = hsh.GetLinuxSecurityHardeningStatus()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
//
|
||||
kcData, err = hsh.GetOpenPortsList()
|
||||
if err != nil {
|
||||
return kcData, err
|
||||
}
|
||||
res = append(res, kcData...)
|
||||
// finish
|
||||
cautils.SuccessTextDisplay("Read host information from host sensor")
|
||||
return res, nil
|
||||
}
|
||||
10
hostsensorutils/hostsensorinterface.go
Normal file
10
hostsensorutils/hostsensorinterface.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package hostsensorutils
|
||||
|
||||
import "github.com/armosec/opa-utils/objectsenvelopes/hostsensor"
|
||||
|
||||
type IHostSensor interface {
|
||||
Init() error
|
||||
TearDown() error
|
||||
CollectResources() ([]hostsensor.HostSensorDataEnvelope, error)
|
||||
GetNamespace() string
|
||||
}
|
||||
24
hostsensorutils/hostsensormock.go
Normal file
24
hostsensorutils/hostsensormock.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package hostsensorutils
|
||||
|
||||
import (
|
||||
"github.com/armosec/opa-utils/objectsenvelopes/hostsensor"
|
||||
)
|
||||
|
||||
type HostSensorHandlerMock struct {
|
||||
}
|
||||
|
||||
func (hshm *HostSensorHandlerMock) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hshm *HostSensorHandlerMock) TearDown() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hshm *HostSensorHandlerMock) CollectResources() ([]hostsensor.HostSensorDataEnvelope, error) {
|
||||
return []hostsensor.HostSensorDataEnvelope{}, nil
|
||||
}
|
||||
|
||||
func (hshm *HostSensorHandlerMock) GetNamespace() string {
|
||||
return ""
|
||||
}
|
||||
@@ -53,6 +53,6 @@ echo -e "\033[0m"
|
||||
$KUBESCAPE_EXEC version
|
||||
echo
|
||||
|
||||
echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan framework nsa --exclude-namespaces kube-system,kube-public"
|
||||
echo -e "\033[35mUsage: $ $KUBESCAPE_EXEC scan --submit"
|
||||
|
||||
echo -e "\033[0m"
|
||||
|
||||
16
main.go
16
main.go
@@ -1,23 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/armosec/kubescape/cmd"
|
||||
"github.com/armosec/kubescape/clihandler/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
CheckLatestVersion()
|
||||
cmd.Execute()
|
||||
}
|
||||
|
||||
func CheckLatestVersion() {
|
||||
latest, err := cmd.GetLatestVersion()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
} else if latest != cmd.BuildNumber {
|
||||
fmt.Println("Warning: You are not updated to the latest release: " + latest)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
85
mocks/loadmocks.go
Normal file
85
mocks/loadmocks.go
Normal file
File diff suppressed because one or more lines are too long
@@ -6,52 +6,51 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/armosec/kubescape/cautils"
|
||||
"github.com/armosec/opa-utils/exceptions"
|
||||
ksscore "github.com/armosec/kubescape/score"
|
||||
"github.com/armosec/opa-utils/objectsenvelopes"
|
||||
"github.com/armosec/opa-utils/reporthandling"
|
||||
"github.com/armosec/opa-utils/score"
|
||||
"github.com/armosec/opa-utils/reporthandling/apis"
|
||||
"github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults"
|
||||
"github.com/open-policy-agent/opa/storage"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/armosec/k8s-interface/k8sinterface"
|
||||
"github.com/armosec/k8s-interface/workloadinterface"
|
||||
|
||||
"github.com/armosec/opa-utils/resources"
|
||||
"github.com/golang/glog"
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/rego"
|
||||
"github.com/open-policy-agent/opa/storage"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
const ScoreConfigPath = "/resources/config"
|
||||
|
||||
var RegoK8sCredentials storage.Store
|
||||
|
||||
type OPAProcessorHandler struct {
|
||||
processedPolicy *chan *cautils.OPASessionObj
|
||||
reportResults *chan *cautils.OPASessionObj
|
||||
// componentConfig cautils.ComponentConfig
|
||||
processedPolicy *chan *cautils.OPASessionObj
|
||||
reportResults *chan *cautils.OPASessionObj
|
||||
regoDependenciesData *resources.RegoDependenciesData
|
||||
}
|
||||
|
||||
type OPAProcessor struct {
|
||||
*cautils.OPASessionObj
|
||||
regoDependenciesData *resources.RegoDependenciesData
|
||||
}
|
||||
|
||||
func NewOPAProcessor(sessionObj *cautils.OPASessionObj) *OPAProcessor {
|
||||
func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *resources.RegoDependenciesData) *OPAProcessor {
|
||||
if regoDependenciesData != nil && sessionObj != nil {
|
||||
regoDependenciesData.PostureControlInputs = sessionObj.RegoInputData.PostureControlInputs
|
||||
}
|
||||
return &OPAProcessor{
|
||||
OPASessionObj: sessionObj,
|
||||
OPASessionObj: sessionObj,
|
||||
regoDependenciesData: regoDependenciesData,
|
||||
}
|
||||
}
|
||||
|
||||
func NewOPAProcessorHandler(processedPolicy, reportResults *chan *cautils.OPASessionObj) *OPAProcessorHandler {
|
||||
|
||||
regoDependenciesData := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), cautils.ClusterName)
|
||||
store, err := regoDependenciesData.TOStorage()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RegoK8sCredentials = store
|
||||
|
||||
return &OPAProcessorHandler{
|
||||
processedPolicy: processedPolicy,
|
||||
reportResults: reportResults,
|
||||
processedPolicy: processedPolicy,
|
||||
reportResults: reportResults,
|
||||
regoDependenciesData: resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), cautils.ClusterName),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,166 +58,215 @@ func (opaHandler *OPAProcessorHandler) ProcessRulesListenner() {
|
||||
|
||||
for {
|
||||
opaSessionObj := <-*opaHandler.processedPolicy
|
||||
opap := NewOPAProcessor(opaSessionObj)
|
||||
opap := NewOPAProcessor(opaSessionObj, opaHandler.regoDependenciesData)
|
||||
|
||||
policies := ConvertFrameworksToPolicies(opap.Frameworks, cautils.BuildNumber)
|
||||
|
||||
ConvertFrameworksToSummaryDetails(&opap.Report.SummaryDetails, opap.Frameworks, policies)
|
||||
|
||||
// process
|
||||
if err := opap.Process(); err != nil {
|
||||
fmt.Println(err)
|
||||
if err := opap.Process(policies); err != nil {
|
||||
// fmt.Println(err)
|
||||
}
|
||||
|
||||
// edit results
|
||||
opap.updateResults()
|
||||
|
||||
// update score
|
||||
// opap.updateScore()
|
||||
|
||||
//TODO: review this location
|
||||
scorewrapper := ksscore.NewScoreWrapper(opaSessionObj)
|
||||
scorewrapper.Calculate(ksscore.EPostureReportV2)
|
||||
// report
|
||||
*opaHandler.reportResults <- opaSessionObj
|
||||
}
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) Process() error {
|
||||
func (opap *OPAProcessor) Process(policies *cautils.Policies) error {
|
||||
// glog.Infof(fmt.Sprintf("Starting 'Process'. reportID: %s", opap.PostureReport.ReportID))
|
||||
cautils.ProgressTextDisplay(fmt.Sprintf("Scanning cluster %s", cautils.ClusterName))
|
||||
cautils.StartSpinner()
|
||||
frameworkReports := []reporthandling.FrameworkReport{}
|
||||
|
||||
var errs error
|
||||
for i := range opap.Frameworks {
|
||||
frameworkReport, err := opap.processFramework(&opap.Frameworks[i])
|
||||
for _, control := range policies.Controls {
|
||||
|
||||
resourcesAssociatedControl, err := opap.processControl(&control)
|
||||
if err != nil {
|
||||
errs = fmt.Errorf("%v\n%s", errs, err.Error())
|
||||
appendError(&errs, err)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
frameworkReports = append(frameworkReports, *frameworkReport)
|
||||
}
|
||||
|
||||
opap.PostureReport.FrameworkReports = frameworkReports
|
||||
opap.PostureReport.ReportID = uuid.NewV4().String()
|
||||
opap.PostureReport.ReportGenerationTime = time.Now().UTC()
|
||||
// glog.Infof(fmt.Sprintf("Done 'Process'. reportID: %s", opap.PostureReport.ReportID))
|
||||
opap.Report.ReportGenerationTime = time.Now().UTC()
|
||||
|
||||
cautils.StopSpinner()
|
||||
cautils.SuccessTextDisplay(fmt.Sprintf("Done scanning cluster %s", cautils.ClusterName))
|
||||
return errs
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) processFramework(framework *reporthandling.Framework) (*reporthandling.FrameworkReport, error) {
|
||||
var errs error
|
||||
|
||||
frameworkReport := reporthandling.FrameworkReport{}
|
||||
frameworkReport.Name = framework.Name
|
||||
|
||||
controlReports := []reporthandling.ControlReport{}
|
||||
for i := range framework.Controls {
|
||||
controlReport, err := opap.processControl(&framework.Controls[i])
|
||||
if err != nil {
|
||||
errs = fmt.Errorf("%v\n%s", errs, err.Error())
|
||||
}
|
||||
if controlReport != nil {
|
||||
controlReports = append(controlReports, *controlReport)
|
||||
}
|
||||
func appendError(errs *error, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if errs == nil {
|
||||
errs = &err
|
||||
} else {
|
||||
*errs = fmt.Errorf("%v\n%s", *errs, err.Error())
|
||||
}
|
||||
frameworkReport.ControlReports = controlReports
|
||||
return &frameworkReport, errs
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) processControl(control *reporthandling.Control) (*reporthandling.ControlReport, error) {
|
||||
func (opap *OPAProcessor) processControl(control *reporthandling.Control) (map[string]resourcesresults.ResourceAssociatedControl, error) {
|
||||
var errs error
|
||||
|
||||
controlReport := reporthandling.ControlReport{}
|
||||
controlReport.PortalBase = control.PortalBase
|
||||
controlReport.ControlID = control.ControlID
|
||||
controlReport.Control_ID = control.Control_ID // TODO: delete when 'id' is deprecated
|
||||
resourcesAssociatedControl := make(map[string]resourcesresults.ResourceAssociatedControl)
|
||||
|
||||
controlReport.Name = control.Name
|
||||
controlReport.Description = control.Description
|
||||
controlReport.Remediation = control.Remediation
|
||||
|
||||
ruleReports := []reporthandling.RuleReport{}
|
||||
// ruleResults := make(map[string][]resourcesresults.ResourceAssociatedRule)
|
||||
for i := range control.Rules {
|
||||
ruleReport, err := opap.processRule(&control.Rules[i])
|
||||
resourceAssociatedRule, err := opap.processRule(&control.Rules[i])
|
||||
if err != nil {
|
||||
errs = fmt.Errorf("%v\n%s", errs, err.Error())
|
||||
appendError(&errs, err)
|
||||
continue
|
||||
}
|
||||
if ruleReport != nil {
|
||||
ruleReports = append(ruleReports, *ruleReport)
|
||||
|
||||
// append failed rules to controls
|
||||
if len(resourceAssociatedRule) != 0 {
|
||||
for resourceID, ruleResponse := range resourceAssociatedRule {
|
||||
|
||||
controlResult := resourcesresults.ResourceAssociatedControl{}
|
||||
controlResult.SetID(control.ControlID)
|
||||
controlResult.SetName(control.Name)
|
||||
|
||||
if _, ok := resourcesAssociatedControl[resourceID]; ok {
|
||||
controlResult.ResourceAssociatedRules = resourcesAssociatedControl[resourceID].ResourceAssociatedRules
|
||||
}
|
||||
if ruleResponse != nil {
|
||||
controlResult.ResourceAssociatedRules = append(controlResult.ResourceAssociatedRules, *ruleResponse)
|
||||
}
|
||||
resourcesAssociatedControl[resourceID] = controlResult
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(ruleReports) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
controlReport.RuleReports = ruleReports
|
||||
return &controlReport, errs
|
||||
|
||||
return resourcesAssociatedControl, errs
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule) (*reporthandling.RuleReport, error) {
|
||||
if ruleWithArmoOpaDependency(rule.Attributes) {
|
||||
return nil, nil
|
||||
}
|
||||
k8sObjects := getKubernetesObjects(opap.K8SResources, rule.Match)
|
||||
ruleReport, err := opap.runOPAOnSingleRule(rule, k8sObjects)
|
||||
func (opap *OPAProcessor) processRule(rule *reporthandling.PolicyRule) (map[string]*resourcesresults.ResourceAssociatedRule, error) {
|
||||
|
||||
postureControlInputs := opap.regoDependenciesData.GetFilteredPostureControlInputs(rule.ConfigInputs) // get store
|
||||
|
||||
inputResources, err := reporthandling.RegoResourcesAggregator(rule, getAllSupportedObjects(opap.K8SResources, opap.AllResources, rule))
|
||||
if err != nil {
|
||||
ruleReport.RuleStatus.Status = "failure"
|
||||
ruleReport.RuleStatus.Message = err.Error()
|
||||
return nil, fmt.Errorf("error getting aggregated k8sObjects: %s", err.Error())
|
||||
}
|
||||
if len(inputResources) == 0 {
|
||||
return nil, nil // no resources found for testing
|
||||
}
|
||||
|
||||
inputRawResources := workloadinterface.ListMetaToMap(inputResources)
|
||||
|
||||
resources := map[string]*resourcesresults.ResourceAssociatedRule{}
|
||||
// the failed resources are a subgroup of the enumeratedData, so we store the enumeratedData like it was the input data
|
||||
enumeratedData, err := opap.enumerateData(rule, inputRawResources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inputResources = objectsenvelopes.ListMapToMeta(enumeratedData)
|
||||
for i := range inputResources {
|
||||
resources[inputResources[i].GetID()] = &resourcesresults.ResourceAssociatedRule{
|
||||
Name: rule.Name,
|
||||
ControlConfigurations: postureControlInputs,
|
||||
Status: apis.StatusPassed,
|
||||
}
|
||||
opap.AllResources[inputResources[i].GetID()] = inputResources[i]
|
||||
}
|
||||
|
||||
ruleResponses, err := opap.runOPAOnSingleRule(rule, inputRawResources, ruleData, postureControlInputs)
|
||||
if err != nil {
|
||||
// TODO - Handle error
|
||||
glog.Error(err)
|
||||
} else {
|
||||
ruleReport.RuleStatus.Status = "success"
|
||||
// ruleResponse to ruleResult
|
||||
for i := range ruleResponses {
|
||||
failedResources := objectsenvelopes.ListMapToMeta(ruleResponses[i].GetFailedResources())
|
||||
for j := range failedResources {
|
||||
ruleResult := &resourcesresults.ResourceAssociatedRule{}
|
||||
if r, k := resources[failedResources[j].GetID()]; k {
|
||||
ruleResult = r
|
||||
}
|
||||
|
||||
ruleResult.Status = apis.StatusFailed
|
||||
for j := range ruleResponses[i].FailedPaths {
|
||||
ruleResult.Paths = append(ruleResult.Paths, resourcesresults.Path{FailedPath: ruleResponses[i].FailedPaths[j]})
|
||||
}
|
||||
resources[failedResources[j].GetID()] = ruleResult
|
||||
}
|
||||
}
|
||||
}
|
||||
ruleReport.ListInputResources = k8sObjects
|
||||
return &ruleReport, err
|
||||
|
||||
return resources, err
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) runOPAOnSingleRule(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}) (reporthandling.RuleReport, error) {
|
||||
func (opap *OPAProcessor) runOPAOnSingleRule(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, postureControlInputs map[string][]string) ([]reporthandling.RuleResponse, error) {
|
||||
switch rule.RuleLanguage {
|
||||
case reporthandling.RegoLanguage, reporthandling.RegoLanguage2:
|
||||
return opap.runRegoOnK8s(rule, k8sObjects)
|
||||
return opap.runRegoOnK8s(rule, k8sObjects, getRuleData, postureControlInputs)
|
||||
default:
|
||||
return reporthandling.RuleReport{}, fmt.Errorf("rule: '%s', language '%v' not supported", rule.Name, rule.RuleLanguage)
|
||||
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{}) (reporthandling.RuleReport, error) {
|
||||
|
||||
func (opap *OPAProcessor) runRegoOnK8s(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, postureControlInputs map[string][]string) ([]reporthandling.RuleResponse, error) {
|
||||
var errs error
|
||||
ruleReport := reporthandling.RuleReport{
|
||||
Name: rule.Name,
|
||||
}
|
||||
|
||||
// compile modules
|
||||
modules, err := getRuleDependencies()
|
||||
if err != nil {
|
||||
return ruleReport, fmt.Errorf("rule: '%s', %s", rule.Name, err.Error())
|
||||
return nil, fmt.Errorf("rule: '%s', %s", rule.Name, err.Error())
|
||||
}
|
||||
modules[rule.Name] = rule.Rule
|
||||
modules[rule.Name] = getRuleData(rule)
|
||||
compiled, err := ast.CompileModules(modules)
|
||||
if err != nil {
|
||||
return ruleReport, fmt.Errorf("in 'runRegoOnSingleRule', failed to compile rule, name: %s, reason: %s", rule.Name, err.Error())
|
||||
return nil, fmt.Errorf("in 'runRegoOnSingleRule', failed to compile rule, name: %s, reason: %s", rule.Name, err.Error())
|
||||
}
|
||||
|
||||
store, err := resources.TOStorage(postureControlInputs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Eval
|
||||
results, err := opap.regoEval(k8sObjects, compiled)
|
||||
results, err := opap.regoEval(k8sObjects, compiled, &store)
|
||||
if err != nil {
|
||||
errs = fmt.Errorf("rule: '%s', %s", rule.Name, err.Error())
|
||||
}
|
||||
|
||||
if results != nil {
|
||||
ruleReport.RuleResponses = append(ruleReport.RuleResponses, results...)
|
||||
}
|
||||
return ruleReport, errs
|
||||
return results, errs
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRego *ast.Compiler) ([]reporthandling.RuleResponse, error) {
|
||||
func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRego *ast.Compiler, store *storage.Store) ([]reporthandling.RuleResponse, error) {
|
||||
// opap.regoDependenciesData.PostureControlInputs
|
||||
|
||||
rego := rego.New(
|
||||
rego.Query("data.armo_builtins"), // get package name from rule
|
||||
rego.Compiler(compiledRego),
|
||||
rego.Input(inputObj),
|
||||
rego.Store(RegoK8sCredentials),
|
||||
rego.Store(*store),
|
||||
)
|
||||
|
||||
// Run evaluation
|
||||
resultSet, err := rego.Eval(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("in 'regoEval', failed to evaluate rule, reason: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
results, err := reporthandling.ParseRegoResult(&resultSet)
|
||||
|
||||
// results, err := ParseRegoResult(&resultSet)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
@@ -226,38 +274,20 @@ func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRe
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) updateScore() {
|
||||
func (opap *OPAProcessor) enumerateData(rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}) ([]map[string]interface{}, error) {
|
||||
|
||||
if !k8sinterface.ConnectedToCluster {
|
||||
return
|
||||
if ruleEnumeratorData(rule) == "" {
|
||||
return k8sObjects, nil
|
||||
}
|
||||
postureControlInputs := opap.regoDependenciesData.GetFilteredPostureControlInputs(rule.ConfigInputs)
|
||||
|
||||
// calculate score
|
||||
s := score.NewScore(k8sinterface.NewKubernetesApi(), ScoreConfigPath)
|
||||
s.Calculate(opap.PostureReport.FrameworkReports)
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) updateResults() {
|
||||
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])
|
||||
|
||||
// edit results - remove data
|
||||
|
||||
// TODO - move function to pkg - use RemoveData
|
||||
for c := range opap.PostureReport.FrameworkReports[f].ControlReports {
|
||||
for r, ruleReport := range opap.PostureReport.FrameworkReports[f].ControlReports[c].RuleReports {
|
||||
// editing the responses -> removing duplications, clearing secret data, etc.
|
||||
opap.PostureReport.FrameworkReports[f].ControlReports[c].RuleReports[r].RuleResponses = editRuleResponses(ruleReport.RuleResponses)
|
||||
}
|
||||
}
|
||||
|
||||
ruleResponse, err := opap.runOPAOnSingleRule(rule, k8sObjects, ruleEnumeratorData, postureControlInputs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
failedResources := []map[string]interface{}{}
|
||||
for _, ruleResponse := range ruleResponse {
|
||||
failedResources = append(failedResources, ruleResponse.GetFailedResources()...)
|
||||
}
|
||||
return failedResources, nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user