mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
781b7fc1ee |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,2 +0,0 @@
|
||||
# Keep CRLF newlines in appropriate test files to have reproducible tests
|
||||
core/pkg/fixhandler/testdata/inserts/*-crlf-newlines.yaml text eol=crlf
|
||||
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,33 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
# Description
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
# Environment
|
||||
OS: ` ` <!-- the OS + version you’re running Kubescape on, e.g Ubuntu 22.04 LTS -->
|
||||
Version: ` ` <!-- the version that Kubescape reports when you run `kubescape version` -->
|
||||
|
||||
# Steps To Reproduce
|
||||
<!--
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
-->
|
||||
|
||||
# Expected behavior
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
# Actual Behavior
|
||||
<!-- A clear and concise description of what happened. If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
# Additional context
|
||||
<!-- Add any other context about the problem here. -->
|
||||
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: 'feature'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
<!-- A brief overview of the related current state -->
|
||||
|
||||
## Problem
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
## Solution
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
## Alternatives
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
## Additional context
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
44
.github/PULL_REQUEST_TEMPLATE.md
vendored
44
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,44 +0,0 @@
|
||||
## Overview
|
||||
<!-- Please provide a brief overview of the changes made in this pull request. e.g. current behavior/future behavior -->
|
||||
|
||||
<!--
|
||||
## Additional Information
|
||||
|
||||
> Any additional information that may be useful for reviewers to know
|
||||
-->
|
||||
|
||||
<!--
|
||||
## How to Test
|
||||
|
||||
> Please provide instructions on how to test the changes made in this pull request
|
||||
-->
|
||||
|
||||
<!--
|
||||
## Examples/Screenshots
|
||||
|
||||
> Here you add related screenshots
|
||||
-->
|
||||
|
||||
<!--
|
||||
## Related issues/PRs:
|
||||
|
||||
Here you add related issues and PRs.
|
||||
If this resolved an issue, write "Resolved #<issue number>
|
||||
|
||||
e.g. If this PR resolves issues 1 and 2, it should look as follows:
|
||||
* Resolved #1
|
||||
* Resolved #2
|
||||
-->
|
||||
|
||||
<!--
|
||||
## Checklist before requesting a review
|
||||
|
||||
put an [x] in the box to get it checked
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] If it is a core feature, I have added thorough tests.
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
|
||||
-->
|
||||
44
.github/actions/tag-action/action.yaml
vendored
44
.github/actions/tag-action/action.yaml
vendored
@@ -1,44 +0,0 @@
|
||||
name: 'Tag validator and retag'
|
||||
description: 'This action will check if the tag is rc and create a new tag for release'
|
||||
inputs:
|
||||
ORIGINAL_TAG: # id of input
|
||||
description: 'Original tag'
|
||||
required: true
|
||||
default: ${{ github.ref_name }}
|
||||
SUB_STRING:
|
||||
description: 'Sub string for rc tag'
|
||||
required: true
|
||||
default: "-rc"
|
||||
outputs:
|
||||
NEW_TAG:
|
||||
description: "The new tag for release"
|
||||
value: ${{ steps.retag.outputs.NEW_TAG }}
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- run: |
|
||||
if [[ -z "${{ inputs.ORIGINAL_TAG }}" ]]; then
|
||||
echo "The value of ORIGINAL_TAG is ${{ inputs.ORIGINAL_TAG }}"
|
||||
echo "Setting the value of ORIGINAL_TAG to ${{ github.ref_name }}"
|
||||
echo ORIGINAL_TAG="${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- run: |
|
||||
if [[ "${{ inputs.ORIGINAL_TAG }}" == *"${{ inputs.SUB_STRING }}"* ]]; then
|
||||
echo "Release candidate tag found."
|
||||
else
|
||||
echo "Release candidate tag not found."
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
|
||||
- id: retag
|
||||
run: |
|
||||
NEW_TAG=
|
||||
echo "Original tag: ${{ inputs.ORIGINAL_TAG }}"
|
||||
NEW_TAG=$(echo ${{ inputs.ORIGINAL_TAG }} | awk -F '-rc' '{print $1}')
|
||||
echo "New tag: $NEW_TAG"
|
||||
echo "NEW_TAG=$NEW_TAG" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
11
.github/dependabot.yaml
vendored
11
.github/dependabot.yaml
vendored
@@ -1,11 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
71
.github/workflows/00-pr-scanner.yaml
vendored
71
.github/workflows/00-pr-scanner.yaml
vendored
@@ -1,71 +0,0 @@
|
||||
name: 00-pr_scanner
|
||||
permissions: read-all
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths-ignore:
|
||||
- '**.yaml'
|
||||
- '**.yml'
|
||||
- '**.md'
|
||||
- '**.sh'
|
||||
- 'website/*'
|
||||
- 'examples/*'
|
||||
- 'docs/*'
|
||||
- 'build/*'
|
||||
- '.github/*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pr-scanner:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
deployments: read
|
||||
id-token: write
|
||||
issues: read
|
||||
discussions: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: write
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
attestations: read
|
||||
contents: write
|
||||
uses: ./.github/workflows/a-pr-scanner.yaml
|
||||
with:
|
||||
RELEASE: ""
|
||||
CLIENT: test
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
secrets: inherit
|
||||
|
||||
binary-build:
|
||||
if: ${{ github.actor == 'kubescape' }}
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: write
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
attestations: read
|
||||
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
|
||||
with:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.23"
|
||||
RELEASE: "latest"
|
||||
CLIENT: test
|
||||
secrets: inherit
|
||||
112
.github/workflows/02-release.yaml
vendored
112
.github/workflows/02-release.yaml
vendored
@@ -1,112 +0,0 @@
|
||||
name: 02-create_release
|
||||
permissions: read-all
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*-rc.*'
|
||||
jobs:
|
||||
retag:
|
||||
outputs:
|
||||
NEW_TAG: ${{ steps.tag-calculator.outputs.NEW_TAG }}
|
||||
runs-on: ubuntu22-core4-mem16-ssd150
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: tag-calculator
|
||||
uses: ./.github/actions/tag-action
|
||||
with:
|
||||
SUB_STRING: "-rc"
|
||||
binary-build:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
contents: write
|
||||
attestations: write
|
||||
needs: [retag]
|
||||
uses: ./.github/workflows/b-binary-build-and-e2e-tests.yaml
|
||||
with:
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: ""
|
||||
GO_VERSION: "1.23"
|
||||
RELEASE: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
CLIENT: release
|
||||
secrets: inherit
|
||||
create-release:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: write
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
statuses: read
|
||||
security-events: read
|
||||
attestations: read
|
||||
needs: [retag, binary-build]
|
||||
uses: ./.github/workflows/c-create-release.yaml
|
||||
with:
|
||||
RELEASE_NAME: "Release ${{ needs.retag.outputs.NEW_TAG }}"
|
||||
TAG: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
DRAFT: false
|
||||
secrets: inherit
|
||||
publish-image:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
attestations: read
|
||||
contents: write
|
||||
uses: ./.github/workflows/d-publish-image.yaml
|
||||
needs: [create-release, retag]
|
||||
with:
|
||||
client: "image-release"
|
||||
image_name: "quay.io/${{ github.repository_owner }}/kubescape-cli"
|
||||
image_tag: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
support_platforms: true
|
||||
cosign: true
|
||||
secrets: inherit
|
||||
post-release:
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: write
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
security-events: read
|
||||
statuses: read
|
||||
attestations: read
|
||||
contents: write
|
||||
uses: ./.github/workflows/e-post-release.yaml
|
||||
needs: [publish-image]
|
||||
with:
|
||||
TAG: ${{ needs.retag.outputs.NEW_TAG }}
|
||||
secrets: inherit
|
||||
52
.github/workflows/README.md
vendored
52
.github/workflows/README.md
vendored
@@ -1,52 +0,0 @@
|
||||
# Kubescape workflows
|
||||
|
||||
Tag terminology: `v<major>.<minor>.<patch>`
|
||||
|
||||
## Developing process
|
||||
|
||||
Kubescape's main branch is `main`, any PR will be opened against the main branch.
|
||||
|
||||
### Opening a PR
|
||||
|
||||
When a user opens a PR, this will trigger some basic tests (units, license, etc.)
|
||||
|
||||
### Reviewing a PR
|
||||
|
||||
The reviewer/maintainer of a PR will decide whether the PR introduces changes that require running the E2E system tests. If so, the reviewer will add the `trigger-integration-test` label.
|
||||
|
||||
### Approving a PR
|
||||
|
||||
Once a maintainer approves the PR, if the `trigger-integration-test` label was added to the PR, the GitHub actions will trigger the system test. The PR will be merged only after the system tests passed successfully. If the label was not added, the PR can be merged.
|
||||
|
||||
### Merging a PR
|
||||
|
||||
The code is merged, no other actions are needed
|
||||
|
||||
|
||||
## Release process
|
||||
|
||||
Every two weeks, we will create a new tag by bumping the minor version, this will create the release and publish the artifacts.
|
||||
If we are introducing breaking changes, we will update the `major` version instead.
|
||||
|
||||
When we wish to push a hot-fix/feature within the two weeks, we will bump the `patch`.
|
||||
|
||||
### Creating a new tag
|
||||
Every two weeks or upon the decision of the maintainers, a maintainer can create a tag.
|
||||
|
||||
The tag should look as follows: `v<A>.<B>.<C>-rc.D` (release candidate).
|
||||
|
||||
When creating a tag, GitHub will trigger the following actions:
|
||||
1. Basic tests - unit tests, license, etc.
|
||||
2. System tests (integration tests). If the tests fail, the actions will stop here.
|
||||
3. Create a new tag: `v<A>.<B>.<C>` (same tag just without the `rc` suffix)
|
||||
4. Create a release
|
||||
5. Publish artifacts
|
||||
6. Build and publish the docker image (this is meanwhile until we separate the microservice code from the LCI codebase)
|
||||
|
||||
## Additional Information
|
||||
|
||||
The "callers" have the alphabetic prefix and the "executes" have the numeric prefix
|
||||
|
||||
## Screenshot
|
||||
|
||||
<img width="1469" alt="image" src="https://user-images.githubusercontent.com/64066841/212532727-e82ec9e7-263d-408b-b4b0-a8c943f0109a.png">
|
||||
151
.github/workflows/a-pr-scanner.yaml
vendored
151
.github/workflows/a-pr-scanner.yaml
vendored
@@ -1,151 +0,0 @@
|
||||
name: a-pr-scanner
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
RELEASE:
|
||||
description: 'release'
|
||||
required: true
|
||||
type: string
|
||||
CLIENT:
|
||||
description: 'Client name'
|
||||
required: true
|
||||
type: string
|
||||
UNIT_TESTS_PATH:
|
||||
required: false
|
||||
type: string
|
||||
default: "./..."
|
||||
GO111MODULE:
|
||||
required: true
|
||||
type: string
|
||||
CGO_ENABLED:
|
||||
type: number
|
||||
default: 1
|
||||
jobs:
|
||||
unit-tests:
|
||||
if: ${{ github.actor != 'kubescape' }}
|
||||
name: Create cross-platform build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
runs-on: ubuntu22-core4-mem16-ssd150
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: ${{ inputs.GO_VERSION }}
|
||||
|
||||
- name: Test core pkg
|
||||
run: ${{ env.DOCKER_CMD }} go test -v ./...
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
|
||||
- name: Test httphandler pkg
|
||||
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test -v ./...'
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
|
||||
- uses: anchore/sbom-action/download-syft@v0.15.2
|
||||
name: Setup Syft
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v5
|
||||
name: Build
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean --snapshot
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
CLIENT: ${{ inputs.CLIENT }}
|
||||
CGO_ENABLED: ${{ inputs.CGO_ENABLED }}
|
||||
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ${PWD}/dist/kubescape-ubuntu-latest
|
||||
|
||||
- name: golangci-lint
|
||||
continue-on-error: false
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout 10m
|
||||
only-new-issues: true
|
||||
skip-pkg-cache: true
|
||||
skip-build-cache: true
|
||||
|
||||
scanners:
|
||||
env:
|
||||
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
name: PR Scanner
|
||||
runs-on: ubuntu22-core4-mem16-ssd150
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- uses: actions/setup-go@v4
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: "1.23"
|
||||
- name: Scanning - Forbidden Licenses (go-licenses)
|
||||
id: licenses-scan
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "## Installing go-licenses tool"
|
||||
go install github.com/google/go-licenses@latest
|
||||
echo "## Scanning for forbiden licenses ##"
|
||||
go-licenses check .
|
||||
- name: Scanning - Credentials (GitGuardian)
|
||||
if: ${{ env.GITGUARDIAN_API_KEY }}
|
||||
continue-on-error: true
|
||||
id: credentials-scan
|
||||
uses: GitGuardian/ggshield-action@master
|
||||
with:
|
||||
args: -v --all-policies
|
||||
env:
|
||||
GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }}
|
||||
GITHUB_PUSH_BASE_SHA: ${{ github.event.base }}
|
||||
GITHUB_PULL_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
|
||||
- name: Scanning - Vulnerabilities (Snyk)
|
||||
if: ${{ env.SNYK_TOKEN }}
|
||||
id: vulnerabilities-scan
|
||||
continue-on-error: true
|
||||
uses: snyk/actions/golang@master
|
||||
with:
|
||||
command: test --all-projects
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
|
||||
- name: Test coverage
|
||||
id: unit-test
|
||||
run: go test -v ${{ inputs.UNIT_TESTS_PATH }} -covermode=count -coverprofile=coverage.out
|
||||
|
||||
- name: Convert coverage count to lcov format
|
||||
uses: jandelgado/gcov2lcov-action@v1
|
||||
|
||||
- name: Submit coverage tests to Coveralls
|
||||
continue-on-error: true
|
||||
uses: coverallsapp/github-action@v1
|
||||
with:
|
||||
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
path-to-lcov: coverage.lcov
|
||||
|
||||
- name: Comment results to PR
|
||||
continue-on-error: true # Warning: This might break opening PRs from forks
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Scan results:
|
||||
- License scan: ${{ steps.licenses-scan.outcome }}
|
||||
- Credentials scan: ${{ steps.credentials-scan.outcome }}
|
||||
- Vulnerabilities scan: ${{ steps.vulnerabilities-scan.outcome }}
|
||||
reactions: 'eyes'
|
||||
359
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
359
.github/workflows/b-binary-build-and-e2e-tests.yaml
vendored
@@ -1,359 +0,0 @@
|
||||
name: b-binary-build-and-e2e-tests
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
COMPONENT_NAME:
|
||||
required: false
|
||||
type: string
|
||||
default: "kubescape"
|
||||
RELEASE:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
CLIENT:
|
||||
required: false
|
||||
type: string
|
||||
default: "test"
|
||||
GO_VERSION:
|
||||
required: false
|
||||
type: string
|
||||
default: "1.23"
|
||||
GO111MODULE:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
CGO_ENABLED:
|
||||
type: number
|
||||
default: 1
|
||||
required: false
|
||||
BINARY_TESTS:
|
||||
type: string
|
||||
required: false
|
||||
default: '[
|
||||
"ks_microservice_create_2_cronjob_mitre_and_nsa_proxy",
|
||||
"ks_microservice_triggering_with_cron_job",
|
||||
"ks_microservice_update_cronjob_schedule",
|
||||
"ks_microservice_delete_cronjob",
|
||||
"ks_microservice_create_2_cronjob_mitre_and_nsa",
|
||||
"ks_microservice_ns_creation",
|
||||
"ks_microservice_on_demand",
|
||||
"ks_microservice_mitre_framework_on_demand",
|
||||
"ks_microservice_nsa_and_mitre_framework_demand",
|
||||
"scan_nsa",
|
||||
"scan_mitre",
|
||||
"scan_with_exceptions",
|
||||
"scan_repository",
|
||||
"scan_local_file",
|
||||
"scan_local_glob_files",
|
||||
"scan_local_list_of_files",
|
||||
"scan_with_exception_to_backend",
|
||||
"scan_nsa_and_submit_to_backend",
|
||||
"scan_mitre_and_submit_to_backend",
|
||||
"scan_local_repository_and_submit_to_backend",
|
||||
"scan_repository_from_url_and_submit_to_backend",
|
||||
"scan_with_custom_framework",
|
||||
"scan_customer_configuration",
|
||||
"scan_compliance_score"
|
||||
]'
|
||||
|
||||
workflow_call:
|
||||
inputs:
|
||||
COMPONENT_NAME:
|
||||
required: true
|
||||
type: string
|
||||
RELEASE:
|
||||
required: true
|
||||
type: string
|
||||
CLIENT:
|
||||
required: true
|
||||
type: string
|
||||
GO_VERSION:
|
||||
type: string
|
||||
default: "1.23"
|
||||
GO111MODULE:
|
||||
required: true
|
||||
type: string
|
||||
CGO_ENABLED:
|
||||
type: number
|
||||
default: 1
|
||||
BINARY_TESTS:
|
||||
type: string
|
||||
default: '[
|
||||
"scan_nsa",
|
||||
"scan_mitre",
|
||||
"scan_with_exceptions",
|
||||
"scan_repository",
|
||||
"scan_local_file",
|
||||
"scan_local_glob_files",
|
||||
"scan_local_list_of_files",
|
||||
"scan_nsa_and_submit_to_backend",
|
||||
"scan_mitre_and_submit_to_backend",
|
||||
"scan_local_repository_and_submit_to_backend",
|
||||
"scan_repository_from_url_and_submit_to_backend",
|
||||
"scan_with_custom_framework",
|
||||
"scan_customer_configuration",
|
||||
"scan_compliance_score",
|
||||
"scan_custom_framework_scanning_file_scope_testing",
|
||||
"scan_custom_framework_scanning_cluster_scope_testing",
|
||||
"scan_custom_framework_scanning_cluster_and_file_scope_testing"
|
||||
]'
|
||||
|
||||
jobs:
|
||||
wf-preparation:
|
||||
name: secret-validator
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }}
|
||||
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
|
||||
|
||||
steps:
|
||||
- name: check if the necessary secrets are set in github secrets
|
||||
id: check-secret-set
|
||||
env:
|
||||
CUSTOMER: ${{ secrets.CUSTOMER }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
CLIENT_ID: ${{ secrets.CLIENT_ID_PROD }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY_PROD }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: "echo \"is-secret-set=${{ env.CUSTOMER != '' && env.USERNAME != '' && env.PASSWORD != '' && env.CLIENT_ID != '' && env.SECRET_KEY != '' && env.REGISTRY_USERNAME != '' && env.REGISTRY_PASSWORD != '' }}\" >> $GITHUB_OUTPUT\n"
|
||||
|
||||
- id: export_tests_to_env
|
||||
name: set test name
|
||||
run: |
|
||||
echo "TEST_NAMES=$input" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
input: ${{ inputs.BINARY_TESTS }}
|
||||
|
||||
check-secret:
|
||||
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
|
||||
steps:
|
||||
- name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
id: check-secret-set
|
||||
env:
|
||||
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
|
||||
|
||||
binary-build:
|
||||
name: Create cross-platform build
|
||||
needs: wf-preparation
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
runs-on: ubuntu-large
|
||||
steps:
|
||||
- name: (debug) Step 1 - Check disk space before checkout
|
||||
run: df -h
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: (debug) Step 2 - Check disk space before installing Go
|
||||
run: df -h
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
name: Installing go
|
||||
with:
|
||||
go-version: ${{ inputs.GO_VERSION }}
|
||||
|
||||
- name: (debug) Step 3 - Check disk space before build
|
||||
run: df -h
|
||||
|
||||
- name: Test core pkg
|
||||
run: ${{ env.DOCKER_CMD }} go test -v ./...
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
|
||||
- name: (debug) Step 4 - Check disk space before testing httphandler pkg
|
||||
run: df -h
|
||||
|
||||
- name: Test httphandler pkg
|
||||
run: ${{ env.DOCKER_CMD }} sh -c 'cd httphandler && go test -v ./...'
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
|
||||
- name: (debug) Step 5 - Check disk space before setting up Syft
|
||||
run: df -h
|
||||
|
||||
- uses: anchore/sbom-action/download-syft@v0
|
||||
name: Setup Syft
|
||||
|
||||
- name: (debug) Step 6 - Check disk space before goreleaser
|
||||
run: df -h
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v5
|
||||
name: Build
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean --snapshot
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
CLIENT: ${{ inputs.CLIENT }}
|
||||
CGO_ENABLED: ${{ inputs.CGO_ENABLED }}
|
||||
|
||||
- name: (debug) Step 7 - Check disk space before smoke testing
|
||||
run: df -h
|
||||
|
||||
- name: Smoke Testing
|
||||
env:
|
||||
RELEASE: ${{ inputs.RELEASE }}
|
||||
KUBESCAPE_SKIP_UPDATE_CHECK: "true"
|
||||
run: ${{ env.DOCKER_CMD }} python3 smoke_testing/init.py ${PWD}/dist/kubescape-ubuntu-latest
|
||||
|
||||
- name: (debug) Step 8 - Check disk space before golangci-lint
|
||||
run: df -h
|
||||
|
||||
- name: golangci-lint
|
||||
continue-on-error: true
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout 10m
|
||||
only-new-issues: true
|
||||
skip-pkg-cache: true
|
||||
skip-build-cache: true
|
||||
|
||||
- name: (debug) Step 9 - Check disk space before uploading artifacts
|
||||
run: df -h
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: Upload artifacts
|
||||
with:
|
||||
name: kubescape
|
||||
path: dist/kubescape*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: (debug) Step 10 - Check disk space after uploading artifacts
|
||||
run: df -h
|
||||
|
||||
build-http-image:
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
packages: write
|
||||
pull-requests: read
|
||||
needs: [check-secret]
|
||||
uses: kubescape/workflows/.github/workflows/incluster-comp-pr-merged.yaml@main
|
||||
with:
|
||||
IMAGE_NAME: quay.io/${{ github.repository_owner }}/kubescape
|
||||
IMAGE_TAG: ${{ inputs.RELEASE }}
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: "on"
|
||||
BUILD_PLATFORM: linux/amd64,linux/arm64
|
||||
GO_VERSION: "1.23"
|
||||
REQUIRED_TESTS: '[
|
||||
"ks_microservice_create_2_cronjob_mitre_and_nsa_proxy",
|
||||
"ks_microservice_triggering_with_cron_job",
|
||||
"ks_microservice_update_cronjob_schedule",
|
||||
"ks_microservice_delete_cronjob",
|
||||
"ks_microservice_create_2_cronjob_mitre_and_nsa",
|
||||
"ks_microservice_ns_creation",
|
||||
"ks_microservice_on_demand",
|
||||
"ks_microservice_mitre_framework_on_demand",
|
||||
"ks_microservice_nsa_and_mitre_framework_demand",
|
||||
"scan_nsa",
|
||||
"scan_mitre",
|
||||
"scan_with_exceptions",
|
||||
"scan_repository",
|
||||
"scan_local_file",
|
||||
"scan_local_glob_files",
|
||||
"scan_local_list_of_files",
|
||||
"scan_with_exception_to_backend",
|
||||
"scan_nsa_and_submit_to_backend",
|
||||
"scan_mitre_and_submit_to_backend",
|
||||
"scan_local_repository_and_submit_to_backend",
|
||||
"scan_repository_from_url_and_submit_to_backend",
|
||||
"scan_with_custom_framework",
|
||||
"scan_customer_configuration",
|
||||
"scan_compliance_score"
|
||||
]'
|
||||
COSIGN: true
|
||||
HELM_E2E_TEST: true
|
||||
FORCE: true
|
||||
secrets: inherit
|
||||
|
||||
run-tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
TEST: ${{ fromJson(needs.wf-preparation.outputs.TEST_NAMES) }}
|
||||
needs: [wf-preparation, binary-build]
|
||||
if: ${{ (needs.wf-preparation.outputs.is-secret-set == 'true') && (always() && (contains(needs.*.result, 'success') || contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'cancelled'))) }}
|
||||
runs-on: ubuntu-latest # This cannot change
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
id: download-artifact
|
||||
with:
|
||||
name: kubescape
|
||||
path: "~"
|
||||
|
||||
- run: ls -laR
|
||||
|
||||
- name: chmod +x
|
||||
run: chmod +x -R ${{steps.download-artifact.outputs.download-path}}/kubescape-ubuntu-latest
|
||||
|
||||
- name: Checkout systests repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: armosec/system-tests
|
||||
path: .
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.8.13'
|
||||
cache: 'pip'
|
||||
|
||||
- name: create env
|
||||
run: ./create_env.sh
|
||||
|
||||
- name: Generate uuid
|
||||
id: uuid
|
||||
run: |
|
||||
echo "RANDOM_UUID=$(uuidgen)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create k8s Kind Cluster
|
||||
id: kind-cluster-install
|
||||
uses: helm/kind-action@v1.10.0
|
||||
with:
|
||||
cluster_name: ${{ steps.uuid.outputs.RANDOM_UUID }}
|
||||
|
||||
- name: run-tests-on-local-built-kubescape
|
||||
env:
|
||||
CUSTOMER: ${{ secrets.CUSTOMER }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
CLIENT_ID: ${{ secrets.CLIENT_ID_PROD }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY_PROD }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "Test history:"
|
||||
echo " ${{ matrix.TEST }} " >/tmp/testhistory
|
||||
cat /tmp/testhistory
|
||||
source systests_python_env/bin/activate
|
||||
|
||||
python3 systest-cli.py \
|
||||
-t ${{ matrix.TEST }} \
|
||||
-b production \
|
||||
-c CyberArmorTests \
|
||||
--duration 3 \
|
||||
--logger DEBUG \
|
||||
--kwargs kubescape=${{steps.download-artifact.outputs.download-path}}/kubescape-ubuntu-latest
|
||||
|
||||
deactivate
|
||||
|
||||
- name: Test Report
|
||||
uses: mikepenz/action-junit-report@v5
|
||||
if: always() # always run even if the previous step fails
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
report_paths: '**/results_xml_format/**.xml'
|
||||
commit: ${{github.event.workflow_run.head_sha}}
|
||||
41
.github/workflows/build-image.yaml
vendored
41
.github/workflows/build-image.yaml
vendored
@@ -1,41 +0,0 @@
|
||||
name: build-image
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
CLIENT:
|
||||
required: false
|
||||
type: string
|
||||
default: "test"
|
||||
IMAGE_TAG:
|
||||
required: true
|
||||
type: string
|
||||
CO_SIGN:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
PLATFORMS:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
jobs:
|
||||
build-http-image:
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
contents: write
|
||||
pull-requests: read
|
||||
uses: kubescape/workflows/.github/workflows/incluster-comp-pr-merged.yaml@main
|
||||
with:
|
||||
IMAGE_NAME: quay.io/${{ github.repository_owner }}/kubescape
|
||||
IMAGE_TAG: ${{ inputs.IMAGE_TAG }}
|
||||
COMPONENT_NAME: kubescape
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: "on"
|
||||
BUILD_PLATFORM: ${{ inputs.PLATFORMS && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
|
||||
GO_VERSION: "1.23"
|
||||
REQUIRED_TESTS: '[]'
|
||||
COSIGN: ${{ inputs.CO_SIGN }}
|
||||
HELM_E2E_TEST: false
|
||||
FORCE: true
|
||||
secrets: inherit
|
||||
57
.github/workflows/build.yaml
vendored
Normal file
57
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
jobs:
|
||||
once:
|
||||
name: Create release
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- name: Create a release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: v1.0.${{ github.run_number }}
|
||||
release_name: Release v1.0.${{ github.run_number }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
build:
|
||||
name: Create cross-platform release build, tag and upload binaries
|
||||
needs: once
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v1.0.${{ github.run_number }}
|
||||
ArmoBEServer: api.armo.cloud
|
||||
ArmoERServer: report.armo.cloud
|
||||
ArmoWebsite: portal.armo.cloud
|
||||
CGO_ENABLED: 0
|
||||
run: python build.py
|
||||
|
||||
- name: Upload Release binaries
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.once.outputs.upload_url }}
|
||||
asset_path: build/${{ matrix.os }}/kubescape
|
||||
asset_name: kubescape-${{ matrix.os }}
|
||||
asset_content_type: application/octet-stream
|
||||
36
.github/workflows/build_dev.yaml
vendored
Normal file
36
.github/workflows/build_dev.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: build-dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
pull_request:
|
||||
branches: [ dev ]
|
||||
types: [ closed ]
|
||||
jobs:
|
||||
build:
|
||||
name: Create cross-platform dev build
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE: v1.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
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: kubescape-${{ matrix.os }}
|
||||
path: build/${{ matrix.os }}/kubescape
|
||||
92
.github/workflows/c-create-release.yaml
vendored
92
.github/workflows/c-create-release.yaml
vendored
@@ -1,92 +0,0 @@
|
||||
name: c-create_release
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
RELEASE_NAME:
|
||||
description: 'Release name'
|
||||
required: true
|
||||
type: string
|
||||
TAG:
|
||||
description: 'Tag name'
|
||||
required: true
|
||||
type: string
|
||||
DRAFT:
|
||||
description: 'Create draft release'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
jobs:
|
||||
create-release:
|
||||
name: create-release
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MAC_OS: macos-latest
|
||||
UBUNTU_OS: ubuntu-latest
|
||||
WINDOWS_OS: windows-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
id: download-artifact
|
||||
with:
|
||||
name: kubescape
|
||||
path: .
|
||||
|
||||
# TODO: kubescape-windows-latest is deprecated and should be removed
|
||||
- name: Get kubescape.exe from kubescape-windows-latest.exe
|
||||
run: cp ${{steps.download-artifact.outputs.download-path}}/kubescape-${{ env.WINDOWS_OS }}.exe ${{steps.download-artifact.outputs.download-path}}/kubescape.exe
|
||||
|
||||
- name: Set release token
|
||||
id: set-token
|
||||
run: |
|
||||
if [ "${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" != "" ]; then
|
||||
echo "token=${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}" >> $GITHUB_OUTPUT;
|
||||
else
|
||||
echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT;
|
||||
fi
|
||||
|
||||
- name: List artifacts
|
||||
run: |
|
||||
find . -type f -print
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
token: ${{ steps.set-token.outputs.token }}
|
||||
name: ${{ inputs.RELEASE_NAME }}
|
||||
tag_name: ${{ inputs.TAG }}
|
||||
body: ${{ github.event.pull_request.body }}
|
||||
draft: ${{ inputs.DRAFT }}
|
||||
prerelease: false
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
./kubescape-${{ env.MAC_OS }}
|
||||
./kubescape-${{ env.MAC_OS }}.sbom
|
||||
./kubescape-${{ env.MAC_OS }}.sha256
|
||||
./kubescape-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescape-${{ env.UBUNTU_OS }}
|
||||
./kubescape-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape-${{ env.WINDOWS_OS }}.exe
|
||||
./kubescape-${{ env.WINDOWS_OS }}.exe.sbom
|
||||
./kubescape-${{ env.WINDOWS_OS }}.exe.sha256
|
||||
./kubescape-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescape-arm64-${{ env.MAC_OS }}
|
||||
./kubescape-arm64-${{ env.MAC_OS }}.sbom
|
||||
./kubescape-arm64-${{ env.MAC_OS }}.sha256
|
||||
./kubescape-arm64-${{ env.MAC_OS }}.tar.gz
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape-arm64-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.exe
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.exe.sbom
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.exe.sha256
|
||||
./kubescape-arm64-${{ env.WINDOWS_OS }}.tar.gz
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}.sbom
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}.sha256
|
||||
./kubescape-riscv64-${{ env.UBUNTU_OS }}.tar.gz
|
||||
./kubescape.exe
|
||||
20
.github/workflows/comments.yaml
vendored
20
.github/workflows/comments.yaml
vendored
@@ -1,20 +0,0 @@
|
||||
name: pr-agent
|
||||
permissions: read-all
|
||||
on:
|
||||
issue_comment:
|
||||
|
||||
jobs:
|
||||
pr_agent:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
name: Run pr agent on every pull request, respond to user comments
|
||||
steps:
|
||||
- name: PR Agent action step
|
||||
continue-on-error: true
|
||||
id: pragent
|
||||
uses: Codium-ai/pr-agent@main
|
||||
env:
|
||||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
107
.github/workflows/d-publish-image.yaml
vendored
107
.github/workflows/d-publish-image.yaml
vendored
@@ -1,107 +0,0 @@
|
||||
name: d-publish-image
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: write
|
||||
deployments: read
|
||||
discussions: read
|
||||
id-token: write
|
||||
issues: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: read
|
||||
repository-projects: read
|
||||
statuses: read
|
||||
security-events: read
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
client:
|
||||
description: 'client name'
|
||||
required: true
|
||||
type: string
|
||||
image_tag:
|
||||
description: 'image tag'
|
||||
required: true
|
||||
type: string
|
||||
image_name:
|
||||
description: 'image registry and name'
|
||||
required: true
|
||||
type: string
|
||||
cosign:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
description: 'run cosign on released image'
|
||||
support_platforms:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
description: 'support amd64/arm64'
|
||||
jobs:
|
||||
check-secret:
|
||||
name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is-secret-set: ${{ steps.check-secret-set.outputs.is-secret-set }}
|
||||
steps:
|
||||
- name: check if QUAYIO_REGISTRY_USERNAME & QUAYIO_REGISTRY_PASSWORD is set in github secrets
|
||||
id: check-secret-set
|
||||
env:
|
||||
QUAYIO_REGISTRY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
QUAYIO_REGISTRY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "is-secret-set=${{ env.QUAYIO_REGISTRY_USERNAME != '' && env.QUAYIO_REGISTRY_PASSWORD != '' }}" >> $GITHUB_OUTPUT
|
||||
|
||||
build-cli-image:
|
||||
needs: [check-secret]
|
||||
if: needs.check-secret.outputs.is-secret-set == 'true'
|
||||
name: Build image and upload to registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to Quay.io
|
||||
env:
|
||||
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
|
||||
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
|
||||
- uses: actions/download-artifact@v4
|
||||
id: download-artifact
|
||||
with:
|
||||
name: kubescape
|
||||
path: .
|
||||
- name: mv kubescape amd64 binary
|
||||
run: mv kubescape-ubuntu-latest kubescape-amd64-ubuntu-latest
|
||||
- name: chmod +x
|
||||
run: chmod +x -v kubescape-a*
|
||||
- name: Build and push images
|
||||
run: docker buildx build . --file build/kubescape-cli.Dockerfile --tag ${{ inputs.image_name }}:${{ inputs.image_tag }} --tag ${{ inputs.image_name }}:latest --build-arg image_version=${{ inputs.image_tag }} --build-arg client=${{ inputs.client }} --push --platform linux/amd64,linux/arm64
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@main
|
||||
with:
|
||||
cosign-release: 'v2.2.2'
|
||||
- name: sign kubescape container image
|
||||
if: ${{ inputs.cosign }}
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: "true"
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY_V1 }}
|
||||
COSIGN_PRIVATE_KEY_PASSWORD: ${{ secrets.COSIGN_PRIVATE_KEY_V1_PASSWORD }}
|
||||
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY_V1 }}
|
||||
run: |
|
||||
# Sign the image with keyless mode
|
||||
cosign sign -y ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||
|
||||
# Sign the image with key for verifier clients without keyless support
|
||||
# Put the key from environment variable to a file
|
||||
echo "$COSIGN_PRIVATE_KEY" > cosign.key
|
||||
printf "$COSIGN_PRIVATE_KEY_PASSWORD" | cosign sign -key cosign.key -y ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||
rm cosign.key
|
||||
# Verify the image
|
||||
echo "$COSIGN_PUBLIC_KEY" > cosign.pub
|
||||
cosign verify -key cosign.pub ${{ inputs.image_name }}:${{ inputs.image_tag }}
|
||||
46
.github/workflows/e-post-release.yaml
vendored
46
.github/workflows/e-post-release.yaml
vendored
@@ -1,46 +0,0 @@
|
||||
name: e-post_release
|
||||
permissions: read-all
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
TAG:
|
||||
description: 'Tag name'
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
post_release:
|
||||
name: Post release jobs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update new version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@v0.0.47
|
||||
if: github.repository_owner == 'kubescape'
|
||||
env:
|
||||
GITHUB_REF: ${{ inputs.TAG }}
|
||||
- name: Invoke workflow to update packaging
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
if: github.repository_owner == 'kubescape'
|
||||
with:
|
||||
workflow: release.yml
|
||||
repo: kubescape/packaging
|
||||
ref: refs/heads/main
|
||||
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
- name: Invoke workflow to update homebrew tap
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
if: github.repository_owner == 'kubescape'
|
||||
with:
|
||||
workflow: release.yml
|
||||
repo: kubescape/homebrew-tap
|
||||
ref: refs/heads/main
|
||||
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
- name: Invoke workflow to update github action
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
if: github.repository_owner == 'kubescape'
|
||||
with:
|
||||
workflow: release.yaml
|
||||
repo: kubescape/github-action
|
||||
ref: refs/heads/main
|
||||
token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
72
.github/workflows/scorecard.yml
vendored
72
.github/workflows/scorecard.yml
vendored
@@ -1,72 +0,0 @@
|
||||
# This workflow uses actions that are not certified by GitHub. They are provided
|
||||
# by a third-party and are governed by separate terms of service, privacy
|
||||
# policy, and support documentation.
|
||||
|
||||
name: Scorecard supply-chain security
|
||||
on:
|
||||
# For Branch-Protection check. Only the default branch is supported. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||
branch_protection_rule:
|
||||
# To guarantee Maintained check is occasionally updated. See
|
||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||
schedule:
|
||||
- cron: '0 00 * * 1'
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
# Needed to publish results and get a badge (see publish_results below).
|
||||
id-token: write
|
||||
# Uncomment the permissions below if installing in a private repository.
|
||||
# contents: read
|
||||
# actions: read
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@v2.4.0
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||
# - you are installing Scorecard on a *private* repository
|
||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
|
||||
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||
|
||||
# Public repositories:
|
||||
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||
# - Allows the repository to include the Scorecard badge.
|
||||
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories:
|
||||
# - `publish_results` will always be set to `false`, regardless
|
||||
# of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
20
.github/workflows/z-close-typos-issues.yaml
vendored
20
.github/workflows/z-close-typos-issues.yaml
vendored
@@ -1,20 +0,0 @@
|
||||
permissions: read-all
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
jobs:
|
||||
open_PR_message:
|
||||
if: github.event.label.name == 'typo'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: ben-z/actions-comment-on-issue@1.0.2
|
||||
with:
|
||||
message: "Hello! :wave:\n\nThis issue is being automatically closed, Please open a PR with a relevant fix."
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto_close_issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lee-dohm/close-matching-issues@v2
|
||||
with:
|
||||
query: 'label:typo'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,13 +1,4 @@
|
||||
*.vs*
|
||||
*kubescape*
|
||||
!*Dockerfile*
|
||||
*debug*
|
||||
*vendor*
|
||||
*.pyc*
|
||||
.idea
|
||||
.history
|
||||
ca.srl
|
||||
*.out
|
||||
ks
|
||||
|
||||
dist/
|
||||
.idea
|
||||
@@ -1,51 +0,0 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
shadow: true
|
||||
dupl:
|
||||
threshold: 200
|
||||
goconst:
|
||||
min-len: 3
|
||||
min-occurrences: 2
|
||||
gocognit:
|
||||
min-complexity: 65
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gosec
|
||||
- staticcheck
|
||||
- nolintlint
|
||||
- gofmt
|
||||
- unused
|
||||
- govet
|
||||
- bodyclose
|
||||
- typecheck
|
||||
- goimports
|
||||
- ineffassign
|
||||
- gosimple
|
||||
disable:
|
||||
# temporarily disabled
|
||||
- errcheck
|
||||
- dupl
|
||||
- gocritic
|
||||
- gocognit
|
||||
- nakedret
|
||||
- revive
|
||||
- stylecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
#- forbidigo # <- see later
|
||||
# should remain disabled
|
||||
- lll
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
issues:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- revive
|
||||
text: "var-naming"
|
||||
- linters:
|
||||
- revive
|
||||
text: "type name will be used as (.+?) by other packages, and that stutters"
|
||||
- linters:
|
||||
- stylecheck
|
||||
text: "ST1003"
|
||||
@@ -1,60 +0,0 @@
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
|
||||
# The lines bellow are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/need to use them.
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod tidy
|
||||
|
||||
builds:
|
||||
- goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- riscv64
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X "github.com/kubescape/kubescape/v3/core/cautils.BuildNumber={{.Env.RELEASE}}"
|
||||
- -X "github.com/kubescape/kubescape/v3/core/cautils.Client={{.Env.CLIENT}}"
|
||||
binary: >-
|
||||
{{ .ProjectName }}-
|
||||
{{- if eq .Arch "amd64" }}
|
||||
{{- else }}{{ .Arch }}-{{ end }}
|
||||
{{- if eq .Os "darwin" }}macos
|
||||
{{- else if eq .Os "linux" }}ubuntu
|
||||
{{- else }}{{ .Os }}{{ end }}-latest
|
||||
no_unique_dist_dir: true
|
||||
|
||||
archives:
|
||||
- format: binary
|
||||
id: binaries
|
||||
name_template: >-
|
||||
{{ .Binary }}
|
||||
- format: tar.gz
|
||||
name_template: >-
|
||||
{{ .Binary }}
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
|
||||
checksum:
|
||||
ids:
|
||||
- binaries
|
||||
split: true
|
||||
|
||||
sboms:
|
||||
- artifacts: binary
|
||||
documents:
|
||||
- "{{ .Binary }}.sbom"
|
||||
42
.krew.yaml
42
.krew.yaml
@@ -1,42 +0,0 @@
|
||||
apiVersion: krew.googlecontainertools.github.com/v1alpha2
|
||||
kind: Plugin
|
||||
metadata:
|
||||
name: kubescape
|
||||
spec:
|
||||
homepage: https://github.com/kubescape/kubescape/
|
||||
shortDescription: Scan resources and cluster configs against security frameworks.
|
||||
version: {{ .TagName }}
|
||||
description: |
|
||||
It includes risk analysis, security compliance, and misconfiguration scanning
|
||||
with an easy-to-use CLI interface, flexible output formats, and automated scanning capabilities.
|
||||
platforms:
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-macos-latest.tar.gz" .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: arm64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-arm64-macos-latest.tar.gz" .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-ubuntu-latest.tar.gz" .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: arm64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-arm64-ubuntu-latest.tar.gz" .TagName }}
|
||||
bin: kubescape
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: amd64
|
||||
{{ addURIAndSha "https://github.com/kubescape/kubescape/releases/download/{{ .TagName }}/kubescape-windows-latest.tar.gz" .TagName }}
|
||||
bin: kubescape.exe
|
||||
@@ -1,5 +0,0 @@
|
||||
# Adopters
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized ADOPTERS.md](https://github.com/kubescape/project-governance/blob/main/ADOPTERS.md)
|
||||
@@ -1,5 +0,0 @@
|
||||
# Code of Conduct
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized CODE_OF_CONDUCT.md](https://github.com/kubescape/project-governance/blob/main/CODE_OF_CONDUCT.md)
|
||||
@@ -1,5 +0,0 @@
|
||||
# Community
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized COMMUNITY.md](https://github.com/kubescape/project-governance/blob/main/COMMUNITY.md)
|
||||
@@ -1,5 +1,99 @@
|
||||
# Contributing
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
First, it is awesome that you are considering contributing to Kubescape! Contributing is important and fun and we welcome your efforts.
|
||||
|
||||
Go to the [centralized CONTRIBUTING.md](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md)
|
||||
When contributing, we categorize contributions into two:
|
||||
* Small code changes or fixes, whose scope are limited to a single or two files
|
||||
* Complex features and improvements, whose are not limited
|
||||
|
||||
If you have a small change, feel free to fire up a Pull Request.
|
||||
|
||||
When planning a bigger change, please first discuss the change you wish to make via issue,
|
||||
email, or any other method with the owners of this repository before making a change. Most likely your changes or features are great, but sometimes we might already going to this direction (or the exact opposite ;-) ) and we don't want to waste your time.
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
|
||||
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.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
We will distance those who are constantly adhere to unacceptable behavior.
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers 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, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -1,5 +0,0 @@
|
||||
# Governance
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized GOVERNANCE.md](https://github.com/kubescape/project-governance/blob/main/GOVERNANCE.md)
|
||||
@@ -1,5 +0,0 @@
|
||||
# Maintainers
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized MAINTAINERS.md](https://github.com/kubescape/project-governance/blob/main/MAINTAINERS.md)
|
||||
12
Makefile
12
Makefile
@@ -1,12 +0,0 @@
|
||||
.PHONY: test all build
|
||||
|
||||
# default task invoked while running make
|
||||
all: build
|
||||
|
||||
export CGO_ENABLED=0
|
||||
|
||||
build:
|
||||
go build -v .
|
||||
|
||||
test:
|
||||
go test -v ./...
|
||||
286
README.md
286
README.md
@@ -1,121 +1,239 @@
|
||||
[](https://github.com/kubescape/kubescape/releases)
|
||||
[](https://github.com/kubescape/kubescape/actions/workflows/02-release.yaml)
|
||||
[](https://goreportcard.com/report/github.com/kubescape/kubescape)
|
||||
[](https://gitpod.io/#https://github.com/kubescape/kubescape)
|
||||
[](https://github.com/kubescape/kubescape/blob/master/LICENSE)
|
||||
[](https://landscape.cncf.io/?item=provisioning--security-compliance--kubescape)
|
||||
[](https://artifacthub.io/packages/search?repo=kubescape)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fkubescape%2Fkubescape?ref=badge_shield&issueType=license)
|
||||
[](https://www.bestpractices.dev/projects/6944)
|
||||
[](https://securityscorecards.dev/viewer/?uri=github.com/kubescape/kubescape)
|
||||
[](https://github.com/kubescape/kubescape/stargazers)
|
||||
[](https://twitter.com/kubescape)
|
||||
[](https://cloud-native.slack.com/archives/C04EY3ZF9GE)
|
||||
<img src="docs/kubescape.png" width="300" alt="logo" align="center">
|
||||
|
||||
# Kubescape
|
||||
[](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
|
||||
[](https://goreportcard.com/report/github.com/armosec/kubescape)
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubescape/stacked/white/kubescape-stacked-white.svg" width="150">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubescape/stacked/color/kubescape-stacked-color.svg" width="150">
|
||||
<img alt="Kubescape logo" align="right" src="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubescape/stacked/color/kubescape-stacked-color.svg" width="150">
|
||||
</picture>
|
||||
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/)
|
||||
|
||||
_Comprehensive Kubernetes Security from Development to Runtime_
|
||||
Use Kubescape to test clusters or scan single YAML files and integrate it to your processes.
|
||||
|
||||
Kubescape is an open-source Kubernetes security platform that provides comprehensive security coverage, from left to right across the entire development and deployment lifecycle. It offers hardening, posture management, and runtime security capabilities to ensure robust protection for Kubernetes environments. It saves Kubernetes users and admins precious time, effort, and resources.
|
||||
<img src="docs/demo.gif">
|
||||
|
||||
Kubescape scans clusters, YAML files, and Helm charts. It detects misconfigurations according to multiple frameworks (including [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo/?utm_source=github&utm_medium=repository), [MITRE ATT&CK®](https://www.armosec.io/glossary/mitre-attck-framework/?utm_source=github&utm_medium=repository) and the [CIS Benchmark](https://www.armosec.io/blog/cis-kubernetes-benchmark-framework-scanning-tools-comparison/?utm_source=github&utm_medium=repository)).
|
||||
|
||||
Kubescape was created by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository) and is a [Cloud Native Computing Foundation (CNCF) incubating project](https://www.cncf.io/projects/).
|
||||
|
||||
_Please [star ⭐](https://github.com/kubescape/kubescape/stargazers) the repo if you want us to continue developing and improving Kubescape! 😀_
|
||||
|
||||
## Demo
|
||||
|
||||
Kubescape has a command line tool that you can use to quickly get a report on the security posture of a Kubernetes cluster:
|
||||
|
||||
<img src="docs/img/demo-v3.gif">
|
||||
|
||||
## Getting started
|
||||
|
||||
Experimenting with Kubescape is as easy as:
|
||||
|
||||
```sh
|
||||
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
|
||||
# TL;DR
|
||||
## Install:
|
||||
```
|
||||
curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh | /bin/bash
|
||||
```
|
||||
|
||||
This script will automatically download the latest Kubescape CLI release and scan the Kubernetes cluster in your current kubectl context.
|
||||
[Install on windows](#install-on-windows)
|
||||
|
||||
Learn more about:
|
||||
[Install on macOS](#install-on-macos)
|
||||
|
||||
* [Installing the Kubescape CLI](https://kubescape.io/docs/install-cli/)
|
||||
* [Running your first scan](https://kubescape.io/docs/scanning/)
|
||||
* [Accepting risk with exceptions](https://kubescape.io/docs/accepting-risk/)
|
||||
## Run:
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
```
|
||||
|
||||
_Did you know you can use Kubescape in all these places?_
|
||||
If you wish to scan all namespaces in your cluster, remove the `--exclude-namespaces` flag.
|
||||
|
||||
<div align="center">
|
||||
<img src="docs/img/ksfromcodetodeploy.png" alt="Places you can use Kubescape: in your IDE, CI, CD, or against a running cluster.">
|
||||
</div>
|
||||
<img src="docs/summary.png">
|
||||
|
||||
### Continuous security monitoring with the Kubescape Operator
|
||||
### Click [👍](https://github.com/armosec/kubescape/stargazers) if you want us to continue to develop and improve Kubescape 😀
|
||||
|
||||
As well as a CLI, Kubescape provides an in-cluster mode, which is installed via a Helm chart. Kubescape in-cluster provides extensive features such as continuous scanning, image vulnerability scanning, runtime analysis, network policy generation, and more. [Learn more about the Kubescape operator](https://kubescape.io/docs/operator/).
|
||||
# Being part of the team
|
||||
|
||||
### Using Kubescape as a GitHub Action
|
||||
We invite you to our team! We are excited about this project and want to return the love we get.
|
||||
|
||||
Kubescape can be used as a GitHub Action. This is a great way to integrate Kubescape into your CI/CD pipeline. You can find the Kubescape GitHub Action in the [GitHub Action marketplace](https://github.com/marketplace/actions/kubescape).
|
||||
Want to contribute? Want to discuss something? Have an issue?
|
||||
|
||||
## Under the hood
|
||||
* Open a issue, we are trying to respond within 48 hours
|
||||
* [Join us](https://armosec.github.io/kubescape/) in a discussion on our discord server!
|
||||
|
||||
Kubescape uses [Open Policy Agent](https://github.com/open-policy-agent/opa) to verify Kubernetes objects against [a library of posture controls](https://github.com/kubescape/regolibrary).
|
||||
For image scanning, it uses [Grype](https://github.com/anchore/grype).
|
||||
For image patching, it uses [Copacetic](https://github.com/project-copacetic/copacetic).
|
||||
For eBPF, it uses [Inspektor Gadget](https://github.com/inspektor-gadget)
|
||||
[<img src="docs/discord-banner.png" width="100" alt="logo" align="center">](https://armosec.github.io/kubescape/)
|
||||
|
||||
By default, CLI scan results are printed in a console-friendly manner, but they can be:
|
||||
# Options and examples
|
||||
|
||||
* exported to JSON, junit XML or SARIF
|
||||
* rendered to HTML or PDF
|
||||
* submitted to a [cloud service](docs/providers.md)
|
||||
## Install on Windows
|
||||
|
||||
### In-cluster architecture
|
||||
**Requires powershell v5.0+**
|
||||
|
||||

|
||||
``` powershell
|
||||
iwr -useb https://raw.githubusercontent.com/armosec/kubescape/master/install.ps1 | iex
|
||||
```
|
||||
|
||||
## Community
|
||||
Note: if you get an error you might need to change the execution policy (i.e. enable Powershell) with
|
||||
|
||||
Kubescape is an open source project. We welcome your feedback and ideas for improvement. We are part of the CNCF community and are evolving Kubescape in sync with the security needs of Kubernetes users. To learn more about where Kubescape is heading, please check out our [ROADMAP](https://github.com/kubescape/project-governance/blob/main/ROADMAP.md).
|
||||
``` powershell
|
||||
Set-ExecutionPolicy RemoteSigned -scope CurrentUser
|
||||
```
|
||||
|
||||
If you feel inspired to contribute to Kubescape, check out our [CONTRIBUTING](https://github.com/kubescape/project-governance/blob/main/CONTRIBUTING.md) file to learn how. You can find the issues we are working on (triage to development) on the [Kubescaping board](https://github.com/orgs/kubescape/projects/4/views/1)
|
||||
## Install on macOS
|
||||
```
|
||||
brew tap armosec/kubescape
|
||||
brew install kubescape
|
||||
```
|
||||
|
||||
* Feel free to pick a task from the [board](https://github.com/orgs/kubescape/projects/4) or suggest a feature of your own.
|
||||
* Open an issue on the board. We aim to respond to all issues within 48 hours.
|
||||
* [Join the CNCF Slack](https://slack.cncf.io/) and then our [users](https://cloud-native.slack.com/archives/C04EY3ZF9GE) or [developers](https://cloud-native.slack.com/archives/C04GY6H082K) channel.
|
||||
## Flags
|
||||
|
||||
The Kubescape project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
| 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 |
|
||||
| `--results-locally` | `false` | Kubescape sends scan results to Armo management portal to allow users to control exceptions and maintain chronological scan results. Use this flag if you do not wish to use these features | `true`/`false`|
|
||||
|
||||
For more information about the Kubescape community, please visit [COMMUNITY](https://github.com/kubescape/project-governance/blob/main/COMMUNITY.md).
|
||||
## 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
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
```
|
||||
|
||||
|
||||
We would like to take this opportunity to thank all our contibutors to date.
|
||||
* Scan a running Kubernetes cluster with [`mitre`](https://www.microsoft.com/security/blog/2020/04/02/attack-matrix-kubernetes/) framework
|
||||
```
|
||||
kubescape scan framework mitre --exclude-namespaces kube-system,kube-public
|
||||
```
|
||||
|
||||
<br>
|
||||
* Scan local `yaml`/`json` files before deploying
|
||||
```
|
||||
kubescape scan framework nsa *.yaml
|
||||
```
|
||||
|
||||
<a href = "https://github.com/kubescape/kubescape/graphs/contributors">
|
||||
<img src = "https://contrib.rocks/image?repo=kubescape/kubescape"/>
|
||||
</a>
|
||||
|
||||
## Changelog
|
||||
* Scan `yaml`/`json` files from url
|
||||
```
|
||||
kubescape scan framework nsa https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/master/release/kubernetes-manifests.yaml
|
||||
```
|
||||
|
||||
Kubescape changes are tracked on the [release](https://github.com/kubescape/kubescape/releases) page.
|
||||
* Output in `json` format
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format json --output results.json
|
||||
```
|
||||
|
||||
## License
|
||||
* Output in `junit xml` format
|
||||
```
|
||||
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format junit --output results.xml
|
||||
```
|
||||
|
||||
Copyright 2021-2024, the Kubescape Authors. All rights reserved. Kubescape is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details.
|
||||
* Scan with exceptions, objects with exceptions will be presented as `warning` and not `fail` <img src="docs/new-feature.svg">
|
||||
```
|
||||
kubescape scan framework nsa --exceptions examples/exceptions.json
|
||||
```
|
||||
|
||||
### Helm Support
|
||||
|
||||
* 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:
|
||||
```
|
||||
helm template bitnami/mysql --generate-name --dry-run | kubescape scan framework nsa -
|
||||
```
|
||||
### Offline Support
|
||||
|
||||
It is possible to run Kubescape offline!
|
||||
|
||||
First download the framework and then scan with `--use-from` flag
|
||||
|
||||
* Download and save in file, if file name not specified, will store save to `~/.kubescape/<framework name>.json`
|
||||
```
|
||||
kubescape download framework nsa --output nsa.json
|
||||
```
|
||||
|
||||
* Scan using the downloaded framework
|
||||
```
|
||||
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.
|
||||
|
||||
# How to build
|
||||
|
||||
## Build using python script
|
||||
|
||||
Kubescpae can be built using:
|
||||
|
||||
``` sh
|
||||
python build.py
|
||||
```
|
||||
|
||||
Note: In order to built using the above script, one must set the environment
|
||||
variables in this script:
|
||||
|
||||
+ RELEASE
|
||||
+ ArmoBEServer
|
||||
+ ArmoERServer
|
||||
+ ArmoWebsite
|
||||
|
||||
|
||||
## Build using go
|
||||
|
||||
Note: development (and the release process) is done with Go `1.17`
|
||||
|
||||
1. Clone Project
|
||||
```
|
||||
git clone https://github.com/armosec/kubescape.git kubescape && cd "$_"
|
||||
```
|
||||
|
||||
2. Build
|
||||
```
|
||||
go mod tidy && go build -o kubescape .
|
||||
```
|
||||
|
||||
3. Run
|
||||
```
|
||||
./kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
```
|
||||
|
||||
4. Enjoy :zany_face:
|
||||
|
||||
## How to build in Docker
|
||||
|
||||
1. Clone Project
|
||||
```
|
||||
git clone https://github.com/armosec/kubescape.git kubescape && cd "$_"
|
||||
```
|
||||
|
||||
2. Build
|
||||
```
|
||||
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/)
|
||||
* Non-root containers
|
||||
* Immutable container filesystem
|
||||
* Privileged containers
|
||||
* hostPID, hostIPC privileges
|
||||
* hostNetwork access
|
||||
* allowedHostPaths field
|
||||
* Protecting pod service account tokens
|
||||
* Resource policies
|
||||
* Control plane hardening
|
||||
* Exposed dashboard
|
||||
* Allow privilege escalation
|
||||
* Applications credentials in configuration files
|
||||
* Cluster-admin binding
|
||||
* Exec into container
|
||||
* Dangerous capabilities
|
||||
* Insecure capabilities
|
||||
* Linux hardening
|
||||
* Ingress and Egress blocked
|
||||
* Container hostPort
|
||||
* Network policies
|
||||
* Symlink Exchange Can Allow Host Filesystem Access (CVE-2021-25741)
|
||||
|
||||
|
||||
|
||||
## Technology
|
||||
Kubescape based on OPA engine: https://github.com/open-policy-agent/opa and ARMO's posture controls.
|
||||
|
||||
The tools retrieves Kubernetes objects from the API server and runs a set of [regos snippets](https://www.openpolicyagent.org/docs/latest/policy-language/) developed by [ARMO](https://www.armosec.io/).
|
||||
|
||||
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.
|
||||
|
||||
Kubescape is a [Cloud Native Computing Foundation (CNCF) incubating project](https://www.cncf.io/projects/kubescape/) and was contributed by [ARMO](https://www.armosec.io/?utm_source=github&utm_medium=repository).
|
||||
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/cncf/artwork/refs/heads/main/other/cncf-member/incubating/color/cncf-incubating-color.svg" width="300" alt="CNCF Incubating Project">
|
||||
</div>
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
header:
|
||||
schema-version: 1.0.0
|
||||
last-updated: '2023-10-12'
|
||||
last-reviewed: '2023-10-12'
|
||||
expiration-date: '2024-10-12T01:00:00.000Z'
|
||||
project-url: https://github.com/kubescape/kubescape/
|
||||
project-release: 1.0.0
|
||||
project-lifecycle:
|
||||
status: active
|
||||
bug-fixes-only: false
|
||||
core-maintainers:
|
||||
- github:amirmalka
|
||||
- github:amitschendel
|
||||
- github:bezbran
|
||||
- github:craigbox
|
||||
- github:dwertent
|
||||
- github:matthyx
|
||||
- github:rotemamsa
|
||||
- github:slashben
|
||||
contribution-policy:
|
||||
accepts-pull-requests: true
|
||||
accepts-automated-pull-requests: false
|
||||
code-of-conduct: https://github.com/kubescape/kubescape/blob/master/CODE_OF_CONDUCT.md
|
||||
dependencies:
|
||||
third-party-packages: true
|
||||
dependencies-lists:
|
||||
- https://github.com/kubescape/kubescape/blob/master/go.mod
|
||||
- https://github.com/kubescape/kubescape/blob/master/httphandler/go.mod
|
||||
env-dependencies-policy:
|
||||
policy-url: https://github.com/kubescape/kubescape/blob/master/docs/environment-dependencies-policy.md
|
||||
documentation:
|
||||
- https://github.com/kubescape/kubescape/tree/master/docs
|
||||
distribution-points:
|
||||
- https://github.com/kubescape/kubescape/
|
||||
security-artifacts:
|
||||
threat-model:
|
||||
threat-model-created: false
|
||||
security-testing:
|
||||
- tool-type: sca
|
||||
tool-name: Dependabot
|
||||
tool-version: latest
|
||||
integration:
|
||||
ad-hoc: false
|
||||
ci: true
|
||||
before-release: true
|
||||
comment: |
|
||||
Dependabot is enabled for this repo.
|
||||
security-contacts:
|
||||
- type: email
|
||||
value: cncf-kubescape-maintainers@lists.cncf.io
|
||||
vulnerability-reporting:
|
||||
accepts-vulnerability-reports: true
|
||||
security-policy: https://github.com/kubescape/kubescape/security/policy
|
||||
email-contact: cncf-kubescape-maintainers@lists.cncf.io
|
||||
comment: |
|
||||
The first and best way to report a vulnerability is by using private security issues in GitHub.
|
||||
@@ -1,5 +0,0 @@
|
||||
# Security
|
||||
|
||||
The Kubescape project manages this document in the central project repository.
|
||||
|
||||
Go to the [centralized SECURITY.md](https://github.com/kubescape/project-governance/blob/main/SECURITY.md)
|
||||
82
build.py
Normal file
82
build.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
BASE_GETTER_CONST = "github.com/armosec/kubescape/cautils/getter"
|
||||
BE_SERVER_CONST = BASE_GETTER_CONST + ".ArmoBEURL"
|
||||
ER_SERVER_CONST = BASE_GETTER_CONST + ".ArmoERURL"
|
||||
WEBSITE_CONST = BASE_GETTER_CONST + ".ArmoFEURL"
|
||||
|
||||
def checkStatus(status, msg):
|
||||
if status != 0:
|
||||
sys.stderr.write(msg)
|
||||
exit(status)
|
||||
|
||||
|
||||
def getBuildDir():
|
||||
currentPlatform = platform.system()
|
||||
buildDir = "build/"
|
||||
|
||||
if currentPlatform == "Windows": buildDir += "windows-latest"
|
||||
elif currentPlatform == "Linux": buildDir += "ubuntu-latest"
|
||||
elif currentPlatform == "Darwin": buildDir += "macos-latest"
|
||||
else: raise OSError("Platform %s is not supported!" % (currentPlatform))
|
||||
|
||||
return buildDir
|
||||
|
||||
def getPackageName():
|
||||
packageName = "kubescape"
|
||||
# if platform.system() == "Windows": packageName += ".exe"
|
||||
|
||||
return packageName
|
||||
|
||||
|
||||
def main():
|
||||
print("Building Kubescape")
|
||||
|
||||
# print environment variables
|
||||
print(os.environ)
|
||||
|
||||
# Set some variables
|
||||
packageName = getPackageName()
|
||||
buildUrl = "github.com/armosec/kubescape/cmd.BuildNumber"
|
||||
releaseVersion = os.getenv("RELEASE")
|
||||
ArmoBEServer = os.getenv("ArmoBEServer")
|
||||
ArmoERServer = os.getenv("ArmoERServer")
|
||||
ArmoWebsite = os.getenv("ArmoWebsite")
|
||||
|
||||
# Create build directory
|
||||
buildDir = getBuildDir()
|
||||
|
||||
if not os.path.isdir(buildDir):
|
||||
os.makedirs(buildDir)
|
||||
|
||||
# Get dependencies
|
||||
try:
|
||||
status = subprocess.call(["go", "mod", "tidy"])
|
||||
checkStatus(status, "Failed to get dependencies")
|
||||
|
||||
except OSError:
|
||||
print("An error occured: (Hint: check if go is installed)")
|
||||
raise
|
||||
|
||||
# Build kubescape
|
||||
ldflags = "-w -s -X %s=%s -X %s=%s -X %s=%s -X %s=%s" \
|
||||
% (buildUrl, releaseVersion, BE_SERVER_CONST, ArmoBEServer,
|
||||
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())
|
||||
with open(buildDir + "/" + packageName + ".sha1", "w") as kube_sha:
|
||||
kube_sha.write(sha1.hexdigest())
|
||||
|
||||
print("Build Done")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,27 +1,16 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23-bookworm AS builder
|
||||
FROM golang:1.17-alpine as builder
|
||||
ENV GOPROXY=https://goproxy.io,direct
|
||||
ENV GO111MODULE=on
|
||||
|
||||
ENV GO111MODULE=on CGO_ENABLED=0
|
||||
WORKDIR /work
|
||||
ARG TARGETOS TARGETARCH
|
||||
ADD . .
|
||||
RUN go mod tidy
|
||||
RUN GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w " -installsuffix cgo -o kubescape .
|
||||
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg \
|
||||
cd httphandler && GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/ksserver .
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg \
|
||||
go run downloader/main.go
|
||||
FROM alpine
|
||||
COPY --from=builder /work/kubescape /usr/bin/kubescape
|
||||
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
# # Download the frameworks. Use the "--use-default" flag when running kubescape
|
||||
# RUN kubescape download framework nsa && kubescape download framework mitre
|
||||
|
||||
USER nonroot
|
||||
WORKDIR /home/nonroot/
|
||||
|
||||
COPY --from=builder /out/ksserver /usr/bin/ksserver
|
||||
COPY --from=builder /root/.kubescape /home/nonroot/.kubescape
|
||||
|
||||
ARG image_version client
|
||||
ENV RELEASE=$image_version CLIENT=$client
|
||||
|
||||
ENTRYPOINT ["ksserver"]
|
||||
CMD ["kubescape"]
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
.git
|
||||
kubescape*
|
||||
@@ -1,19 +0,0 @@
|
||||
## Docker Build
|
||||
|
||||
### Build your own Docker image
|
||||
|
||||
1. Clone Project
|
||||
```
|
||||
git clone https://github.com/kubescape/kubescape.git kubescape && cd "$_"
|
||||
```
|
||||
|
||||
2. Build kubescape CLI Docker image
|
||||
```
|
||||
make all
|
||||
docker buildx build -t kubescape-cli -f build/kubescape-cli.Dockerfile --build-arg="ks_binary=kubescape" --load .
|
||||
```
|
||||
|
||||
3. Build kubescape Docker image
|
||||
```
|
||||
docker buildx build -t kubescape -f build/Dockerfile --load .
|
||||
```
|
||||
@@ -1,12 +0,0 @@
|
||||
FROM gcr.io/distroless/static-debian12:debug-nonroot
|
||||
|
||||
USER nonroot
|
||||
WORKDIR /home/nonroot/
|
||||
|
||||
ARG image_version client TARGETARCH
|
||||
ENV RELEASE=$image_version CLIENT=$client
|
||||
|
||||
COPY kubescape-${TARGETARCH}-ubuntu-latest /usr/bin/kubescape
|
||||
RUN ["kubescape", "download", "artifacts"]
|
||||
|
||||
ENTRYPOINT ["kubescape"]
|
||||
@@ -1 +0,0 @@
|
||||
.git
|
||||
101
cautils/apis/backendconnector.go
Normal file
101
cautils/apis/backendconnector.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HTTPReqFunc allows you to insert query params and more to aggregation message while using update aggregator
|
||||
type HTTPReqFunc func(req *http.Request, qryData interface{})
|
||||
|
||||
func BasicBEQuery(req *http.Request, qryData interface{}) {
|
||||
|
||||
q := req.URL.Query()
|
||||
|
||||
if notificationData, isok := qryData.(*LoginObject); isok {
|
||||
q.Add("customerGUID", notificationData.GUID)
|
||||
}
|
||||
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
func EmptyQuery(req *http.Request, qryData interface{}) {
|
||||
q := req.URL.Query()
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
func MapQuery(req *http.Request, qryData interface{}) {
|
||||
q := req.URL.Query()
|
||||
if qryMap, isok := qryData.(map[string]string); isok {
|
||||
for k, v := range qryMap {
|
||||
q.Add(k, v)
|
||||
}
|
||||
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
func BEHttpRequest(loginobj *LoginObject, beURL,
|
||||
httpverb string,
|
||||
endpoint string,
|
||||
payload []byte,
|
||||
f HTTPReqFunc,
|
||||
qryData interface{}) ([]byte, error) {
|
||||
client := &http.Client{}
|
||||
|
||||
beURL = fmt.Sprintf("%v/%v", beURL, endpoint)
|
||||
req, err := http.NewRequest(httpverb, beURL, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", loginobj.Authorization)
|
||||
f(req, qryData)
|
||||
|
||||
for _, cookie := range loginobj.Cookies {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
fmt.Printf("req:\n%v\nresp:%v\n", req, resp)
|
||||
return nil, fmt.Errorf("Error #%v Due to: %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
type BELoginResponse struct {
|
||||
Name string `json:"name"`
|
||||
PreferredUsername string `json:"preferred_username"`
|
||||
Email string `json:"email"`
|
||||
CustomerGuid string `json:"customerGuid"`
|
||||
Expires string `json:"expires"`
|
||||
Authorization string `json:"authorization"`
|
||||
Cookies []*http.Cookie
|
||||
}
|
||||
|
||||
func (r *BELoginResponse) ToLoginObject() *LoginObject {
|
||||
l := &LoginObject{}
|
||||
l.Authorization = r.Authorization
|
||||
l.Cookies = r.Cookies
|
||||
l.Expires = r.Expires
|
||||
l.GUID = r.CustomerGuid
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
type BackendConnector struct {
|
||||
BaseURL string
|
||||
BELoginResponse *BELoginResponse
|
||||
Credentials *CustomerLoginDetails
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
128
cautils/apis/backendconnectormethods.go
Normal file
128
cautils/apis/backendconnectormethods.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MakeBackendConnector(client *http.Client, baseURL string, loginDetails *CustomerLoginDetails) (*BackendConnector, error) {
|
||||
if err := ValidateBEConnectorMakerInput(client, baseURL, loginDetails); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn := &BackendConnector{BaseURL: baseURL, Credentials: loginDetails, HTTPClient: client}
|
||||
err := conn.Login()
|
||||
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func ValidateBEConnectorMakerInput(client *http.Client, baseURL string, loginDetails *CustomerLoginDetails) error {
|
||||
if client == nil {
|
||||
fmt.Errorf("You must provide an initialized httpclient")
|
||||
}
|
||||
if len(baseURL) == 0 {
|
||||
return fmt.Errorf("you must provide a valid backend url")
|
||||
}
|
||||
|
||||
if loginDetails == nil || (len(loginDetails.Email) == 0 && len(loginDetails.Password) == 0) {
|
||||
return fmt.Errorf("you must provide valid login details")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *BackendConnector) Login() error {
|
||||
if !r.IsExpired() {
|
||||
return nil
|
||||
}
|
||||
|
||||
loginInfoBytes, err := json.Marshal(r.Credentials)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshal credentials properly")
|
||||
}
|
||||
|
||||
beURL := fmt.Sprintf("%v/%v", r.BaseURL, "login")
|
||||
|
||||
req, err := http.NewRequest("POST", beURL, bytes.NewReader(loginInfoBytes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Referer", strings.Replace(beURL, "dashbe", "cpanel", 1))
|
||||
resp, err := r.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read login response")
|
||||
}
|
||||
|
||||
loginS := &BELoginResponse{}
|
||||
json.Unmarshal(body, &loginS)
|
||||
|
||||
loginS.Cookies = resp.Cookies()
|
||||
r.BELoginResponse = loginS
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *BackendConnector) IsExpired() bool {
|
||||
return r.BELoginResponse == nil || r.BELoginResponse.ToLoginObject().IsExpired()
|
||||
}
|
||||
|
||||
func (r *BackendConnector) GetBaseURL() string {
|
||||
return r.BaseURL
|
||||
}
|
||||
func (r *BackendConnector) GetLoginObj() *LoginObject {
|
||||
return r.BELoginResponse.ToLoginObject()
|
||||
}
|
||||
func (r *BackendConnector) GetClient() *http.Client {
|
||||
return r.HTTPClient
|
||||
}
|
||||
|
||||
func (r *BackendConnector) HTTPSend(httpverb string,
|
||||
endpoint string,
|
||||
payload []byte,
|
||||
f HTTPReqFunc,
|
||||
qryData interface{}) ([]byte, error) {
|
||||
|
||||
beURL := fmt.Sprintf("%v/%v", r.GetBaseURL(), endpoint)
|
||||
req, err := http.NewRequest(httpverb, beURL, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.IsExpired() {
|
||||
r.Login()
|
||||
}
|
||||
|
||||
loginobj := r.GetLoginObj()
|
||||
req.Header.Set("Authorization", loginobj.Authorization)
|
||||
f(req, qryData)
|
||||
q := req.URL.Query()
|
||||
q.Set("customerGUID", loginobj.GUID)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
for _, cookie := range loginobj.Cookies {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
resp, err := r.GetClient().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
fmt.Printf("req:\n%v\nresp:%v\n", req, resp)
|
||||
return nil, fmt.Errorf("Error #%v Due to: %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
25
cautils/apis/clusterapis.go
Normal file
25
cautils/apis/clusterapis.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package apis
|
||||
|
||||
// WebsocketScanCommand api
|
||||
const (
|
||||
WebsocketScanCommandVersion string = "v1"
|
||||
WebsocketScanCommandPath string = "scanImage"
|
||||
)
|
||||
|
||||
// commands send via websocket
|
||||
const (
|
||||
UPDATE string = "update"
|
||||
ATTACH string = "Attach"
|
||||
REMOVE string = "remove"
|
||||
DETACH string = "Detach"
|
||||
INCOMPATIBLE string = "Incompatible"
|
||||
REPLACE_HEADERS string = "ReplaceHeaders"
|
||||
IMAGE_UNREACHABLE string = "ImageUnreachable"
|
||||
SIGN string = "sign"
|
||||
UNREGISTERED string = "unregistered"
|
||||
INJECT string = "inject"
|
||||
RESTART string = "restart"
|
||||
ENCRYPT string = "encryptSecret"
|
||||
DECRYPT string = "decryptSecret"
|
||||
SCAN string = "scan"
|
||||
)
|
||||
78
cautils/apis/datastructures.go
Normal file
78
cautils/apis/datastructures.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
// WebsocketScanCommand trigger scan thru the websocket
|
||||
type WebsocketScanCommand struct {
|
||||
// CustomerGUID string `json:"customerGUID"`
|
||||
ImageTag string `json:"imageTag"`
|
||||
Wlid string `json:"wlid"`
|
||||
IsScanned bool `json:"isScanned"`
|
||||
ContainerName string `json:"containerName"`
|
||||
JobID string `json:"jobID,omitempty"`
|
||||
LastAction int `json:"actionIDN"`
|
||||
// ImageHash string `json:"imageHash"`
|
||||
Credentials *types.AuthConfig `json:"credentials,omitempty"`
|
||||
}
|
||||
|
||||
//taken from BE
|
||||
// ElasticRespTotal holds the total struct in Elastic array response
|
||||
type ElasticRespTotal struct {
|
||||
Value int `json:"value"`
|
||||
Relation string `json:"relation"`
|
||||
}
|
||||
|
||||
// V2ListResponse holds the response of some list request with some metadata
|
||||
type V2ListResponse struct {
|
||||
Total ElasticRespTotal `json:"total"`
|
||||
Response interface{} `json:"response"`
|
||||
// Cursor for quick access to the next page. Not supported yet
|
||||
Cursor string `json:"cursor"`
|
||||
}
|
||||
|
||||
// Oauth2Customer returns inside the "ca_groups" field in claims section of
|
||||
// Oauth2 verification process
|
||||
type Oauth2Customer struct {
|
||||
CustomerName string `json:"customerName"`
|
||||
CustomerGUID string `json:"customerGUID"`
|
||||
}
|
||||
|
||||
type LoginObject struct {
|
||||
Authorization string `json:"authorization"`
|
||||
GUID string
|
||||
Cookies []*http.Cookie
|
||||
Expires string
|
||||
}
|
||||
|
||||
type SafeMode struct {
|
||||
Reporter string `json:"reporter"` // "Agent"
|
||||
Action string `json:"action,omitempty"` // "action"
|
||||
Wlid string `json:"wlid"` // CAA_WLID
|
||||
PodName string `json:"podName"` // CAA_POD_NAME
|
||||
InstanceID string `json:"instanceID"` // CAA_POD_NAME
|
||||
ContainerName string `json:"containerName,omitempty"` // CAA_CONTAINER_NAME
|
||||
ProcessName string `json:"processName,omitempty"`
|
||||
ProcessID int `json:"processID,omitempty"`
|
||||
ProcessCMD string `json:"processCMD,omitempty"`
|
||||
ComponentGUID string `json:"componentGUID,omitempty"` // CAA_GUID
|
||||
StatusCode int `json:"statusCode"` // 0/1/2
|
||||
ProcessExitCode int `json:"processExitCode"` // 0 +
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Message string `json:"message,omitempty"` // any string
|
||||
JobID string `json:"jobID,omitempty"` // any string
|
||||
Compatible *bool `json:"compatible,omitempty"`
|
||||
}
|
||||
|
||||
func (safeMode *SafeMode) Json() string {
|
||||
b, err := json.Marshal(*safeMode)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s", b)
|
||||
}
|
||||
26
cautils/apis/datastructures_test.go
Normal file
26
cautils/apis/datastructures_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package apis
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "net/http"
|
||||
// "testing"
|
||||
// )
|
||||
|
||||
// func TestAuditStructure(t *testing.T) {
|
||||
// c := http.Client{}
|
||||
// be, err := MakeBackendConnector(&c, "https://dashbe.eudev3.cyberarmorsoft.com", &CustomerLoginDetails{Email: "lalafi@cyberarmor.io", Password: "*", CustomerName: "CyberArmorTests"})
|
||||
// if err != nil {
|
||||
// t.Errorf("sad1")
|
||||
|
||||
// }
|
||||
|
||||
// b, err := be.HTTPSend("GET", "v1/microservicesOverview", nil, MapQuery, map[string]string{"wlid": "wlid://cluster-childrenofbodom/namespace-default/deployment-pos"})
|
||||
// if err != nil {
|
||||
// t.Errorf("sad2")
|
||||
|
||||
// }
|
||||
// fmt.Printf("%v", string(b))
|
||||
|
||||
// t.Errorf("sad")
|
||||
|
||||
// }
|
||||
27
cautils/apis/interfaces.go
Normal file
27
cautils/apis/interfaces.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// type Dashboard interface {
|
||||
// OPAFRAMEWORKGet(string, bool) ([]opapolicy.Framework, error)
|
||||
// }
|
||||
|
||||
// Connector - interface for any connector (BE/Portal and so on)
|
||||
type Connector interface {
|
||||
|
||||
//may used for a more generic httpsend interface based method
|
||||
GetBaseURL() string
|
||||
GetLoginObj() *LoginObject
|
||||
GetClient() *http.Client
|
||||
|
||||
Login() error
|
||||
IsExpired() bool
|
||||
|
||||
HTTPSend(httpverb string,
|
||||
endpoint string,
|
||||
payload []byte,
|
||||
f HTTPReqFunc,
|
||||
qryData interface{}) ([]byte, error)
|
||||
}
|
||||
255
cautils/apis/login.go
Normal file
255
cautils/apis/login.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
|
||||
// "go.uber.org/zap"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func GetOauth2TokenURL() string {
|
||||
return "https://idens.eudev3.cyberarmorsoft.com/auth/realms/CyberArmorSites"
|
||||
}
|
||||
|
||||
func GetLoginStruct() (LoginAux, error) {
|
||||
|
||||
return LoginAux{Referer: "https://cpanel.eudev3.cyberarmorsoft.com/login", Url: "https://cpanel.eudev3.cyberarmorsoft.com/login"}, nil
|
||||
}
|
||||
|
||||
func LoginWithKeycloak(loginDetails CustomerLoginDetails) ([]uuid.UUID, *oidc.IDToken, error) {
|
||||
// var custGUID uuid.UUID
|
||||
// config.Oauth2TokenURL
|
||||
if GetOauth2TokenURL() == "" {
|
||||
return nil, nil, fmt.Errorf("missing oauth2 token URL")
|
||||
}
|
||||
urlaux, _ := GetLoginStruct()
|
||||
conf, err := getOauth2Config(urlaux)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ctx := context.Background()
|
||||
provider, err := oidc.NewProvider(ctx, GetOauth2TokenURL())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// "Oauth2ClientID": "golang-client"
|
||||
oidcConfig := &oidc.Config{
|
||||
ClientID: "golang-client",
|
||||
SkipClientIDCheck: true,
|
||||
}
|
||||
|
||||
verifier := provider.Verifier(oidcConfig)
|
||||
ouToken, err := conf.PasswordCredentialsToken(ctx, loginDetails.Email, loginDetails.Password)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// "Authorization",
|
||||
authorization := fmt.Sprintf("%s %s", ouToken.Type(), ouToken.AccessToken)
|
||||
// oidc.IDTokenVerifier
|
||||
tkn, err := verifier.Verify(ctx, ouToken.AccessToken)
|
||||
if err != nil {
|
||||
return nil, tkn, err
|
||||
}
|
||||
tkn.Nonce = authorization
|
||||
if loginDetails.CustomerName == "" {
|
||||
customers, err := getCustomersNames(tkn)
|
||||
if err != nil {
|
||||
return nil, tkn, err
|
||||
}
|
||||
if len(customers) == 1 {
|
||||
loginDetails.CustomerName = customers[0]
|
||||
} else {
|
||||
return nil, tkn, fmt.Errorf("login with one of the following customers: %v", customers)
|
||||
}
|
||||
}
|
||||
custGUID, err := getCustomerGUID(tkn, &loginDetails)
|
||||
if err != nil {
|
||||
return nil, tkn, err
|
||||
}
|
||||
return []uuid.UUID{custGUID}, tkn, nil
|
||||
}
|
||||
|
||||
func getOauth2Config(urlaux LoginAux) (*oauth2.Config, error) {
|
||||
reURLSlices := strings.Split(urlaux.Referer, "/")
|
||||
if len(reURLSlices) == 0 {
|
||||
reURLSlices = strings.Split(urlaux.Url, "/")
|
||||
}
|
||||
// zapLogger.With(zap.Strings("referer", reURLSlices)).Info("Searching oauth2Config for")
|
||||
if len(reURLSlices) < 3 {
|
||||
reURLSlices = []string{reURLSlices[0], reURLSlices[0], reURLSlices[0]}
|
||||
}
|
||||
lg, _ := GetLoginStruct()
|
||||
provider, _ := oidc.NewProvider(context.Background(), GetOauth2TokenURL())
|
||||
//provider.Endpoint {"AuthURL":"https://idens.eudev3.cyberarmorsoft.com/auth/realms/CyberArmorSites/protocol/openid-connect/auth","TokenURL":"https://idens.eudev3.cyberarmorsoft.com/auth/realms/CyberArmorSites/protocol/openid-connect/token","AuthStyle":0}
|
||||
conf := oauth2.Config{
|
||||
ClientID: "golang-client",
|
||||
ClientSecret: "4e33bad2-3491-41a6-b486-93c492cfb4a2",
|
||||
RedirectURL: lg.Referer,
|
||||
// Discovery returns the OAuth2 endpoints.
|
||||
Endpoint: provider.Endpoint(),
|
||||
// "openid" is a required scope for OpenID Connect flows.
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
return &conf, nil
|
||||
// return nil, fmt.Errorf("canno't find oauth2Config for referer '%+v'.\nPlease set referer or origin headers", reURLSlices)
|
||||
}
|
||||
|
||||
func getCustomersNames(oauth2Details *oidc.IDToken) ([]string, error) {
|
||||
var claimsJSON Oauth2Claims
|
||||
if err := oauth2Details.Claims(&claimsJSON); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customersList := make([]string, 0, len(claimsJSON.CAGroups))
|
||||
for _, v := range claimsJSON.CAGroups {
|
||||
var caCustomer Oauth2Customer
|
||||
if err := json.Unmarshal([]byte(v), &caCustomer); err == nil {
|
||||
customersList = append(customersList, caCustomer.CustomerName)
|
||||
}
|
||||
}
|
||||
return customersList, nil
|
||||
}
|
||||
|
||||
func getCustomerGUID(tkn *oidc.IDToken, loginDetails *CustomerLoginDetails) (uuid.UUID, error) {
|
||||
|
||||
customers, err := getCustomersList(tkn)
|
||||
if err != nil {
|
||||
return uuid.UUID{}, err
|
||||
}
|
||||
|
||||
// if customer name not provided - use default customer
|
||||
if loginDetails.CustomerName == "" && len(customers) > 0 {
|
||||
return uuid.FromString(customers[0].CustomerGUID)
|
||||
}
|
||||
|
||||
for _, i := range customers {
|
||||
if i.CustomerName == loginDetails.CustomerName {
|
||||
return uuid.FromString(i.CustomerGUID)
|
||||
}
|
||||
}
|
||||
return uuid.UUID{}, fmt.Errorf("customer name not found in customer list")
|
||||
}
|
||||
|
||||
func getCustomersList(oauth2Details *oidc.IDToken) ([]Oauth2Customer, error) {
|
||||
var claimsJSON Oauth2Claims
|
||||
if err := oauth2Details.Claims(&claimsJSON); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customersList := make([]Oauth2Customer, 0, len(claimsJSON.CAGroups))
|
||||
for _, v := range claimsJSON.CAGroups {
|
||||
var caCustomer Oauth2Customer
|
||||
if err := json.Unmarshal([]byte(v), &caCustomer); err == nil {
|
||||
customersList = append(customersList, caCustomer)
|
||||
}
|
||||
}
|
||||
return customersList, nil
|
||||
}
|
||||
|
||||
// func MakeAuthCookies(custGUID uuid.UUID, ouToken *oidc.IDToken) (*http.Cookie, error) {
|
||||
// var ccc http.Cookie
|
||||
// var responseData AuthenticationCookie
|
||||
// expireDate := time.Now().UTC().Add(time.Duration(config.CookieExpirationHours) * time.Hour)
|
||||
// if ouToken != nil {
|
||||
// expireDate = ouToken.Expiry
|
||||
// }
|
||||
// ccc.Expires = expireDate
|
||||
// responseData.CustomerGUID = custGUID
|
||||
// responseData.Expires = ccc.Expires
|
||||
// responseData.Version = 0
|
||||
// authorizationStr := ""
|
||||
// if ouToken != nil {
|
||||
// authorizationStr = ouToken.Nonce
|
||||
// if err := ouToken.Claims(&responseData.Oauth2Claims); err != nil {
|
||||
// errStr := fmt.Sprintf("failed to get claims from JWT")
|
||||
// return nil, fmt.Errorf("%v", errStr)
|
||||
// }
|
||||
// }
|
||||
// jsonBytes, err := json.Marshal(responseData)
|
||||
// if err != nil {
|
||||
// errStr := fmt.Sprintf("failed to get claims from JWT")
|
||||
// return nil, fmt.Errorf("%v", errStr)
|
||||
// }
|
||||
// ccc.Name = "auth"
|
||||
// ccc.Value = hex.EncodeToString(jsonBytes) + "." + cacheaccess.CalcHmac256(jsonBytes)
|
||||
// // TODO: HttpOnly for security...
|
||||
// ccc.HttpOnly = false
|
||||
// ccc.Path = "/"
|
||||
// ccc.Secure = true
|
||||
// ccc.SameSite = http.SameSiteNoneMode
|
||||
// http.SetCookie(w, &ccc)
|
||||
// responseData.Authorization = authorizationStr
|
||||
// jsonBytes, err = json.Marshal(responseData)
|
||||
// if err != nil {
|
||||
// w.WriteHeader(http.StatusInternalServerError)
|
||||
// fmt.Fprintf(w, "error while marshaling response(2) %s", err)
|
||||
// return
|
||||
// }
|
||||
// w.Write(jsonBytes)
|
||||
// }
|
||||
|
||||
func Login(loginDetails CustomerLoginDetails) (*LoginObject, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func GetBEInfo(cfgFile string) string {
|
||||
return "https://dashbe.eudev3.cyberarmorsoft.com"
|
||||
}
|
||||
|
||||
func BELogin(loginDetails *CustomerLoginDetails, login string, cfg string) (*BELoginResponse, error) {
|
||||
client := &http.Client{}
|
||||
|
||||
basebeURL := GetBEInfo(cfg)
|
||||
beURL := fmt.Sprintf("%v/%v", basebeURL, login)
|
||||
|
||||
loginInfoBytes, err := json.Marshal(loginDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", beURL, bytes.NewReader(loginInfoBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Referer", strings.Replace(beURL, "dashbe", "cpanel", 1))
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loginS := &BELoginResponse{}
|
||||
json.Unmarshal(body, &loginS)
|
||||
|
||||
loginS.Cookies = resp.Cookies()
|
||||
return loginS, nil
|
||||
}
|
||||
|
||||
func (r *LoginObject) IsExpired() bool {
|
||||
if r == nil {
|
||||
return true
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339, r.Expires)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return t.UTC().Before(time.Now().UTC())
|
||||
}
|
||||
41
cautils/apis/login_test.go
Normal file
41
cautils/apis/login_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package apis
|
||||
|
||||
// func TestLogin2BE(t *testing.T) {
|
||||
|
||||
// loginDetails := CustomerLoginDetails{Email: "lalafi@cyberarmor.io", Password: "***", CustomerName: "CyberArmorTests"}
|
||||
// res, err := BELogin(loginDetails, "login")
|
||||
// if err != nil {
|
||||
// t.Errorf("failed to get raw audit is different ")
|
||||
// }
|
||||
// k := res.ToLoginObject()
|
||||
|
||||
// fmt.Printf("%v\n", k)
|
||||
|
||||
// }
|
||||
|
||||
// func TestGetMicroserviceOverview(t *testing.T) {
|
||||
// // client := &http.Client{}
|
||||
// loginDetails := CustomerLoginDetails{Email: "lalafi@cyberarmor.io", Password: "***", CustomerName: "CyberArmorTests"}
|
||||
// loginobj, err := BELogin(loginDetails, "login")
|
||||
// if err != nil {
|
||||
// t.Errorf("failed to get raw audit is different ")
|
||||
// }
|
||||
// k := loginobj.ToLoginObject()
|
||||
// beURL := GetBEInfo("")
|
||||
|
||||
// res, err := BEHttpRequest(k, beURL,
|
||||
// "GET",
|
||||
// "v1/microservicesOverview",
|
||||
// nil,
|
||||
// BasicBEQuery,
|
||||
// k)
|
||||
|
||||
// if err != nil {
|
||||
// t.Errorf("failed to get raw audit is different ")
|
||||
// }
|
||||
|
||||
// s := string(res)
|
||||
|
||||
// fmt.Printf("%v\n", s)
|
||||
|
||||
// }
|
||||
38
cautils/apis/logindatastructures.go
Normal file
38
cautils/apis/logindatastructures.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
// AuthenticationCookie is what it is
|
||||
type AuthenticationCookie struct {
|
||||
Oauth2Claims `json:",inline"`
|
||||
CustomerGUID uuid.UUID `json:"customerGuid"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Version int `json:"version"`
|
||||
Authorization string `json:"authorization,omitempty"`
|
||||
}
|
||||
|
||||
type LoginAux struct {
|
||||
Referer string
|
||||
Url string
|
||||
}
|
||||
|
||||
// CustomerLoginDetails is what it is
|
||||
type CustomerLoginDetails struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
CustomerName string `json:"customer,omitempty"`
|
||||
CustomerGUID uuid.UUID `json:"customerGuid,omitempty"`
|
||||
}
|
||||
|
||||
// Oauth2Claims returns in claims section of Oauth2 verification process
|
||||
type Oauth2Claims struct {
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
PreferredUserName string `json:"preferred_username"`
|
||||
CAGroups []string `json:"ca_groups"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
132
cautils/apis/websocketdatastructures.go
Normal file
132
cautils/apis/websocketdatastructures.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Commands list of commands received from websocket
|
||||
type Commands struct {
|
||||
Commands []Command `json:"commands"`
|
||||
}
|
||||
|
||||
// Command structure of command received from websocket
|
||||
type Command struct {
|
||||
CommandName string `json:"commandName"`
|
||||
ResponseID string `json:"responseID"`
|
||||
Wlid string `json:"wlid,omitempty"`
|
||||
WildWlid string `json:"wildWlid,omitempty"`
|
||||
Sid string `json:"sid,omitempty"`
|
||||
WildSid string `json:"wildSid,omitempty"`
|
||||
JobTracking JobTracking `json:"jobTracking"`
|
||||
Args map[string]interface{} `json:"args,omitempty"`
|
||||
}
|
||||
|
||||
type JobTracking struct {
|
||||
JobID string `json:"jobID,omitempty"`
|
||||
ParentID string `json:"parentAction,omitempty"`
|
||||
LastActionNumber int `json:"numSeq,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Command) DeepCopy() *Command {
|
||||
newCommand := &Command{}
|
||||
newCommand.CommandName = c.CommandName
|
||||
newCommand.ResponseID = c.ResponseID
|
||||
newCommand.Wlid = c.Wlid
|
||||
newCommand.WildWlid = c.WildWlid
|
||||
if c.Args != nil {
|
||||
newCommand.Args = make(map[string]interface{})
|
||||
for i, j := range c.Args {
|
||||
newCommand.Args[i] = j
|
||||
}
|
||||
}
|
||||
return newCommand
|
||||
}
|
||||
|
||||
func (c *Command) GetLabels() map[string]string {
|
||||
if c.Args != nil {
|
||||
if ilabels, ok := c.Args["labels"]; ok {
|
||||
labels := map[string]string{}
|
||||
if b, e := json.Marshal(ilabels); e == nil {
|
||||
if e = json.Unmarshal(b, &labels); e == nil {
|
||||
return labels
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (c *Command) SetLabels(labels map[string]string) {
|
||||
if c.Args == nil {
|
||||
c.Args = make(map[string]interface{})
|
||||
}
|
||||
c.Args["labels"] = labels
|
||||
}
|
||||
|
||||
func (c *Command) GetFieldSelector() map[string]string {
|
||||
if c.Args != nil {
|
||||
if ilabels, ok := c.Args["fieldSelector"]; ok {
|
||||
labels := map[string]string{}
|
||||
if b, e := json.Marshal(ilabels); e == nil {
|
||||
if e = json.Unmarshal(b, &labels); e == nil {
|
||||
return labels
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (c *Command) SetFieldSelector(labels map[string]string) {
|
||||
if c.Args == nil {
|
||||
c.Args = make(map[string]interface{})
|
||||
}
|
||||
c.Args["fieldSelector"] = labels
|
||||
}
|
||||
|
||||
func (c *Command) GetID() string {
|
||||
if c.WildWlid != "" {
|
||||
return c.WildWlid
|
||||
}
|
||||
if c.WildSid != "" {
|
||||
return c.WildSid
|
||||
}
|
||||
if c.Wlid != "" {
|
||||
return c.Wlid
|
||||
}
|
||||
if c.Sid != "" {
|
||||
return c.Sid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Command) Json() string {
|
||||
b, _ := json.Marshal(*c)
|
||||
return fmt.Sprintf("%s", b)
|
||||
}
|
||||
|
||||
func SIDFallback(c *Command) {
|
||||
if c.GetID() == "" {
|
||||
sid, err := getSIDFromArgs(c.Args)
|
||||
if err != nil || sid == "" {
|
||||
return
|
||||
}
|
||||
c.Sid = sid
|
||||
}
|
||||
}
|
||||
|
||||
func getSIDFromArgs(args map[string]interface{}) (string, error) {
|
||||
sidInterface, ok := args["sid"]
|
||||
if !ok {
|
||||
return "", nil
|
||||
}
|
||||
sid, ok := sidInterface.(string)
|
||||
if !ok || sid == "" {
|
||||
return "", fmt.Errorf("sid found in args but empty")
|
||||
}
|
||||
// if _, err := secrethandling.SplitSecretID(sid); err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
return sid, nil
|
||||
}
|
||||
16
cautils/armotypes/executionpolicytypes.go
Normal file
16
cautils/armotypes/executionpolicytypes.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package armotypes
|
||||
|
||||
type EnforcementsRule struct {
|
||||
MonitoredObject []string `json:"monitoredObject"`
|
||||
MonitoredObjectExistence []string `json:"objectExistence"`
|
||||
MonitoredObjectEvent []string `json:"event"`
|
||||
Action []string `json:"action"`
|
||||
}
|
||||
|
||||
type ExecutionPolicy struct {
|
||||
PortalBase `json:",inline"`
|
||||
Designators []PortalDesignator `json:"designators"`
|
||||
PolicyType string `json:"policyType"`
|
||||
CreationTime string `json:"creation_time"`
|
||||
ExecutionEnforcementsRule []EnforcementsRule `json:"enforcementRules"`
|
||||
}
|
||||
66
cautils/armotypes/portaltypes.go
Normal file
66
cautils/armotypes/portaltypes.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package armotypes
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
CostumerGuidQuery = "costumerGUID"
|
||||
ClusterNameQuery = "cluster"
|
||||
DatacenterNameQuery = "datacenter"
|
||||
NamespaceQuery = "namespace"
|
||||
ProjectQuery = "project"
|
||||
WlidQuery = "wlid"
|
||||
SidQuery = "sid"
|
||||
)
|
||||
|
||||
// PortalBase holds basic items data from portal BE
|
||||
type PortalBase struct {
|
||||
GUID string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
Attributes map[string]interface{} `json:"attributes,omitempty"` // could be string
|
||||
}
|
||||
|
||||
type DesignatorType string
|
||||
|
||||
// Supported designators
|
||||
const (
|
||||
DesignatorAttributes DesignatorType = "Attributes"
|
||||
DesignatorAttribute DesignatorType = "Attribute" // Deprecated
|
||||
/*
|
||||
WorkloadID format.
|
||||
k8s format: wlid://cluster-<cluster>/namespace-<namespace>/<kind>-<name>
|
||||
native format: wlid://datacenter-<datacenter>/project-<project>/native-<name>
|
||||
*/
|
||||
DesignatorWlid DesignatorType = "Wlid"
|
||||
/*
|
||||
Wild card - subset of wlid. e.g.
|
||||
1. Include cluster:
|
||||
wlid://cluster-<cluster>/
|
||||
2. Include cluster and namespace (filter out all other namespaces):
|
||||
wlid://cluster-<cluster>/namespace-<namespace>/
|
||||
*/
|
||||
DesignatorWildWlid DesignatorType = "WildWlid"
|
||||
DesignatorWlidContainer DesignatorType = "WlidContainer"
|
||||
DesignatorWlidProcess DesignatorType = "WlidProcess"
|
||||
DesignatorSid DesignatorType = "Sid" // secret id
|
||||
)
|
||||
|
||||
func (dt DesignatorType) ToLower() DesignatorType {
|
||||
return DesignatorType(strings.ToLower(string(dt)))
|
||||
}
|
||||
|
||||
// attributes
|
||||
const (
|
||||
AttributeCluster = "cluster"
|
||||
AttributeNamespace = "namespace"
|
||||
AttributeKind = "kind"
|
||||
AttributeName = "name"
|
||||
)
|
||||
|
||||
// PortalDesignator represented single designation options
|
||||
type PortalDesignator struct {
|
||||
DesignatorType DesignatorType `json:"designatorType"`
|
||||
WLID string `json:"wlid"`
|
||||
WildWLID string `json:"wildwlid"`
|
||||
SID string `json:"sid"`
|
||||
Attributes map[string]string `json:"attributes"`
|
||||
}
|
||||
18
cautils/armotypes/portaltypes_mock.go
Normal file
18
cautils/armotypes/portaltypes_mock.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package armotypes
|
||||
|
||||
func MockPortalBase(customerGUID, name string, attributes map[string]interface{}) *PortalBase {
|
||||
if customerGUID == "" {
|
||||
customerGUID = "36b6f9e1-3b63-4628-994d-cbe16f81e9c7"
|
||||
}
|
||||
if name == "" {
|
||||
name = "portalbase-a"
|
||||
}
|
||||
if attributes == nil {
|
||||
attributes = make(map[string]interface{})
|
||||
}
|
||||
return &PortalBase{
|
||||
GUID: customerGUID,
|
||||
Name: name,
|
||||
Attributes: attributes,
|
||||
}
|
||||
}
|
||||
113
cautils/armotypes/portaltypesutils.go
Normal file
113
cautils/armotypes/portaltypesutils.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package armotypes
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/cautils/cautils"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
var IgnoreLabels = []string{AttributeCluster, AttributeNamespace}
|
||||
|
||||
func (designator *PortalDesignator) GetCluster() string {
|
||||
cluster, _, _, _, _ := designator.DigestPortalDesignator()
|
||||
return cluster
|
||||
}
|
||||
|
||||
func (designator *PortalDesignator) GetNamespace() string {
|
||||
_, namespace, _, _, _ := designator.DigestPortalDesignator()
|
||||
return namespace
|
||||
}
|
||||
|
||||
func (designator *PortalDesignator) GetKind() string {
|
||||
_, _, kind, _, _ := designator.DigestPortalDesignator()
|
||||
return kind
|
||||
}
|
||||
|
||||
func (designator *PortalDesignator) GetName() string {
|
||||
_, _, _, name, _ := designator.DigestPortalDesignator()
|
||||
return name
|
||||
}
|
||||
func (designator *PortalDesignator) GetLabels() map[string]string {
|
||||
_, _, _, _, labels := designator.DigestPortalDesignator()
|
||||
return labels
|
||||
}
|
||||
|
||||
// DigestPortalDesignator - get cluster namespace and labels from designator
|
||||
func (designator *PortalDesignator) DigestPortalDesignator() (string, string, string, string, map[string]string) {
|
||||
switch designator.DesignatorType.ToLower() {
|
||||
case DesignatorAttributes.ToLower(), DesignatorAttribute.ToLower():
|
||||
return designator.DigestAttributesDesignator()
|
||||
case DesignatorWlid.ToLower(), DesignatorWildWlid.ToLower():
|
||||
return cautils.GetClusterFromWlid(designator.WLID), cautils.GetNamespaceFromWlid(designator.WLID), cautils.GetKindFromWlid(designator.WLID), cautils.GetNameFromWlid(designator.WLID), map[string]string{}
|
||||
// case DesignatorSid: // TODO
|
||||
default:
|
||||
glog.Warningf("in 'digestPortalDesignator' designator type: '%v' not yet supported. please contact Armo team", designator.DesignatorType)
|
||||
}
|
||||
return "", "", "", "", nil
|
||||
}
|
||||
|
||||
func (designator *PortalDesignator) DigestAttributesDesignator() (string, string, string, string, map[string]string) {
|
||||
cluster := ""
|
||||
namespace := ""
|
||||
kind := ""
|
||||
name := ""
|
||||
labels := map[string]string{}
|
||||
attributes := designator.Attributes
|
||||
if attributes == nil {
|
||||
return cluster, namespace, kind, name, labels
|
||||
}
|
||||
for k, v := range attributes {
|
||||
labels[k] = v
|
||||
}
|
||||
if v, ok := attributes[AttributeNamespace]; ok {
|
||||
namespace = v
|
||||
delete(labels, AttributeNamespace)
|
||||
}
|
||||
if v, ok := attributes[AttributeCluster]; ok {
|
||||
cluster = v
|
||||
delete(labels, AttributeCluster)
|
||||
}
|
||||
if v, ok := attributes[AttributeKind]; ok {
|
||||
kind = v
|
||||
delete(labels, AttributeKind)
|
||||
}
|
||||
if v, ok := attributes[AttributeName]; ok {
|
||||
name = v
|
||||
delete(labels, AttributeName)
|
||||
}
|
||||
return cluster, namespace, kind, name, labels
|
||||
}
|
||||
|
||||
// DigestPortalDesignator DEPRECATED. use designator.DigestPortalDesignator() - get cluster namespace and labels from designator
|
||||
func DigestPortalDesignator(designator *PortalDesignator) (string, string, map[string]string) {
|
||||
switch designator.DesignatorType {
|
||||
case DesignatorAttributes, DesignatorAttribute:
|
||||
return DigestAttributesDesignator(designator.Attributes)
|
||||
case DesignatorWlid, DesignatorWildWlid:
|
||||
return cautils.GetClusterFromWlid(designator.WLID), cautils.GetNamespaceFromWlid(designator.WLID), map[string]string{}
|
||||
// case DesignatorSid: // TODO
|
||||
default:
|
||||
glog.Warningf("in 'digestPortalDesignator' designator type: '%v' not yet supported. please contact Armo team", designator.DesignatorType)
|
||||
}
|
||||
return "", "", nil
|
||||
}
|
||||
func DigestAttributesDesignator(attributes map[string]string) (string, string, map[string]string) {
|
||||
cluster := ""
|
||||
namespace := ""
|
||||
labels := map[string]string{}
|
||||
if attributes == nil {
|
||||
return cluster, namespace, labels
|
||||
}
|
||||
for k, v := range attributes {
|
||||
labels[k] = v
|
||||
}
|
||||
if v, ok := attributes[AttributeNamespace]; ok {
|
||||
namespace = v
|
||||
delete(labels, AttributeNamespace)
|
||||
}
|
||||
if v, ok := attributes[AttributeCluster]; ok {
|
||||
cluster = v
|
||||
delete(labels, AttributeCluster)
|
||||
}
|
||||
|
||||
return cluster, namespace, labels
|
||||
}
|
||||
42
cautils/armotypes/postureexceptionpolicytypes.go
Normal file
42
cautils/armotypes/postureexceptionpolicytypes.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package armotypes
|
||||
|
||||
type PostureExceptionPolicyActions string
|
||||
|
||||
const AlertOnly PostureExceptionPolicyActions = "alertOnly"
|
||||
const Disable PostureExceptionPolicyActions = "disable"
|
||||
|
||||
type PostureExceptionPolicy struct {
|
||||
PortalBase `json:",inline"`
|
||||
PolicyType string `json:"policyType"`
|
||||
CreationTime string `json:"creationTime"`
|
||||
Actions []PostureExceptionPolicyActions `json:"actions"`
|
||||
Resources []PortalDesignator `json:"resources"`
|
||||
PosturePolicies []PosturePolicy `json:"posturePolicies"`
|
||||
}
|
||||
|
||||
type PosturePolicy struct {
|
||||
FrameworkName string `json:"frameworkName"`
|
||||
ControlName string `json:"controlName"`
|
||||
RuleName string `json:"ruleName"`
|
||||
}
|
||||
|
||||
func (exceptionPolicy *PostureExceptionPolicy) IsAlertOnly() bool {
|
||||
if exceptionPolicy.IsDisable() {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range exceptionPolicy.Actions {
|
||||
if exceptionPolicy.Actions[i] == AlertOnly {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (exceptionPolicy *PostureExceptionPolicy) IsDisable() bool {
|
||||
for i := range exceptionPolicy.Actions {
|
||||
if exceptionPolicy.Actions[i] == Disable {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
1
cautils/armotypes/postureexceptionpolicytypesutils.go
Normal file
1
cautils/armotypes/postureexceptionpolicytypesutils.go
Normal file
@@ -0,0 +1 @@
|
||||
package armotypes
|
||||
196
cautils/cautils/armometadata.go
Normal file
196
cautils/cautils/armometadata.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// labels added to the workload
|
||||
const (
|
||||
ArmoPrefix string = "armo"
|
||||
ArmoAttach string = ArmoPrefix + ".attach"
|
||||
ArmoInitialSecret string = ArmoPrefix + ".initial"
|
||||
ArmoSecretStatus string = ArmoPrefix + ".secret"
|
||||
ArmoCompatibleLabel string = ArmoPrefix + ".compatible"
|
||||
|
||||
ArmoSecretProtectStatus string = "protect"
|
||||
ArmoSecretClearStatus string = "clear"
|
||||
)
|
||||
|
||||
// annotations added to the workload
|
||||
const (
|
||||
ArmoUpdate string = ArmoPrefix + ".last-update"
|
||||
ArmoWlid string = ArmoPrefix + ".wlid"
|
||||
ArmoSid string = ArmoPrefix + ".sid"
|
||||
ArmoJobID string = ArmoPrefix + ".job"
|
||||
ArmoJobIDPath string = ArmoJobID + "/id"
|
||||
ArmoJobParentPath string = ArmoJobID + "/parent"
|
||||
ArmoJobActionPath string = ArmoJobID + "/action"
|
||||
ArmoCompatibleAnnotation string = ArmoAttach + "/compatible"
|
||||
ArmoReplaceheaders string = ArmoAttach + "/replaceheaders"
|
||||
)
|
||||
|
||||
const ( // DEPRECATED
|
||||
|
||||
CAAttachLabel string = "cyberarmor"
|
||||
Patched string = "Patched"
|
||||
Done string = "Done"
|
||||
Encrypted string = "Protected"
|
||||
|
||||
CAInjectOld = "injectCyberArmor"
|
||||
|
||||
CAPrefix string = "cyberarmor"
|
||||
CAProtectedSecret string = CAPrefix + ".secret"
|
||||
CAInitialSecret string = CAPrefix + ".initial"
|
||||
CAInject string = CAPrefix + ".inject"
|
||||
CAIgnore string = CAPrefix + ".ignore"
|
||||
CAReplaceHeaders string = CAPrefix + ".removeSecurityHeaders"
|
||||
)
|
||||
|
||||
const ( // DEPRECATED
|
||||
CAUpdate string = CAPrefix + ".last-update"
|
||||
CAStatus string = CAPrefix + ".status"
|
||||
CAWlid string = CAPrefix + ".wlid"
|
||||
)
|
||||
|
||||
type ClusterConfig struct {
|
||||
EventReceiverREST string `json:"eventReceiverREST"`
|
||||
EventReceiverWS string `json:"eventReceiverWS"`
|
||||
MaserNotificationServer string `json:"maserNotificationServer"`
|
||||
Postman string `json:"postman"`
|
||||
Dashboard string `json:"dashboard"`
|
||||
Portal string `json:"portal"`
|
||||
CustomerGUID string `json:"customerGUID"`
|
||||
ClusterGUID string `json:"clusterGUID"`
|
||||
ClusterName string `json:"clusterName"`
|
||||
OciImageURL string `json:"ociImageURL"`
|
||||
NotificationWSURL string `json:"notificationWSURL"`
|
||||
NotificationRestURL string `json:"notificationRestURL"`
|
||||
VulnScanURL string `json:"vulnScanURL"`
|
||||
OracleURL string `json:"oracleURL"`
|
||||
ClairURL string `json:"clairURL"`
|
||||
}
|
||||
|
||||
// represents workload basic info
|
||||
type SpiffeBasicInfo struct {
|
||||
//cluster/datacenter
|
||||
Level0 string `json:"level0"`
|
||||
Level0Type string `json:"level0Type"`
|
||||
|
||||
//namespace/project
|
||||
Level1 string `json:"level0"`
|
||||
Level1Type string `json:"level0Type"`
|
||||
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ImageInfo struct {
|
||||
Registry string `json:"registry"`
|
||||
VersionImage string `json:"versionImage"`
|
||||
}
|
||||
|
||||
func IsAttached(labels map[string]string) *bool {
|
||||
attach := false
|
||||
if labels == nil {
|
||||
return nil
|
||||
}
|
||||
if attached, ok := labels[ArmoAttach]; ok {
|
||||
if strings.ToLower(attached) == "true" {
|
||||
attach = true
|
||||
return &attach
|
||||
} else {
|
||||
return &attach
|
||||
}
|
||||
}
|
||||
|
||||
// deprecated
|
||||
if _, ok := labels[CAAttachLabel]; ok {
|
||||
attach = true
|
||||
return &attach
|
||||
}
|
||||
|
||||
// deprecated
|
||||
if inject, ok := labels[CAInject]; ok {
|
||||
if strings.ToLower(inject) == "true" {
|
||||
attach = true
|
||||
return &attach
|
||||
}
|
||||
}
|
||||
|
||||
// deprecated
|
||||
if ignore, ok := labels[CAIgnore]; ok {
|
||||
if strings.ToLower(ignore) == "true" {
|
||||
return &attach
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsSecretProtected(labels map[string]string) *bool {
|
||||
protect := false
|
||||
if labels == nil {
|
||||
return nil
|
||||
}
|
||||
if protected, ok := labels[ArmoSecretStatus]; ok {
|
||||
if strings.ToLower(protected) == ArmoSecretProtectStatus {
|
||||
protect = true
|
||||
return &protect
|
||||
} else {
|
||||
return &protect
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadConfig(configPath string, loadToEnv bool) (*ClusterConfig, error) {
|
||||
if configPath == "" {
|
||||
configPath = "/etc/config/clusterData.json"
|
||||
}
|
||||
|
||||
dat, err := os.ReadFile(configPath)
|
||||
if err != nil || len(dat) == 0 {
|
||||
return nil, fmt.Errorf("Config empty or not found. path: %s", configPath)
|
||||
}
|
||||
componentConfig := &ClusterConfig{}
|
||||
if err := json.Unmarshal(dat, componentConfig); err != nil {
|
||||
return componentConfig, fmt.Errorf("Failed to read component config, path: %s, reason: %s", configPath, err.Error())
|
||||
}
|
||||
if loadToEnv {
|
||||
componentConfig.LoadConfigToEnv()
|
||||
}
|
||||
return componentConfig, nil
|
||||
}
|
||||
|
||||
func (clusterConfig *ClusterConfig) LoadConfigToEnv() {
|
||||
|
||||
SetEnv("CA_CLUSTER_NAME", clusterConfig.ClusterName)
|
||||
SetEnv("CA_CLUSTER_GUID", clusterConfig.ClusterGUID)
|
||||
SetEnv("CA_ORACLE_SERVER", clusterConfig.OracleURL)
|
||||
SetEnv("CA_CUSTOMER_GUID", clusterConfig.CustomerGUID)
|
||||
SetEnv("CA_DASHBOARD_BACKEND", clusterConfig.Dashboard)
|
||||
SetEnv("CA_NOTIFICATION_SERVER_REST", clusterConfig.NotificationWSURL)
|
||||
SetEnv("CA_NOTIFICATION_SERVER_WS", clusterConfig.NotificationWSURL)
|
||||
SetEnv("CA_NOTIFICATION_SERVER_REST", clusterConfig.NotificationRestURL)
|
||||
SetEnv("CA_OCIMAGE_URL", clusterConfig.OciImageURL)
|
||||
SetEnv("CA_K8S_REPORT_URL", clusterConfig.EventReceiverWS)
|
||||
SetEnv("CA_EVENT_RECEIVER_HTTP", clusterConfig.EventReceiverREST)
|
||||
SetEnv("CA_VULNSCAN", clusterConfig.VulnScanURL)
|
||||
SetEnv("CA_POSTMAN", clusterConfig.Postman)
|
||||
SetEnv("MASTER_NOTIFICATION_SERVER_HOST", clusterConfig.MaserNotificationServer)
|
||||
SetEnv("CLAIR_URL", clusterConfig.ClairURL)
|
||||
|
||||
}
|
||||
|
||||
func SetEnv(key, value string) {
|
||||
if e := os.Getenv(key); e == "" {
|
||||
if err := os.Setenv(key, value); err != nil {
|
||||
glog.Warning("%s: %s", key, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
29
cautils/cautils/cautils_test.go
Normal file
29
cautils/cautils/cautils_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// tests wlid parse
|
||||
|
||||
func TestSpiffeWLIDToInfoSuccess(t *testing.T) {
|
||||
|
||||
WLID := "wlid://cluster-HipsterShopCluster2/namespace-prod/deployment-cartservice"
|
||||
ms, er := SpiffeToSpiffeInfo(WLID)
|
||||
|
||||
if er != nil || ms.Level0 != "HipsterShopCluster2" || ms.Level0Type != "cluster" || ms.Level1 != "prod" || ms.Level1Type != "namespace" ||
|
||||
ms.Kind != "deployment" || ms.Name != "cartservice" {
|
||||
t.Errorf("TestSpiffeWLIDToInfoSuccess failed to parse %v", WLID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpiffeSIDInfoSuccess(t *testing.T) {
|
||||
|
||||
SID := "sid://cluster-HipsterShopCluster2/namespace-dev/secret-caregcred"
|
||||
ms, er := SpiffeToSpiffeInfo(SID)
|
||||
|
||||
if er != nil || ms.Level0 != "HipsterShopCluster2" || ms.Level0Type != "cluster" || ms.Level1 != "dev" || ms.Level1Type != "namespace" ||
|
||||
ms.Kind != "secret" || ms.Name != "caregcred" {
|
||||
t.Errorf("TestSpiffeSIDInfoSuccess failed to parse %v", SID)
|
||||
}
|
||||
}
|
||||
118
cautils/cautils/genericutils.go
Normal file
118
cautils/cautils/genericutils.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// wlid/ sid utils
|
||||
const (
|
||||
SpiffePrefix = "://"
|
||||
)
|
||||
|
||||
// wlid/ sid utils
|
||||
const (
|
||||
PackagePath = "vendor/github.com/armosec/capacketsgo"
|
||||
)
|
||||
|
||||
//AsSHA256 takes anything turns it into string :) https://blog.8bitzen.com/posts/22-08-2019-how-to-hash-a-struct-in-go
|
||||
func AsSHA256(v interface{}) string {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(fmt.Sprintf("%v", v)))
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
func SpiffeToSpiffeInfo(spiffe string) (*SpiffeBasicInfo, error) {
|
||||
basicInfo := &SpiffeBasicInfo{}
|
||||
|
||||
pos := strings.Index(spiffe, SpiffePrefix)
|
||||
if pos < 0 {
|
||||
return nil, fmt.Errorf("invalid spiffe %s", spiffe)
|
||||
}
|
||||
|
||||
pos += len(SpiffePrefix)
|
||||
spiffeNoPrefix := spiffe[pos:]
|
||||
splits := strings.Split(spiffeNoPrefix, "/")
|
||||
if len(splits) < 3 {
|
||||
return nil, fmt.Errorf("invalid spiffe %s", spiffe)
|
||||
}
|
||||
|
||||
p0 := strings.Index(splits[0], "-")
|
||||
p1 := strings.Index(splits[1], "-")
|
||||
p2 := strings.Index(splits[2], "-")
|
||||
if p0 == -1 || p1 == -1 || p2 == -1 {
|
||||
return nil, fmt.Errorf("invalid spiffe %s", spiffe)
|
||||
}
|
||||
basicInfo.Level0Type = splits[0][:p0]
|
||||
basicInfo.Level0 = splits[0][p0+1:]
|
||||
basicInfo.Level1Type = splits[1][:p1]
|
||||
basicInfo.Level1 = splits[1][p1+1:]
|
||||
basicInfo.Kind = splits[2][:p2]
|
||||
basicInfo.Name = splits[2][p2+1:]
|
||||
|
||||
return basicInfo, nil
|
||||
}
|
||||
|
||||
func ImageTagToImageInfo(imageTag string) (*ImageInfo, error) {
|
||||
ImageInfo := &ImageInfo{}
|
||||
spDelimiter := "/"
|
||||
pos := strings.Index(imageTag, spDelimiter)
|
||||
if pos < 0 {
|
||||
ImageInfo.Registry = ""
|
||||
ImageInfo.VersionImage = imageTag
|
||||
return ImageInfo, nil
|
||||
}
|
||||
|
||||
splits := strings.Split(imageTag, spDelimiter)
|
||||
if len(splits) == 0 {
|
||||
|
||||
return nil, fmt.Errorf("Invalid image info %s", imageTag)
|
||||
}
|
||||
|
||||
ImageInfo.Registry = splits[0]
|
||||
if len(splits) > 1 {
|
||||
ImageInfo.VersionImage = splits[len(splits)-1]
|
||||
} else {
|
||||
ImageInfo.VersionImage = ""
|
||||
}
|
||||
|
||||
return ImageInfo, nil
|
||||
}
|
||||
|
||||
func BoolPointer(b bool) *bool { return &b }
|
||||
|
||||
func BoolToString(b bool) string {
|
||||
if b {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func BoolPointerToString(b *bool) string {
|
||||
if b == nil {
|
||||
return ""
|
||||
}
|
||||
if *b {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func StringToBool(s string) bool {
|
||||
if strings.ToLower(s) == "true" || strings.ToLower(s) == "1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func StringToBoolPointer(s string) *bool {
|
||||
if strings.ToLower(s) == "true" || strings.ToLower(s) == "1" {
|
||||
return BoolPointer(true)
|
||||
}
|
||||
if strings.ToLower(s) == "false" || strings.ToLower(s) == "0" {
|
||||
return BoolPointer(false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
52
cautils/cautils/k8sutils.go
Normal file
52
cautils/cautils/k8sutils.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var NamespacesListToIgnore = make([]string, 0)
|
||||
var KubeNamespaces = []string{metav1.NamespaceSystem, metav1.NamespacePublic}
|
||||
|
||||
// NamespacesListToIgnore namespaces to ignore if a pod
|
||||
func InitNamespacesListToIgnore(caNamespace string) {
|
||||
if len(NamespacesListToIgnore) > 0 {
|
||||
return
|
||||
}
|
||||
NamespacesListToIgnore = append(NamespacesListToIgnore, KubeNamespaces...)
|
||||
NamespacesListToIgnore = append(NamespacesListToIgnore, caNamespace)
|
||||
}
|
||||
|
||||
func IfIgnoreNamespace(ns string) bool {
|
||||
for i := range NamespacesListToIgnore {
|
||||
if NamespacesListToIgnore[i] == ns {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IfKubeNamespace(ns string) bool {
|
||||
for i := range KubeNamespaces {
|
||||
if NamespacesListToIgnore[i] == ns {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hash(s string) string {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(s))
|
||||
return fmt.Sprintf("%d", h.Sum32())
|
||||
}
|
||||
func GenarateConfigMapName(wlid string) string {
|
||||
name := strings.ToLower(fmt.Sprintf("ca-%s-%s-%s", GetNamespaceFromWlid(wlid), GetKindFromWlid(wlid), GetNameFromWlid(wlid)))
|
||||
if len(name) >= 63 {
|
||||
name = hash(name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
238
cautils/cautils/wlid.go
Normal file
238
cautils/cautils/wlid.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// API fields
|
||||
var (
|
||||
WlidPrefix = "wlid://"
|
||||
SidPrefix = "sid://"
|
||||
ClusterWlidPrefix = "cluster-"
|
||||
NamespaceWlidPrefix = "namespace-"
|
||||
DataCenterWlidPrefix = "datacenter-"
|
||||
ProjectWlidPrefix = "project-"
|
||||
SecretSIDPrefix = "secret-"
|
||||
SubSecretSIDPrefix = "subsecret-"
|
||||
K8SKindsList = []string{"ComponentStatus", "ConfigMap", "ControllerRevision", "CronJob",
|
||||
"CustomResourceDefinition", "DaemonSet", "Deployment", "Endpoints", "Event", "HorizontalPodAutoscaler",
|
||||
"Ingress", "Job", "Lease", "LimitRange", "LocalSubjectAccessReview", "MutatingWebhookConfiguration",
|
||||
"Namespace", "NetworkPolicy", "Node", "PersistentVolume", "PersistentVolumeClaim", "Pod",
|
||||
"PodDisruptionBudget", "PodSecurityPolicy", "PodTemplate", "PriorityClass", "ReplicaSet",
|
||||
"ReplicationController", "ResourceQuota", "Role", "RoleBinding", "Secret", "SelfSubjectAccessReview",
|
||||
"SelfSubjectRulesReview", "Service", "ServiceAccount", "StatefulSet", "StorageClass",
|
||||
"SubjectAccessReview", "TokenReview", "ValidatingWebhookConfiguration", "VolumeAttachment"}
|
||||
NativeKindsList = []string{"Dockerized", "Native"}
|
||||
KindReverseMap = map[string]string{}
|
||||
dataImagesList = []string{}
|
||||
)
|
||||
|
||||
func IsWlid(id string) bool {
|
||||
return strings.HasPrefix(id, WlidPrefix)
|
||||
}
|
||||
|
||||
func IsSid(id string) bool {
|
||||
return strings.HasPrefix(id, SidPrefix)
|
||||
}
|
||||
|
||||
// GetK8SKindFronList get the calculated wlid
|
||||
func GetK8SKindFronList(kind string) string { // TODO GetK8SKindFromList
|
||||
for i := range K8SKindsList {
|
||||
if strings.ToLower(kind) == strings.ToLower(K8SKindsList[i]) {
|
||||
return K8SKindsList[i]
|
||||
}
|
||||
}
|
||||
return kind
|
||||
}
|
||||
|
||||
// IsK8SKindInList Check if the kind is a known kind
|
||||
func IsK8SKindInList(kind string) bool {
|
||||
for i := range K8SKindsList {
|
||||
if strings.ToLower(kind) == strings.ToLower(K8SKindsList[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// generateWLID
|
||||
func generateWLID(pLevel0, level0, pLevel1, level1, k, name string) string {
|
||||
kind := strings.ToLower(k)
|
||||
kind = strings.Replace(kind, "-", "", -1)
|
||||
|
||||
wlid := WlidPrefix
|
||||
wlid += fmt.Sprintf("%s%s", pLevel0, level0)
|
||||
if level1 == "" {
|
||||
return wlid
|
||||
}
|
||||
wlid += fmt.Sprintf("/%s%s", pLevel1, level1)
|
||||
|
||||
if kind == "" {
|
||||
return wlid
|
||||
}
|
||||
wlid += fmt.Sprintf("/%s", kind)
|
||||
|
||||
if name == "" {
|
||||
return wlid
|
||||
}
|
||||
wlid += fmt.Sprintf("-%s", name)
|
||||
|
||||
return wlid
|
||||
}
|
||||
|
||||
// GetWLID get the calculated wlid
|
||||
func GetWLID(level0, level1, k, name string) string {
|
||||
return generateWLID(ClusterWlidPrefix, level0, NamespaceWlidPrefix, level1, k, name)
|
||||
}
|
||||
|
||||
// GetK8sWLID get the k8s calculated wlid
|
||||
func GetK8sWLID(level0, level1, k, name string) string {
|
||||
return generateWLID(ClusterWlidPrefix, level0, NamespaceWlidPrefix, level1, k, name)
|
||||
}
|
||||
|
||||
// GetNativeWLID get the native calculated wlid
|
||||
func GetNativeWLID(level0, level1, k, name string) string {
|
||||
return generateWLID(DataCenterWlidPrefix, level0, ProjectWlidPrefix, level1, k, name)
|
||||
}
|
||||
|
||||
// WildWlidContainsWlid does WildWlid contains Wlid
|
||||
func WildWlidContainsWlid(wildWlid, wlid string) bool { // TODO- test
|
||||
if wildWlid == wlid {
|
||||
return true
|
||||
}
|
||||
wildWlidR, _ := RestoreMicroserviceIDsFromSpiffe(wildWlid)
|
||||
wlidR, _ := RestoreMicroserviceIDsFromSpiffe(wlid)
|
||||
if len(wildWlidR) > len(wildWlidR) {
|
||||
// invalid wlid
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range wildWlidR {
|
||||
if wildWlidR[i] != wlidR[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func restoreInnerIdentifiersFromID(spiffeSlices []string) []string {
|
||||
if len(spiffeSlices) >= 1 && strings.HasPrefix(spiffeSlices[0], ClusterWlidPrefix) {
|
||||
spiffeSlices[0] = spiffeSlices[0][len(ClusterWlidPrefix):]
|
||||
}
|
||||
if len(spiffeSlices) >= 2 && strings.HasPrefix(spiffeSlices[1], NamespaceWlidPrefix) {
|
||||
spiffeSlices[1] = spiffeSlices[1][len(NamespaceWlidPrefix):]
|
||||
}
|
||||
if len(spiffeSlices) >= 3 && strings.Contains(spiffeSlices[2], "-") {
|
||||
dashIdx := strings.Index(spiffeSlices[2], "-")
|
||||
spiffeSlices = append(spiffeSlices, spiffeSlices[2][dashIdx+1:])
|
||||
spiffeSlices[2] = spiffeSlices[2][:dashIdx]
|
||||
if val, ok := KindReverseMap[spiffeSlices[2]]; ok {
|
||||
spiffeSlices[2] = val
|
||||
}
|
||||
}
|
||||
return spiffeSlices
|
||||
}
|
||||
|
||||
// RestoreMicroserviceIDsFromSpiffe -
|
||||
func RestoreMicroserviceIDsFromSpiffe(spiffe string) ([]string, error) {
|
||||
if spiffe == "" {
|
||||
return nil, fmt.Errorf("in RestoreMicroserviceIDsFromSpiffe, expecting valid wlid recieved empty string")
|
||||
}
|
||||
|
||||
if StringHasWhitespace(spiffe) {
|
||||
return nil, fmt.Errorf("wlid %s invalid. whitespace found", spiffe)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(spiffe, WlidPrefix) {
|
||||
spiffe = spiffe[len(WlidPrefix):]
|
||||
} else if strings.HasPrefix(spiffe, SidPrefix) {
|
||||
spiffe = spiffe[len(SidPrefix):]
|
||||
}
|
||||
spiffeSlices := strings.Split(spiffe, "/")
|
||||
// The documented WLID format (https://cyberarmorio.sharepoint.com/sites/development2/Shared%20Documents/kubernetes_design1.docx?web=1)
|
||||
if len(spiffeSlices) <= 3 {
|
||||
spiffeSlices = restoreInnerIdentifiersFromID(spiffeSlices)
|
||||
}
|
||||
if len(spiffeSlices) != 4 { // first used WLID, deprecated since 24.10.2019
|
||||
return spiffeSlices, fmt.Errorf("invalid WLID format. format received: %v", spiffeSlices)
|
||||
}
|
||||
|
||||
for i := range spiffeSlices {
|
||||
if spiffeSlices[i] == "" {
|
||||
return spiffeSlices, fmt.Errorf("one or more entities are empty, spiffeSlices: %v", spiffeSlices)
|
||||
}
|
||||
}
|
||||
|
||||
return spiffeSlices, nil
|
||||
}
|
||||
|
||||
// RestoreMicroserviceIDsFromSpiffe -
|
||||
func RestoreMicroserviceIDs(spiffe string) []string {
|
||||
if spiffe == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if StringHasWhitespace(spiffe) {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(spiffe, WlidPrefix) {
|
||||
spiffe = spiffe[len(WlidPrefix):]
|
||||
} else if strings.HasPrefix(spiffe, SidPrefix) {
|
||||
spiffe = spiffe[len(SidPrefix):]
|
||||
}
|
||||
spiffeSlices := strings.Split(spiffe, "/")
|
||||
|
||||
return restoreInnerIdentifiersFromID(spiffeSlices)
|
||||
}
|
||||
|
||||
// GetClusterFromWlid parse wlid and get cluster
|
||||
func GetClusterFromWlid(wlid string) string {
|
||||
r := RestoreMicroserviceIDs(wlid)
|
||||
if len(r) >= 1 {
|
||||
return r[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetNamespaceFromWlid parse wlid and get Namespace
|
||||
func GetNamespaceFromWlid(wlid string) string {
|
||||
r := RestoreMicroserviceIDs(wlid)
|
||||
if len(r) >= 2 {
|
||||
return r[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetKindFromWlid parse wlid and get kind
|
||||
func GetKindFromWlid(wlid string) string {
|
||||
r := RestoreMicroserviceIDs(wlid)
|
||||
if len(r) >= 3 {
|
||||
return GetK8SKindFronList(r[2])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetNameFromWlid parse wlid and get name
|
||||
func GetNameFromWlid(wlid string) string {
|
||||
r := RestoreMicroserviceIDs(wlid)
|
||||
if len(r) >= 4 {
|
||||
return GetK8SKindFronList(r[3])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsWlidValid test if wlid is a valid wlid
|
||||
func IsWlidValid(wlid string) error {
|
||||
_, err := RestoreMicroserviceIDsFromSpiffe(wlid)
|
||||
return err
|
||||
}
|
||||
|
||||
// StringHasWhitespace check if a string has whitespace
|
||||
func StringHasWhitespace(str string) bool {
|
||||
if whitespace := strings.Index(str, " "); whitespace != -1 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
324
cautils/customerloader.go
Normal file
324
cautils/customerloader.go
Normal file
@@ -0,0 +1,324 @@
|
||||
package cautils
|
||||
|
||||
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/kubescape/cautils/k8sinterface"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
configMapName = "kubescape"
|
||||
ConfigFileName = "config"
|
||||
)
|
||||
|
||||
type ConfigObj struct {
|
||||
CustomerGUID string `json:"customerGUID"`
|
||||
Token string `json:"invitationParam"`
|
||||
CustomerAdminEMail string `json:"adminMail"`
|
||||
}
|
||||
|
||||
func (co *ConfigObj) Json() []byte {
|
||||
if b, err := json.Marshal(co); err == nil {
|
||||
return b
|
||||
}
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
type IClusterConfig interface {
|
||||
SetCustomerGUID() error
|
||||
GetCustomerGUID() string
|
||||
GenerateURL()
|
||||
}
|
||||
|
||||
type ClusterConfig struct {
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
defaultNS string
|
||||
armoAPI *getter.ArmoAPI
|
||||
configObj *ConfigObj
|
||||
}
|
||||
|
||||
type EmptyConfig struct {
|
||||
}
|
||||
|
||||
func (c *EmptyConfig) GenerateURL() {
|
||||
}
|
||||
|
||||
func (c *EmptyConfig) SetCustomerGUID() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *EmptyConfig) GetCustomerGUID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewEmptyConfig() *EmptyConfig {
|
||||
return &EmptyConfig{}
|
||||
}
|
||||
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, armoAPI *getter.ArmoAPI) *ClusterConfig {
|
||||
return &ClusterConfig{
|
||||
k8s: k8s,
|
||||
armoAPI: armoAPI,
|
||||
defaultNS: k8sinterface.GetDefaultNamespace(),
|
||||
}
|
||||
}
|
||||
func createConfigJson() {
|
||||
os.WriteFile(getter.GetDefaultPath(ConfigFileName+".json"), nil, 0664)
|
||||
|
||||
}
|
||||
|
||||
func update(configObj *ConfigObj) {
|
||||
os.WriteFile(getter.GetDefaultPath(ConfigFileName+".json"), configObj.Json(), 0664)
|
||||
}
|
||||
func (c *ClusterConfig) GenerateURL() {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = getter.ArmoFEURL
|
||||
if c.configObj == nil {
|
||||
return
|
||||
}
|
||||
if c.configObj.CustomerAdminEMail != "" {
|
||||
msgStr := fmt.Sprintf("To view all controls and get remediations ask access permissions to %s from %s", u.String(), c.configObj.CustomerAdminEMail)
|
||||
InfoTextDisplay(os.Stdout, msgStr+"\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()
|
||||
fmt.Println("To view all controls and get remediations visit:")
|
||||
InfoTextDisplay(os.Stdout, u.String()+"\n")
|
||||
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetCustomerGUID() string {
|
||||
if c.configObj != nil {
|
||||
return c.configObj.CustomerGUID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetValueByKeyFromConfigMap(key string) (string, error) {
|
||||
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if val, ok := configMap.Data[key]; ok {
|
||||
return val, nil
|
||||
} else {
|
||||
return "", fmt.Errorf("value does not exist")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func GetValueFromConfigJson(key string) (string, error) {
|
||||
data, err := os.ReadFile(getter.GetDefaultPath(ConfigFileName + ".json"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var obj map[string]interface{}
|
||||
err = json.Unmarshal(data, &obj)
|
||||
if val, ok := obj[key]; ok {
|
||||
return fmt.Sprint(val), nil
|
||||
} else {
|
||||
return "", fmt.Errorf("value does not exist")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func SetKeyValueInConfigJson(key string, value string) error {
|
||||
data, err := os.ReadFile(getter.GetDefaultPath(ConfigFileName + ".json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var obj map[string]interface{}
|
||||
err = json.Unmarshal(data, &obj)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj[key] = value
|
||||
newData, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(getter.GetDefaultPath(ConfigFileName+".json"), newData, 0664)
|
||||
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
|
||||
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
configMap = &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: configMapName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if len(configMap.Data) == 0 {
|
||||
configMap.Data = make(map[string]string)
|
||||
}
|
||||
|
||||
configMap.Data[key] = value
|
||||
|
||||
if err != nil {
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
} else {
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(configMap.Namespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) SetCustomerGUID() error {
|
||||
|
||||
// get from configMap
|
||||
if c.existsConfigMap() {
|
||||
c.configObj, _ = c.loadConfigFromConfigMap()
|
||||
} else if existsConfigJson() { // get from file
|
||||
c.configObj, _ = loadConfigFromFile()
|
||||
} else {
|
||||
c.createConfigMap()
|
||||
createConfigJson()
|
||||
}
|
||||
|
||||
customerGUID := c.GetCustomerGUID()
|
||||
|
||||
// get from armoBE
|
||||
tenantResponse, err := c.armoAPI.GetCustomerGUID(customerGUID)
|
||||
if err == nil && tenantResponse != nil {
|
||||
if tenantResponse.AdminMail != "" { // this customer already belongs to some user
|
||||
if existsConfigJson() {
|
||||
update(&ConfigObj{CustomerGUID: customerGUID, CustomerAdminEMail: tenantResponse.AdminMail})
|
||||
}
|
||||
if c.existsConfigMap() {
|
||||
c.configObj.CustomerAdminEMail = tenantResponse.AdminMail
|
||||
c.updateConfigMap()
|
||||
}
|
||||
} else {
|
||||
if existsConfigJson() {
|
||||
update(&ConfigObj{CustomerGUID: tenantResponse.TenantID, Token: tenantResponse.Token})
|
||||
}
|
||||
if c.existsConfigMap() {
|
||||
c.configObj = &ConfigObj{CustomerGUID: tenantResponse.TenantID, Token: tenantResponse.Token}
|
||||
c.updateConfigMap()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != nil && strings.Contains(err.Error(), "already exists") {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bData, err := json.Marshal(configMap.Data); err == nil {
|
||||
return readConfig(bData)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) existsConfigMap() bool {
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func existsConfigJson() bool {
|
||||
_, err := os.ReadFile(getter.GetDefaultPath(ConfigFileName + ".json"))
|
||||
|
||||
return err == nil
|
||||
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) createConfigMap() error {
|
||||
if c.k8s == nil {
|
||||
return nil
|
||||
}
|
||||
configMap := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: configMapName,
|
||||
},
|
||||
}
|
||||
c.updateConfigData(configMap)
|
||||
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
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{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.updateConfigData(configMap)
|
||||
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(configMap.Namespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
|
||||
if len(configMap.Data) == 0 {
|
||||
configMap.Data = make(map[string]string)
|
||||
}
|
||||
m := c.ToMapString()
|
||||
for k, v := range m {
|
||||
if s, ok := v.(string); ok {
|
||||
configMap.Data[k] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
func loadConfigFromFile() (*ConfigObj, error) {
|
||||
dat, err := os.ReadFile(getter.GetDefaultPath(ConfigFileName + ".json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return readConfig(dat)
|
||||
}
|
||||
func readConfig(dat []byte) (*ConfigObj, error) {
|
||||
|
||||
if len(dat) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
configObj := &ConfigObj{}
|
||||
err := json.Unmarshal(dat, configObj)
|
||||
|
||||
return configObj, err
|
||||
}
|
||||
func (c *ClusterConfig) ToMapString() map[string]interface{} {
|
||||
m := map[string]interface{}{}
|
||||
bc, _ := json.Marshal(c.configObj)
|
||||
json.Unmarshal(bc, &m)
|
||||
return m
|
||||
}
|
||||
51
cautils/datastructures.go
Normal file
51
cautils/datastructures.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/cautils/armotypes"
|
||||
"github.com/armosec/kubescape/cautils/opapolicy"
|
||||
)
|
||||
|
||||
// K8SResources map[<api group>/<api version>/<resource>]<resource object>
|
||||
type K8SResources map[string]interface{}
|
||||
|
||||
type OPASessionObj struct {
|
||||
Frameworks []opapolicy.Framework
|
||||
K8SResources *K8SResources
|
||||
Exceptions []armotypes.PostureExceptionPolicy
|
||||
PostureReport *opapolicy.PostureReport
|
||||
}
|
||||
|
||||
func NewOPASessionObj(frameworks []opapolicy.Framework, k8sResources *K8SResources) *OPASessionObj {
|
||||
return &OPASessionObj{
|
||||
Frameworks: frameworks,
|
||||
K8SResources: k8sResources,
|
||||
PostureReport: &opapolicy.PostureReport{
|
||||
ClusterName: ClusterName,
|
||||
CustomerGUID: CustomerGUID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewOPASessionObjMock() *OPASessionObj {
|
||||
return &OPASessionObj{
|
||||
Frameworks: nil,
|
||||
K8SResources: nil,
|
||||
PostureReport: &opapolicy.PostureReport{
|
||||
ClusterName: "",
|
||||
CustomerGUID: "",
|
||||
ReportID: "",
|
||||
JobID: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type ComponentConfig struct {
|
||||
Exceptions Exception `json:"exceptions"`
|
||||
}
|
||||
|
||||
type Exception struct {
|
||||
Ignore *bool `json:"ignore"` // ignore test results
|
||||
MultipleScore *opapolicy.AlertScore `json:"multipleScore"` // MultipleScore number - float32
|
||||
Namespaces []string `json:"namespaces"`
|
||||
Regex string `json:"regex"` // not supported
|
||||
}
|
||||
79
cautils/display.go
Normal file
79
cautils/display.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package cautils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/fatih/color"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var silent = false
|
||||
|
||||
func SetSilentMode(s bool) {
|
||||
silent = s
|
||||
}
|
||||
|
||||
func IsSilent() bool {
|
||||
return silent
|
||||
}
|
||||
|
||||
var FailureDisplay = color.New(color.Bold, color.FgHiRed).FprintfFunc()
|
||||
var WarningDisplay = color.New(color.Bold, color.FgCyan).FprintfFunc()
|
||||
var FailureTextDisplay = color.New(color.Faint, color.FgHiRed).FprintfFunc()
|
||||
var InfoDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
|
||||
var InfoTextDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
|
||||
var SimpleDisplay = color.New().FprintfFunc()
|
||||
var SuccessDisplay = color.New(color.Bold, color.FgHiGreen).FprintfFunc()
|
||||
var DescriptionDisplay = color.New(color.Faint, color.FgWhite).FprintfFunc()
|
||||
|
||||
var Spinner *spinner.Spinner
|
||||
|
||||
func ScanStartDisplay() {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
InfoDisplay(os.Stdout, "ARMO security scanner starting\n")
|
||||
}
|
||||
|
||||
func SuccessTextDisplay(str string) {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
SuccessDisplay(os.Stdout, "[success] ")
|
||||
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
|
||||
|
||||
}
|
||||
|
||||
func ErrorDisplay(str string) {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
SuccessDisplay(os.Stdout, "[Error] ")
|
||||
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
|
||||
|
||||
}
|
||||
|
||||
func ProgressTextDisplay(str string) {
|
||||
if IsSilent() {
|
||||
return
|
||||
}
|
||||
InfoDisplay(os.Stdout, "[progress] ")
|
||||
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
|
||||
|
||||
}
|
||||
func StartSpinner() {
|
||||
if !IsSilent() && isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
Spinner = spinner.New(spinner.CharSets[7], 100*time.Millisecond) // Build our new spinner
|
||||
Spinner.Start()
|
||||
}
|
||||
}
|
||||
|
||||
func StopSpinner() {
|
||||
if Spinner == nil {
|
||||
return
|
||||
}
|
||||
Spinner.Stop()
|
||||
}
|
||||
6
cautils/downloadinfo.go
Normal file
6
cautils/downloadinfo.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package cautils
|
||||
|
||||
type DownloadInfo struct {
|
||||
Path string
|
||||
FrameworkName string
|
||||
}
|
||||
11
cautils/environments.go
Normal file
11
cautils/environments.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package cautils
|
||||
|
||||
// CA environment vars
|
||||
var (
|
||||
CustomerGUID = ""
|
||||
ClusterName = ""
|
||||
EventReceiverURL = ""
|
||||
NotificationServerURL = ""
|
||||
DashboardBackendURL = ""
|
||||
RestAPIPort = "4001"
|
||||
)
|
||||
88
cautils/getter/armoapi.go
Normal file
88
cautils/getter/armoapi.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/armotypes"
|
||||
"github.com/armosec/kubescape/cautils/opapolicy"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
// =============================================== ArmoAPI ===============================================================
|
||||
// =======================================================================================================================
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// Armo API for downloading policies
|
||||
type ArmoAPI struct {
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func NewArmoAPI() *ArmoAPI {
|
||||
return &ArmoAPI{
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
}
|
||||
func (armoAPI *ArmoAPI) GetFramework(name string) (*opapolicy.Framework, error) {
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getFrameworkURL(name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
framework := &opapolicy.Framework{}
|
||||
if err = JSONDecoder(respStr).Decode(framework); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
SaveFrameworkInFile(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
|
||||
}
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getExceptionsURL(customerGUID, clusterName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = JSONDecoder(respStr).Decode(&exceptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exceptions, nil
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) GetCustomerGUID(customerGUID string) (*TenantResponse, error) {
|
||||
url := armoAPI.getCustomerURL()
|
||||
if customerGUID != "" {
|
||||
url = fmt.Sprintf("%s?customerGUID=%s", url, customerGUID)
|
||||
}
|
||||
respStr, err := HttpGetter(armoAPI.httpClient, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant := &TenantResponse{}
|
||||
if err = JSONDecoder(respStr).Decode(tenant); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
type TenantResponse struct {
|
||||
TenantID string `json:"tenantId"`
|
||||
Token string `json:"token"`
|
||||
Expires string `json:"expires"`
|
||||
AdminMail string `json:"adminMail,omitempty"`
|
||||
}
|
||||
44
cautils/getter/armoapiutils.go
Normal file
44
cautils/getter/armoapiutils.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = ArmoBEURL
|
||||
u.Path = "v1/armoFrameworks"
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", "11111111-1111-1111-1111-111111111111")
|
||||
q.Add("frameworkName", strings.ToUpper(frameworkName))
|
||||
q.Add("getRules", "true")
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) string {
|
||||
u := url.URL{}
|
||||
u.Scheme = "https"
|
||||
u.Host = ArmoBEURL
|
||||
u.Path = "api/v1/armoPostureExceptions"
|
||||
|
||||
q := u.Query()
|
||||
q.Add("customerGUID", 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.Path = "api/v1/createTenant"
|
||||
return u.String()
|
||||
}
|
||||
85
cautils/getter/downloadreleasedpolicy.go
Normal file
85
cautils/getter/downloadreleasedpolicy.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/opapolicy"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
// ======================================== DownloadReleasedPolicy =======================================================
|
||||
// =======================================================================================================================
|
||||
|
||||
// Download released version
|
||||
type DownloadReleasedPolicy struct {
|
||||
hostURL string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func NewDownloadReleasedPolicy() *DownloadReleasedPolicy {
|
||||
return &DownloadReleasedPolicy{
|
||||
hostURL: "",
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
func (drp *DownloadReleasedPolicy) GetFramework(name string) (*opapolicy.Framework, error) {
|
||||
if err := drp.setURL(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
respStr, err := HttpGetter(drp.httpClient, drp.hostURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
framework := &opapolicy.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)
|
||||
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)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("failed to download '%s' - not found", frameworkName)
|
||||
|
||||
}
|
||||
14
cautils/getter/getpolicies.go
Normal file
14
cautils/getter/getpolicies.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"github.com/armosec/kubescape/cautils/armotypes"
|
||||
"github.com/armosec/kubescape/cautils/opapolicy"
|
||||
)
|
||||
|
||||
type IPolicyGetter interface {
|
||||
GetFramework(name string) (*opapolicy.Framework, error)
|
||||
}
|
||||
|
||||
type IExceptionsGetter interface {
|
||||
GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error)
|
||||
}
|
||||
114
cautils/getter/getpoliciesutils.go
Normal file
114
cautils/getter/getpoliciesutils.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/opapolicy"
|
||||
)
|
||||
|
||||
func GetDefaultPath(name string) string {
|
||||
defaultfilePath := filepath.Join(DefaultLocalStore, name)
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
defaultfilePath = filepath.Join(homeDir, defaultfilePath)
|
||||
}
|
||||
return defaultfilePath
|
||||
}
|
||||
|
||||
func SaveFrameworkInFile(framework *opapolicy.Framework, path string) error {
|
||||
encodedData, err := json.Marshal(framework)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(path, []byte(fmt.Sprintf("%v", string(encodedData))), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// JSONDecoder returns JSON decoder for given string
|
||||
func JSONDecoder(origin string) *json.Decoder {
|
||||
dec := json.NewDecoder(strings.NewReader(origin))
|
||||
dec.UseNumber()
|
||||
return dec
|
||||
}
|
||||
|
||||
func HttpGetter(httpClient *http.Client, fullURL string) (string, error) {
|
||||
|
||||
req, err := http.NewRequest("GET", fullURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
respStr, err := httpRespToString(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return respStr, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return "", nil
|
||||
}
|
||||
strBuilder := strings.Builder{}
|
||||
defer resp.Body.Close()
|
||||
if resp.ContentLength > 0 {
|
||||
strBuilder.Grow(int(resp.ContentLength))
|
||||
}
|
||||
bytesNum, err := io.Copy(&strBuilder, resp.Body)
|
||||
respStr := strBuilder.String()
|
||||
if err != nil {
|
||||
respStrNewLen := len(respStr)
|
||||
if respStrNewLen > 1024 {
|
||||
respStrNewLen = 1024
|
||||
}
|
||||
return "", fmt.Errorf("HTTP request failed. URL: '%s', Read-ERROR: '%s', HTTP-CODE: '%s', BODY(top): '%s', HTTP-HEADERS: %v, HTTP-BODY-BUFFER-LENGTH: %v", resp.Request.URL.RequestURI(), err, resp.Status, respStr[:respStrNewLen], resp.Header, bytesNum)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
respStrNewLen := len(respStr)
|
||||
if respStrNewLen > 1024 {
|
||||
respStrNewLen = 1024
|
||||
}
|
||||
err = fmt.Errorf("HTTP request failed. URL: '%s', HTTP-ERROR: '%s', BODY: '%s', HTTP-HEADERS: %v, HTTP-BODY-BUFFER-LENGTH: %v", resp.Request.URL.RequestURI(), resp.Status, respStr[:respStrNewLen], resp.Header, bytesNum)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
54
cautils/getter/loadpolicy.go
Normal file
54
cautils/getter/loadpolicy.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/armotypes"
|
||||
"github.com/armosec/kubescape/cautils/opapolicy"
|
||||
)
|
||||
|
||||
// =======================================================================================================================
|
||||
// ============================================== LoadPolicy =============================================================
|
||||
// =======================================================================================================================
|
||||
const DefaultLocalStore = ".kubescape"
|
||||
|
||||
// Load policies from a local repository
|
||||
type LoadPolicy struct {
|
||||
filePath string
|
||||
}
|
||||
|
||||
func NewLoadPolicy(filePath string) *LoadPolicy {
|
||||
return &LoadPolicy{
|
||||
filePath: filePath,
|
||||
}
|
||||
}
|
||||
|
||||
func (lp *LoadPolicy) GetFramework(frameworkName string) (*opapolicy.Framework, error) {
|
||||
|
||||
framework := &opapolicy.Framework{}
|
||||
f, err := os.ReadFile(lp.filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(f, framework)
|
||||
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) {
|
||||
|
||||
exception := []armotypes.PostureExceptionPolicy{}
|
||||
f, err := os.ReadFile(lp.filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(f, &exception)
|
||||
return exception, err
|
||||
}
|
||||
265
cautils/k8sinterface/cloudvendorregistrycreds.go
Normal file
265
cautils/k8sinterface/cloudvendorregistrycreds.go
Normal file
@@ -0,0 +1,265 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ecr"
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
// For GCR there are some permissions one need to assign in order to allow ARMO to pull images:
|
||||
// https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity
|
||||
// gcloud iam service-accounts create armo-controller-sa
|
||||
// gcloud projects add-iam-policy-binding <PROJECT_NAME> --role roles/storage.objectViewer --member "serviceAccount:armo-controller-sa@<PROJECT_NAME>.iam.gserviceaccount.com"
|
||||
// gcloud iam service-accounts add-iam-policy-binding --role roles/iam.workloadIdentityUser --member "serviceAccount:<PROJECT_NAME>.svc.id.goog[cyberarmor-system/ca-controller-service-account]" armo-controller-sa@<PROJECT_NAME>.iam.gserviceaccount.com
|
||||
// kubectl annotate serviceaccount --overwrite --namespace cyberarmor-system ca-controller-service-account iam.gke.io/gcp-service-account=armo-controller-sa@<PROJECT_NAME>.iam.gserviceaccount.com
|
||||
|
||||
const (
|
||||
gcrDefaultServiceAccountName = "default"
|
||||
// armoServiceAccountName = "ca-controller-service-account"
|
||||
)
|
||||
|
||||
var (
|
||||
httpClient = http.Client{Timeout: 5 * time.Second}
|
||||
)
|
||||
|
||||
// CheckIsECRImage check if this image is suspected as ECR hosted image
|
||||
func CheckIsECRImage(imageTag string) bool {
|
||||
return strings.Contains(imageTag, "dkr.ecr")
|
||||
}
|
||||
|
||||
// GetLoginDetailsForECR return user name + password using the default iam-role OR ~/.aws/config of the machine
|
||||
func GetLoginDetailsForECR(imageTag string) (string, string, error) {
|
||||
// imageTag := "015253967648.dkr.ecr.eu-central-1.amazonaws.com/armo:1"
|
||||
imageTagSlices := strings.Split(imageTag, ".")
|
||||
repo := imageTagSlices[0]
|
||||
region := imageTagSlices[3]
|
||||
mySession := session.Must(session.NewSession())
|
||||
ecrClient := ecr.New(mySession, aws.NewConfig().WithRegion(region))
|
||||
input := &ecr.GetAuthorizationTokenInput{
|
||||
RegistryIds: []*string{&repo},
|
||||
}
|
||||
res, err := ecrClient.GetAuthorizationToken(input)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("in PullFromECR, failed to GetAuthorizationToken: %v", err)
|
||||
}
|
||||
res64 := (*res.AuthorizationData[0].AuthorizationToken)
|
||||
resB, err := base64.StdEncoding.DecodeString(res64)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("in PullFromECR, failed to DecodeString: %v", err)
|
||||
}
|
||||
delimiterIdx := bytes.IndexByte(resB, ':')
|
||||
// userName := resB[:delimiterIdx]
|
||||
// resB = resB[delimiterIdx+1:]
|
||||
// resB, err = base64.StdEncoding.DecodeString(string(resB))
|
||||
// if err != nil {
|
||||
// t.Errorf("failed to DecodeString #2: %v\n\n", err)
|
||||
// }
|
||||
return string(resB[:delimiterIdx]), string(resB[delimiterIdx+1:]), nil
|
||||
}
|
||||
|
||||
func CheckIsACRImage(imageTag string) bool {
|
||||
// atest1.azurecr.io/go-inf:1
|
||||
return strings.Contains(imageTag, ".azurecr.io/")
|
||||
}
|
||||
|
||||
type azureADDResponseJson struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn string `json:"expires_in"`
|
||||
ExpiresOn string `json:"expires_on"`
|
||||
NotBefore string `json:"not_before"`
|
||||
Resource string `json:"resource"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
func getAzureAADAccessToken() (string, error) {
|
||||
msi_endpoint, err := url.Parse("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("creating URL : %v", err)
|
||||
}
|
||||
msi_parameters := url.Values{}
|
||||
msi_parameters.Add("resource", "https://management.azure.com/")
|
||||
msi_parameters.Add("api-version", "2018-02-01")
|
||||
msi_endpoint.RawQuery = msi_parameters.Encode()
|
||||
req, err := http.NewRequest("GET", msi_endpoint.String(), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("creating HTTP request : %v", err)
|
||||
}
|
||||
req.Header.Add("Metadata", "true")
|
||||
|
||||
// Call managed services for Azure resources token endpoint
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("calling token endpoint : %v", err)
|
||||
}
|
||||
|
||||
// Pull out response body
|
||||
responseBytes, err := io.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading response body : %v", err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return "", fmt.Errorf("azure ActiveDirectory AT resp: %v, %v", resp.Status, string(responseBytes))
|
||||
}
|
||||
|
||||
// Unmarshall response body into struct
|
||||
var r azureADDResponseJson
|
||||
err = json.Unmarshal(responseBytes, &r)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unmarshalling the response: %v", err)
|
||||
}
|
||||
return r.AccessToken, nil
|
||||
}
|
||||
|
||||
// GetLoginDetailsForAzurCR return user name + password to use
|
||||
func GetLoginDetailsForAzurCR(imageTag string) (string, string, error) {
|
||||
// imageTag := "atest1.azurecr.io/go-inf:1"
|
||||
imageTagSlices := strings.Split(imageTag, "/")
|
||||
azureIdensAT, err := getAzureAADAccessToken()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
atMap := make(map[string]interface{})
|
||||
azureIdensATSlices := strings.Split(azureIdensAT, ".")
|
||||
if len(azureIdensATSlices) < 2 {
|
||||
return "", "", fmt.Errorf("len(azureIdensATSlices) < 2")
|
||||
}
|
||||
resB, err := base64.RawStdEncoding.DecodeString(azureIdensATSlices[1])
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("in GetLoginDetailsForAzurCR, failed to DecodeString: %v, %s", err, azureIdensATSlices[1])
|
||||
}
|
||||
if err := json.Unmarshal(resB, &atMap); err != nil {
|
||||
return "", "", fmt.Errorf("failed to unmarshal azureIdensAT: %v, %s", err, string(resB))
|
||||
}
|
||||
// excahnging AAD for ACR refresh token
|
||||
refreshToken, err := excahngeAzureAADAccessTokenForACRRefreshToken(imageTagSlices[0], fmt.Sprintf("%v", atMap["tid"]), azureIdensAT)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to excahngeAzureAADAccessTokenForACRRefreshToken: %v, registry: %s, tenantID: %s, azureAADAT: %s", err, imageTagSlices[0], fmt.Sprintf("%v", atMap["tid"]), azureIdensAT)
|
||||
}
|
||||
|
||||
return "00000000-0000-0000-0000-000000000000", refreshToken, nil
|
||||
}
|
||||
|
||||
func excahngeAzureAADAccessTokenForACRRefreshToken(registry, tenantID, azureAADAT string) (string, error) {
|
||||
msi_parameters := url.Values{}
|
||||
msi_parameters.Add("service", registry)
|
||||
msi_parameters.Add("grant_type", "access_token")
|
||||
msi_parameters.Add("tenant", tenantID)
|
||||
msi_parameters.Add("access_token", azureAADAT)
|
||||
postBodyStr := msi_parameters.Encode()
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("https://%v/oauth2/exchange", registry), strings.NewReader(postBodyStr))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("creating HTTP request : %v", err)
|
||||
}
|
||||
req.Header.Add("Metadata", "true")
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
// Call managed services for Azure resources token endpoint
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("calling token endpoint : %v", err)
|
||||
}
|
||||
|
||||
// Pull out response body
|
||||
responseBytes, err := io.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading response body : %v", err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return "", fmt.Errorf("azure exchange AT resp: %v, %v", resp.Status, string(responseBytes))
|
||||
}
|
||||
resultMap := make(map[string]string)
|
||||
err = json.Unmarshal(responseBytes, &resultMap)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unmarshalling the response: %v", err)
|
||||
}
|
||||
return resultMap["refresh_token"], nil
|
||||
}
|
||||
|
||||
func CheckIsGCRImage(imageTag string) bool {
|
||||
// gcr.io/elated-pottery-310110/golang-inf:2
|
||||
return strings.Contains(imageTag, "gcr.io/")
|
||||
}
|
||||
|
||||
// GetLoginDetailsForGCR return user name + password to use
|
||||
func GetLoginDetailsForGCR(imageTag string) (string, string, error) {
|
||||
msi_endpoint, err := url.Parse(fmt.Sprintf("http://169.254.169.254/computeMetadata/v1/instance/service-accounts/%s/token", gcrDefaultServiceAccountName))
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("creating URL : %v", err)
|
||||
}
|
||||
req, err := http.NewRequest("GET", msi_endpoint.String(), nil)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("creating HTTP request : %v", err)
|
||||
}
|
||||
req.Header.Add("Metadata-Flavor", "Google")
|
||||
|
||||
// Call managed services for Azure resources token endpoint
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("calling token endpoint : %v", err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return "", "", fmt.Errorf("HTTP Status : %v, make sure the '%s' service account is configured for ARMO pod", resp.Status, gcrDefaultServiceAccountName)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respMap := make(map[string]interface{})
|
||||
if err := json.NewDecoder(resp.Body).Decode(&respMap); err != nil {
|
||||
return "", "", fmt.Errorf("json Decode : %v", err)
|
||||
}
|
||||
return "oauth2accesstoken", fmt.Sprintf("%v", respMap["access_token"]), nil
|
||||
}
|
||||
|
||||
func GetCloudVendorRegistryCredentials(imageTag string) (map[string]types.AuthConfig, error) {
|
||||
secrets := map[string]types.AuthConfig{}
|
||||
var errRes error
|
||||
if CheckIsACRImage(imageTag) {
|
||||
userName, password, err := GetLoginDetailsForAzurCR(imageTag)
|
||||
if err != nil {
|
||||
errRes = fmt.Errorf("failed to GetLoginDetailsForACR(%s): %v", imageTag, err)
|
||||
} else {
|
||||
secrets[imageTag] = types.AuthConfig{
|
||||
Username: userName,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if CheckIsECRImage(imageTag) {
|
||||
userName, password, err := GetLoginDetailsForECR(imageTag)
|
||||
if err != nil {
|
||||
errRes = fmt.Errorf("failed to GetLoginDetailsForECR(%s): %v", imageTag, err)
|
||||
} else {
|
||||
secrets[imageTag] = types.AuthConfig{
|
||||
Username: userName,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if CheckIsGCRImage(imageTag) {
|
||||
userName, password, err := GetLoginDetailsForGCR(imageTag)
|
||||
if err != nil {
|
||||
errRes = fmt.Errorf("failed to GetLoginDetailsForGCR(%s): %v", imageTag, err)
|
||||
} else {
|
||||
secrets[imageTag] = types.AuthConfig{
|
||||
Username: userName,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return secrets, errRes
|
||||
}
|
||||
121
cautils/k8sinterface/k8sconfig.go
Normal file
121
cautils/k8sinterface/k8sconfig.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
// DO NOT REMOVE - load cloud providers auth
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
)
|
||||
|
||||
var ConnectedToCluster = true
|
||||
|
||||
// K8SConfig pointer to k8s config
|
||||
var K8SConfig *restclient.Config
|
||||
|
||||
// KubernetesApi -
|
||||
type KubernetesApi struct {
|
||||
KubernetesClient kubernetes.Interface
|
||||
DynamicClient dynamic.Interface
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// NewKubernetesApi -
|
||||
func NewKubernetesApi() *KubernetesApi {
|
||||
var kubernetesClient *kubernetes.Clientset
|
||||
var err error
|
||||
|
||||
if !IsConnectedToCluster() {
|
||||
fmt.Println(fmt.Errorf("failed to load kubernetes config: no configuration has been provided, try setting KUBECONFIG environment variable"))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
kubernetesClient, err = kubernetes.NewForConfig(GetK8sConfig())
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to load config file, reason: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
dynamicClient, err := dynamic.NewForConfig(K8SConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to load config file, reason: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return &KubernetesApi{
|
||||
KubernetesClient: kubernetesClient,
|
||||
DynamicClient: dynamicClient,
|
||||
Context: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
// RunningIncluster whether running in cluster
|
||||
var RunningIncluster bool
|
||||
|
||||
// LoadK8sConfig load config from local file or from cluster
|
||||
func LoadK8sConfig() error {
|
||||
kubeconfig, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load kubernetes config: %s", strings.ReplaceAll(err.Error(), "KUBERNETES_MASTER", "KUBECONFIG"))
|
||||
}
|
||||
if _, err := restclient.InClusterConfig(); err == nil {
|
||||
RunningIncluster = true
|
||||
}
|
||||
|
||||
K8SConfig = kubeconfig
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetK8sConfig get config. load if not loaded yet
|
||||
func GetK8sConfig() *restclient.Config {
|
||||
if !IsConnectedToCluster() {
|
||||
return nil
|
||||
}
|
||||
return K8SConfig
|
||||
}
|
||||
|
||||
func IsConnectedToCluster() bool {
|
||||
if K8SConfig == nil {
|
||||
if err := LoadK8sConfig(); err != nil {
|
||||
ConnectedToCluster = false
|
||||
}
|
||||
}
|
||||
return ConnectedToCluster
|
||||
}
|
||||
func GetClusterName() string {
|
||||
if !ConnectedToCluster {
|
||||
return ""
|
||||
}
|
||||
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{})
|
||||
config, err := kubeConfig.RawConfig()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// TODO - Handle if empty
|
||||
return config.CurrentContext
|
||||
}
|
||||
|
||||
func GetDefaultNamespace() string {
|
||||
defaultNamespace := "default"
|
||||
clientCfg, err := clientcmd.NewDefaultClientConfigLoadingRules().Load()
|
||||
if err != nil {
|
||||
return defaultNamespace
|
||||
}
|
||||
apiContext, ok := clientCfg.Contexts[clientCfg.CurrentContext]
|
||||
if !ok || apiContext == nil {
|
||||
return defaultNamespace
|
||||
}
|
||||
namespace := apiContext.Namespace
|
||||
if apiContext.Namespace == "" {
|
||||
namespace = defaultNamespace
|
||||
}
|
||||
return namespace
|
||||
}
|
||||
34
cautils/k8sinterface/k8sconfig_test.go
Normal file
34
cautils/k8sinterface/k8sconfig_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/cautils"
|
||||
)
|
||||
|
||||
func TestGetGroupVersionResource(t *testing.T) {
|
||||
wlid := "wlid://cluster-david-v1/namespace-default/deployment-nginx-deployment"
|
||||
r, err := GetGroupVersionResource(cautils.GetKindFromWlid(wlid))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if r.Group != "apps" {
|
||||
t.Errorf("wrong group")
|
||||
}
|
||||
if r.Version != "v1" {
|
||||
t.Errorf("wrong Version")
|
||||
}
|
||||
if r.Resource != "deployments" {
|
||||
t.Errorf("wrong Resource")
|
||||
}
|
||||
|
||||
r2, err := GetGroupVersionResource("NetworkPolicy")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if r2.Resource != "networkpolicies" {
|
||||
t.Errorf("wrong Resource")
|
||||
}
|
||||
}
|
||||
145
cautils/k8sinterface/k8sdynamic.go
Normal file
145
cautils/k8sinterface/k8sdynamic.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/cautils"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
//
|
||||
// Uncomment to load all auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth
|
||||
//
|
||||
// Or uncomment to load specific auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||
)
|
||||
|
||||
func (k8sAPI *KubernetesApi) GetWorkloadByWlid(wlid string) (*Workload, error) {
|
||||
return k8sAPI.GetWorkload(cautils.GetNamespaceFromWlid(wlid), cautils.GetKindFromWlid(wlid), cautils.GetNameFromWlid(wlid))
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) GetWorkload(namespace, kind, name string) (*Workload, error) {
|
||||
groupVersionResource, err := GetGroupVersionResource(kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := k8sAPI.ResourceInterface(&groupVersionResource, namespace).Get(k8sAPI.Context, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to GET resource, kind: '%s', namespace: '%s', name: '%s', reason: %s", kind, namespace, name, err.Error())
|
||||
}
|
||||
return NewWorkloadObj(w.Object), nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) ListWorkloads(groupVersionResource *schema.GroupVersionResource, namespace string, podLabels, fieldSelector map[string]string) ([]Workload, error) {
|
||||
listOptions := metav1.ListOptions{}
|
||||
if podLabels != nil && len(podLabels) > 0 {
|
||||
set := labels.Set(podLabels)
|
||||
listOptions.LabelSelector = SelectorToString(set)
|
||||
}
|
||||
if fieldSelector != nil && len(fieldSelector) > 0 {
|
||||
set := labels.Set(fieldSelector)
|
||||
listOptions.FieldSelector = SelectorToString(set)
|
||||
}
|
||||
uList, err := k8sAPI.ResourceInterface(groupVersionResource, namespace).List(k8sAPI.Context, listOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to LIST resources, reason: %s", err.Error())
|
||||
}
|
||||
workloads := make([]Workload, len(uList.Items))
|
||||
for i := range uList.Items {
|
||||
workloads[i] = *NewWorkloadObj(uList.Items[i].Object)
|
||||
}
|
||||
return workloads, nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) DeleteWorkloadByWlid(wlid string) error {
|
||||
groupVersionResource, err := GetGroupVersionResource(cautils.GetKindFromWlid(wlid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = k8sAPI.ResourceInterface(&groupVersionResource, cautils.GetNamespaceFromWlid(wlid)).Delete(k8sAPI.Context, cautils.GetNameFromWlid(wlid), metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to DELETE resource, workloadID: '%s', reason: %s", wlid, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) CreateWorkload(workload *Workload) (*Workload, error) {
|
||||
groupVersionResource, err := GetGroupVersionResource(workload.GetKind())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj, err := workload.ToUnstructured()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w, err := k8sAPI.ResourceInterface(&groupVersionResource, workload.GetNamespace()).Create(k8sAPI.Context, obj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to CREATE resource, workload: '%s', reason: %s", workload.Json(), err.Error())
|
||||
}
|
||||
return NewWorkloadObj(w.Object), nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) UpdateWorkload(workload *Workload) (*Workload, error) {
|
||||
groupVersionResource, err := GetGroupVersionResource(workload.GetKind())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj, err := workload.ToUnstructured()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := k8sAPI.ResourceInterface(&groupVersionResource, workload.GetNamespace()).Update(k8sAPI.Context, obj, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to UPDATE resource, workload: '%s', reason: %s", workload.Json(), err.Error())
|
||||
}
|
||||
return NewWorkloadObj(w.Object), nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) GetNamespace(ns string) (*Workload, error) {
|
||||
groupVersionResource, err := GetGroupVersionResource("namespace")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w, err := k8sAPI.DynamicClient.Resource(groupVersionResource).Get(k8sAPI.Context, ns, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get namespace: '%s', reason: %s", ns, err.Error())
|
||||
}
|
||||
return NewWorkloadObj(w.Object), nil
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) ResourceInterface(resource *schema.GroupVersionResource, namespace string) dynamic.ResourceInterface {
|
||||
if IsNamespaceScope(resource.Group, resource.Resource) {
|
||||
return k8sAPI.DynamicClient.Resource(*resource).Namespace(namespace)
|
||||
}
|
||||
return k8sAPI.DynamicClient.Resource(*resource)
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) CalculateWorkloadParentRecursive(workload *Workload) (string, string, error) {
|
||||
ownerReferences, err := workload.GetOwnerReferences() // OwnerReferences in workload
|
||||
if err != nil {
|
||||
return workload.GetKind(), workload.GetName(), err
|
||||
}
|
||||
if len(ownerReferences) == 0 {
|
||||
return workload.GetKind(), workload.GetName(), nil // parent found
|
||||
}
|
||||
ownerReference := ownerReferences[0]
|
||||
|
||||
parentWorkload, err := k8sAPI.GetWorkload(workload.GetNamespace(), ownerReference.Kind, ownerReference.Name)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found in resourceMap") { // if parent is RCD
|
||||
return workload.GetKind(), workload.GetName(), nil // parent found
|
||||
}
|
||||
return workload.GetKind(), workload.GetName(), err
|
||||
}
|
||||
return k8sAPI.CalculateWorkloadParentRecursive(parentWorkload)
|
||||
}
|
||||
43
cautils/k8sinterface/k8sdynamic_test.go
Normal file
43
cautils/k8sinterface/k8sdynamic_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
dynamicfake "k8s.io/client-go/dynamic/fake"
|
||||
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
||||
//
|
||||
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
// Uncomment to load all auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth
|
||||
//
|
||||
// Or uncomment to load specific auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||
)
|
||||
|
||||
// NewKubernetesApi -
|
||||
func NewKubernetesApiMock() *KubernetesApi {
|
||||
|
||||
return &KubernetesApi{
|
||||
KubernetesClient: kubernetesfake.NewSimpleClientset(),
|
||||
DynamicClient: dynamicfake.NewSimpleDynamicClient(&runtime.Scheme{}),
|
||||
Context: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
// func TestListDynamic(t *testing.T) {
|
||||
// k8s := NewKubernetesApi()
|
||||
// resource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||
// clientResource, err := k8s.DynamicClient.Resource(resource).Namespace("default").List(k8s.Context, metav1.ListOptions{})
|
||||
// if err != nil {
|
||||
// t.Errorf("err: %v", err)
|
||||
// } else {
|
||||
// bla, _ := json.Marshal(clientResource)
|
||||
// // t.Errorf("BearerToken: %v", *K8SConfig)
|
||||
// // os.WriteFile("bla.json", bla, 777)
|
||||
// t.Errorf("clientResource: %s", string(bla))
|
||||
// }
|
||||
// }
|
||||
66
cautils/k8sinterface/k8sdynamicutils.go
Normal file
66
cautils/k8sinterface/k8sdynamicutils.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
//
|
||||
// Uncomment to load all auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth
|
||||
//
|
||||
// Or uncomment to load specific auth plugins
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||
|
||||
func ConvertUnstructuredSliceToMap(unstructuredSlice []unstructured.Unstructured) []map[string]interface{} {
|
||||
converted := make([]map[string]interface{}, len(unstructuredSlice))
|
||||
for i := range unstructuredSlice {
|
||||
converted[i] = unstructuredSlice[i].Object
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func FilterOutOwneredResources(result []unstructured.Unstructured) []unstructured.Unstructured {
|
||||
response := []unstructured.Unstructured{}
|
||||
recognizedOwners := []string{"Deployment", "ReplicaSet", "DaemonSet", "StatefulSet", "Job", "CronJob"}
|
||||
for i := range result {
|
||||
ownerReferences := result[i].GetOwnerReferences()
|
||||
if len(ownerReferences) == 0 {
|
||||
response = append(response, result[i])
|
||||
} else if !IsStringInSlice(recognizedOwners, ownerReferences[0].Kind) {
|
||||
response = append(response, result[i])
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func IsStringInSlice(slice []string, val string) bool {
|
||||
for _, item := range slice {
|
||||
if item == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String returns all labels listed as a human readable string.
|
||||
// Conveniently, exactly the format that ParseSelector takes.
|
||||
func SelectorToString(ls labels.Set) string {
|
||||
selector := make([]string, 0, len(ls))
|
||||
for key, value := range ls {
|
||||
if value != "" {
|
||||
selector = append(selector, key+"="+value)
|
||||
} else {
|
||||
selector = append(selector, key)
|
||||
}
|
||||
}
|
||||
// Sort for determinism.
|
||||
sort.StringSlice(selector).Sort()
|
||||
return strings.Join(selector, ",")
|
||||
}
|
||||
10
cautils/k8sinterface/k8sdynamicutils_test.go
Normal file
10
cautils/k8sinterface/k8sdynamicutils_test.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package k8sinterface
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestConvertUnstructuredSliceToMap(t *testing.T) {
|
||||
converted := ConvertUnstructuredSliceToMap(V1KubeSystemNamespaceMock().Items)
|
||||
if len(converted) == 0 { // != 7
|
||||
t.Errorf("len(converted) == 0")
|
||||
}
|
||||
}
|
||||
71
cautils/k8sinterface/k8sstatic.go
Normal file
71
cautils/k8sinterface/k8sstatic.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/cautils"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
func IsAttached(labels map[string]string) *bool {
|
||||
return IsLabel(labels, cautils.ArmoAttach)
|
||||
}
|
||||
|
||||
func IsAgentCompatibleLabel(labels map[string]string) *bool {
|
||||
return IsLabel(labels, cautils.ArmoCompatibleLabel)
|
||||
}
|
||||
func IsAgentCompatibleAnnotation(annotations map[string]string) *bool {
|
||||
return IsLabel(annotations, cautils.ArmoCompatibleAnnotation)
|
||||
}
|
||||
func SetAgentCompatibleLabel(labels map[string]string, val bool) {
|
||||
SetLabel(labels, cautils.ArmoCompatibleLabel, val)
|
||||
}
|
||||
func SetAgentCompatibleAnnotation(annotations map[string]string, val bool) {
|
||||
SetLabel(annotations, cautils.ArmoCompatibleAnnotation, val)
|
||||
}
|
||||
func IsLabel(labels map[string]string, key string) *bool {
|
||||
if labels == nil || len(labels) == 0 {
|
||||
return nil
|
||||
}
|
||||
var k bool
|
||||
if l, ok := labels[key]; ok {
|
||||
if l == "true" {
|
||||
k = true
|
||||
} else if l == "false" {
|
||||
k = false
|
||||
}
|
||||
return &k
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func SetLabel(labels map[string]string, key string, val bool) {
|
||||
if labels == nil {
|
||||
return
|
||||
}
|
||||
v := ""
|
||||
if val {
|
||||
v = "true"
|
||||
} else {
|
||||
v = "false"
|
||||
}
|
||||
labels[key] = v
|
||||
}
|
||||
func (k8sAPI *KubernetesApi) ListAttachedPods(namespace string) ([]corev1.Pod, error) {
|
||||
return k8sAPI.ListPods(namespace, map[string]string{cautils.ArmoAttach: cautils.BoolToString(true)})
|
||||
}
|
||||
|
||||
func (k8sAPI *KubernetesApi) ListPods(namespace string, podLabels map[string]string) ([]corev1.Pod, error) {
|
||||
listOptions := metav1.ListOptions{}
|
||||
if podLabels != nil && len(podLabels) > 0 {
|
||||
set := labels.Set(podLabels)
|
||||
listOptions.LabelSelector = set.AsSelector().String()
|
||||
}
|
||||
pods, err := k8sAPI.KubernetesClient.CoreV1().Pods(namespace).List(context.Background(), listOptions)
|
||||
if err != nil {
|
||||
return []corev1.Pod{}, err
|
||||
}
|
||||
return pods.Items, nil
|
||||
}
|
||||
1963
cautils/k8sinterface/mockdynamicobjects.go
Normal file
1963
cautils/k8sinterface/mockdynamicobjects.go
Normal file
File diff suppressed because one or more lines are too long
142
cautils/k8sinterface/resourcegroupmapping.go
Normal file
142
cautils/k8sinterface/resourcegroupmapping.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const ValueNotFound = -1
|
||||
|
||||
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#-strong-api-groups-strong-
|
||||
var ResourceGroupMapping = map[string]string{
|
||||
"services": "/v1",
|
||||
"pods": "/v1",
|
||||
"replicationcontrollers": "/v1",
|
||||
"podtemplates": "/v1",
|
||||
"namespaces": "/v1",
|
||||
"nodes": "/v1",
|
||||
"configmaps": "/v1",
|
||||
"secrets": "/v1",
|
||||
"serviceaccounts": "/v1",
|
||||
"persistentvolumeclaims": "/v1",
|
||||
"limitranges": "/v1",
|
||||
"resourcequotas": "/v1",
|
||||
"daemonsets": "apps/v1",
|
||||
"deployments": "apps/v1",
|
||||
"replicasets": "apps/v1",
|
||||
"statefulsets": "apps/v1",
|
||||
"controllerrevisions": "apps/v1",
|
||||
"jobs": "batch/v1",
|
||||
"cronjobs": "batch/v1beta1",
|
||||
"horizontalpodautoscalers": "autoscaling/v1",
|
||||
"ingresses": "extensions/v1beta1",
|
||||
"networkpolicies": "networking.k8s.io/v1",
|
||||
"clusterroles": "rbac.authorization.k8s.io/v1",
|
||||
"clusterrolebindings": "rbac.authorization.k8s.io/v1",
|
||||
"roles": "rbac.authorization.k8s.io/v1",
|
||||
"rolebindings": "rbac.authorization.k8s.io/v1",
|
||||
"mutatingwebhookconfigurations": "admissionregistration.k8s.io/v1",
|
||||
"validatingwebhookconfigurations": "admissionregistration.k8s.io/v1",
|
||||
}
|
||||
|
||||
var GroupsClusterScope = []string{}
|
||||
var ResourceClusterScope = []string{"nodes", "namespaces", "clusterroles", "clusterrolebindings"}
|
||||
|
||||
func GetGroupVersionResource(resource string) (schema.GroupVersionResource, error) {
|
||||
resource = updateResourceKind(resource)
|
||||
if r, ok := ResourceGroupMapping[resource]; ok {
|
||||
gv := strings.Split(r, "/")
|
||||
return schema.GroupVersionResource{Group: gv[0], Version: gv[1], Resource: resource}, nil
|
||||
}
|
||||
return schema.GroupVersionResource{}, fmt.Errorf("resource '%s' not found in resourceMap", resource)
|
||||
}
|
||||
|
||||
func IsNamespaceScope(apiGroup, resource string) bool {
|
||||
return StringInSlice(GroupsClusterScope, apiGroup) == ValueNotFound &&
|
||||
StringInSlice(ResourceClusterScope, resource) == ValueNotFound
|
||||
}
|
||||
|
||||
func StringInSlice(strSlice []string, str string) int {
|
||||
for i := range strSlice {
|
||||
if strSlice[i] == str {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return ValueNotFound
|
||||
}
|
||||
|
||||
func JoinResourceTriplets(group, version, resource string) string {
|
||||
return fmt.Sprintf("%s/%s/%s", group, version, resource)
|
||||
}
|
||||
func GetResourceTriplets(group, version, resource string) []string {
|
||||
resourceTriplets := []string{}
|
||||
if resource == "" {
|
||||
// load full map
|
||||
for k, v := range ResourceGroupMapping {
|
||||
g := strings.Split(v, "/")
|
||||
resourceTriplets = append(resourceTriplets, JoinResourceTriplets(g[0], g[1], k))
|
||||
}
|
||||
} else if version == "" {
|
||||
// load by resource
|
||||
if v, ok := ResourceGroupMapping[resource]; ok {
|
||||
g := strings.Split(v, "/")
|
||||
if group == "" {
|
||||
group = g[0]
|
||||
}
|
||||
resourceTriplets = append(resourceTriplets, JoinResourceTriplets(group, g[1], resource))
|
||||
} else {
|
||||
glog.Errorf("Resource '%s' unknown", resource)
|
||||
}
|
||||
} else if group == "" {
|
||||
// load by resource and version
|
||||
if v, ok := ResourceGroupMapping[resource]; ok {
|
||||
g := strings.Split(v, "/")
|
||||
resourceTriplets = append(resourceTriplets, JoinResourceTriplets(g[0], version, resource))
|
||||
} else {
|
||||
glog.Errorf("Resource '%s' unknown", resource)
|
||||
}
|
||||
} else {
|
||||
resourceTriplets = append(resourceTriplets, JoinResourceTriplets(group, version, resource))
|
||||
}
|
||||
return resourceTriplets
|
||||
}
|
||||
func ResourceGroupToString(group, version, resource string) []string {
|
||||
if group == "*" {
|
||||
group = ""
|
||||
}
|
||||
if version == "*" {
|
||||
version = ""
|
||||
}
|
||||
if resource == "*" {
|
||||
resource = ""
|
||||
}
|
||||
resource = updateResourceKind(resource)
|
||||
return GetResourceTriplets(group, version, resource)
|
||||
}
|
||||
|
||||
func StringToResourceGroup(str string) (string, string, string) {
|
||||
splitted := strings.Split(str, "/")
|
||||
for i := range splitted {
|
||||
if splitted[i] == "*" {
|
||||
splitted[i] = ""
|
||||
}
|
||||
}
|
||||
return splitted[0], splitted[1], splitted[2]
|
||||
}
|
||||
|
||||
func updateResourceKind(resource string) string {
|
||||
resource = strings.ToLower(resource)
|
||||
|
||||
if resource != "" && !strings.HasSuffix(resource, "s") {
|
||||
if strings.HasSuffix(resource, "y") {
|
||||
return fmt.Sprintf("%sies", strings.TrimSuffix(resource, "y")) // e.g. NetworkPolicy -> networkpolicies
|
||||
} else {
|
||||
return fmt.Sprintf("%ss", resource) // add 's' at the end of a resource
|
||||
}
|
||||
}
|
||||
return resource
|
||||
|
||||
}
|
||||
22
cautils/k8sinterface/resourcegroupmapping_test.go
Normal file
22
cautils/k8sinterface/resourcegroupmapping_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package k8sinterface
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestResourceGroupToString(t *testing.T) {
|
||||
allResources := ResourceGroupToString("*", "*", "*")
|
||||
if len(allResources) != len(ResourceGroupMapping) {
|
||||
t.Errorf("Expected len: %d, received: %d", len(ResourceGroupMapping), len(allResources))
|
||||
}
|
||||
pod := ResourceGroupToString("*", "*", "Pod")
|
||||
if len(pod) == 0 || pod[0] != "/v1/pods" {
|
||||
t.Errorf("pod: %v", pod)
|
||||
}
|
||||
deployments := ResourceGroupToString("*", "*", "Deployment")
|
||||
if len(deployments) == 0 || deployments[0] != "apps/v1/deployments" {
|
||||
t.Errorf("deployments: %v", deployments)
|
||||
}
|
||||
cronjobs := ResourceGroupToString("*", "*", "cronjobs")
|
||||
if len(cronjobs) == 0 || cronjobs[0] != "batch/v1beta1/cronjobs" {
|
||||
t.Errorf("cronjobs: %v", cronjobs)
|
||||
}
|
||||
}
|
||||
161
cautils/k8sinterface/workload.go
Normal file
161
cautils/k8sinterface/workload.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/apis"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
type IWorkload interface {
|
||||
IBasicWorkload
|
||||
|
||||
// Convert
|
||||
ToUnstructured() (*unstructured.Unstructured, error)
|
||||
ToString() string
|
||||
Json() string // DEPRECATED
|
||||
|
||||
// GET
|
||||
GetWlid() string
|
||||
GetJobID() *apis.JobTracking
|
||||
GetVersion() string
|
||||
GetGroup() string
|
||||
|
||||
// SET
|
||||
SetWlid(string)
|
||||
SetInject()
|
||||
SetIgnore()
|
||||
SetUpdateTime()
|
||||
SetJobID(apis.JobTracking)
|
||||
SetCompatible()
|
||||
SetIncompatible()
|
||||
SetReplaceheaders()
|
||||
|
||||
// EXIST
|
||||
IsIgnore() bool
|
||||
IsInject() bool
|
||||
IsAttached() bool
|
||||
IsCompatible() bool
|
||||
IsIncompatible() bool
|
||||
|
||||
// REMOVE
|
||||
RemoveWlid()
|
||||
RemoveSecretData()
|
||||
RemoveInject()
|
||||
RemoveIgnore()
|
||||
RemoveUpdateTime()
|
||||
RemoveJobID()
|
||||
RemoveCompatible()
|
||||
RemoveArmoMetadata()
|
||||
RemoveArmoLabels()
|
||||
RemoveArmoAnnotations()
|
||||
}
|
||||
type IBasicWorkload interface {
|
||||
|
||||
// Set
|
||||
SetKind(string)
|
||||
SetWorkload(map[string]interface{})
|
||||
SetLabel(key, value string)
|
||||
SetAnnotation(key, value string)
|
||||
SetNamespace(string)
|
||||
SetName(string)
|
||||
|
||||
// Get
|
||||
GetNamespace() string
|
||||
GetName() string
|
||||
GetGenerateName() string
|
||||
GetApiVersion() string
|
||||
GetKind() string
|
||||
GetInnerAnnotation(string) (string, bool)
|
||||
GetPodAnnotation(string) (string, bool)
|
||||
GetAnnotation(string) (string, bool)
|
||||
GetLabel(string) (string, bool)
|
||||
GetAnnotations() map[string]string
|
||||
GetInnerAnnotations() map[string]string
|
||||
GetPodAnnotations() map[string]string
|
||||
GetLabels() map[string]string
|
||||
GetInnerLabels() map[string]string
|
||||
GetPodLabels() map[string]string
|
||||
GetVolumes() ([]corev1.Volume, error)
|
||||
GetReplicas() int
|
||||
GetContainers() ([]corev1.Container, error)
|
||||
GetInitContainers() ([]corev1.Container, error)
|
||||
GetOwnerReferences() ([]metav1.OwnerReference, error)
|
||||
GetImagePullSecret() ([]corev1.LocalObjectReference, error)
|
||||
GetServiceAccountName() string
|
||||
GetSelector() (*metav1.LabelSelector, error)
|
||||
GetResourceVersion() string
|
||||
GetUID() string
|
||||
GetPodSpec() (*corev1.PodSpec, error)
|
||||
|
||||
GetWorkload() map[string]interface{}
|
||||
|
||||
// REMOVE
|
||||
RemoveLabel(string)
|
||||
RemoveAnnotation(string)
|
||||
RemovePodStatus()
|
||||
RemoveResourceVersion()
|
||||
}
|
||||
|
||||
type Workload struct {
|
||||
workload map[string]interface{}
|
||||
}
|
||||
|
||||
func NewWorkload(bWorkload []byte) (*Workload, error) {
|
||||
workload := make(map[string]interface{})
|
||||
if bWorkload != nil {
|
||||
if err := json.Unmarshal(bWorkload, &workload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Workload{
|
||||
workload: workload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewWorkloadObj(workload map[string]interface{}) *Workload {
|
||||
return &Workload{
|
||||
workload: workload,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Workload) Json() string {
|
||||
return w.ToString()
|
||||
}
|
||||
func (w *Workload) ToString() string {
|
||||
if w.GetWorkload() == nil {
|
||||
return ""
|
||||
}
|
||||
bWorkload, err := json.Marshal(w.GetWorkload())
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return string(bWorkload)
|
||||
}
|
||||
|
||||
func (workload *Workload) DeepCopy(w map[string]interface{}) {
|
||||
workload.workload = make(map[string]interface{})
|
||||
byt, _ := json.Marshal(w)
|
||||
json.Unmarshal(byt, &workload.workload)
|
||||
}
|
||||
|
||||
func (w *Workload) ToUnstructured() (*unstructured.Unstructured, error) {
|
||||
obj := &unstructured.Unstructured{}
|
||||
if w.workload == nil {
|
||||
return obj, nil
|
||||
}
|
||||
bWorkload, err := json.Marshal(w.workload)
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
if err := json.Unmarshal(bWorkload, obj); err != nil {
|
||||
return obj, err
|
||||
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
642
cautils/k8sinterface/workloadmethods.go
Normal file
642
cautils/k8sinterface/workloadmethods.go
Normal file
@@ -0,0 +1,642 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/apis"
|
||||
"github.com/armosec/kubescape/cautils/cautils"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ======================================= DELETE ========================================
|
||||
|
||||
func (w *Workload) RemoveInject() {
|
||||
w.RemovePodLabel(cautils.CAInject) // DEPRECATED
|
||||
w.RemovePodLabel(cautils.CAAttachLabel) // DEPRECATED
|
||||
w.RemovePodLabel(cautils.ArmoAttach)
|
||||
|
||||
w.RemoveLabel(cautils.CAInject) // DEPRECATED
|
||||
w.RemoveLabel(cautils.CAAttachLabel) // DEPRECATED
|
||||
w.RemoveLabel(cautils.ArmoAttach)
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveIgnore() {
|
||||
w.RemovePodLabel(cautils.CAIgnore) // DEPRECATED
|
||||
w.RemovePodLabel(cautils.ArmoAttach)
|
||||
|
||||
w.RemoveLabel(cautils.CAIgnore) // DEPRECATED
|
||||
w.RemoveLabel(cautils.ArmoAttach)
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveWlid() {
|
||||
w.RemovePodAnnotation(cautils.CAWlid) // DEPRECATED
|
||||
w.RemovePodAnnotation(cautils.ArmoWlid)
|
||||
|
||||
w.RemoveAnnotation(cautils.CAWlid) // DEPRECATED
|
||||
w.RemoveAnnotation(cautils.ArmoWlid)
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveCompatible() {
|
||||
w.RemovePodAnnotation(cautils.ArmoCompatibleAnnotation)
|
||||
}
|
||||
func (w *Workload) RemoveJobID() {
|
||||
w.RemovePodAnnotation(cautils.ArmoJobIDPath)
|
||||
w.RemovePodAnnotation(cautils.ArmoJobParentPath)
|
||||
w.RemovePodAnnotation(cautils.ArmoJobActionPath)
|
||||
|
||||
w.RemoveAnnotation(cautils.ArmoJobIDPath)
|
||||
w.RemoveAnnotation(cautils.ArmoJobParentPath)
|
||||
w.RemoveAnnotation(cautils.ArmoJobActionPath)
|
||||
}
|
||||
func (w *Workload) RemoveArmoMetadata() {
|
||||
w.RemoveArmoLabels()
|
||||
w.RemoveArmoAnnotations()
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveArmoAnnotations() {
|
||||
l := w.GetAnnotations()
|
||||
if l != nil {
|
||||
for k := range l {
|
||||
if strings.HasPrefix(k, cautils.ArmoPrefix) {
|
||||
w.RemoveAnnotation(k)
|
||||
}
|
||||
if strings.HasPrefix(k, cautils.CAPrefix) { // DEPRECATED
|
||||
w.RemoveAnnotation(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
lp := w.GetPodAnnotations()
|
||||
if lp != nil {
|
||||
for k := range lp {
|
||||
if strings.HasPrefix(k, cautils.ArmoPrefix) {
|
||||
w.RemovePodAnnotation(k)
|
||||
}
|
||||
if strings.HasPrefix(k, cautils.CAPrefix) { // DEPRECATED
|
||||
w.RemovePodAnnotation(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func (w *Workload) RemoveArmoLabels() {
|
||||
l := w.GetLabels()
|
||||
if l != nil {
|
||||
for k := range l {
|
||||
if strings.HasPrefix(k, cautils.ArmoPrefix) {
|
||||
w.RemoveLabel(k)
|
||||
}
|
||||
if strings.HasPrefix(k, cautils.CAPrefix) { // DEPRECATED
|
||||
w.RemoveLabel(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
lp := w.GetPodLabels()
|
||||
if lp != nil {
|
||||
for k := range lp {
|
||||
if strings.HasPrefix(k, cautils.ArmoPrefix) {
|
||||
w.RemovePodLabel(k)
|
||||
}
|
||||
if strings.HasPrefix(k, cautils.CAPrefix) { // DEPRECATED
|
||||
w.RemovePodLabel(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func (w *Workload) RemoveUpdateTime() {
|
||||
|
||||
// remove from pod
|
||||
w.RemovePodAnnotation(cautils.CAUpdate) // DEPRECATED
|
||||
w.RemovePodAnnotation(cautils.ArmoUpdate)
|
||||
|
||||
// remove from workload
|
||||
w.RemoveAnnotation(cautils.CAUpdate) // DEPRECATED
|
||||
w.RemoveAnnotation(cautils.ArmoUpdate)
|
||||
}
|
||||
func (w *Workload) RemoveSecretData() {
|
||||
w.RemoveAnnotation("kubectl.kubernetes.io/last-applied-configuration")
|
||||
delete(w.workload, "data")
|
||||
}
|
||||
|
||||
func (w *Workload) RemovePodStatus() {
|
||||
delete(w.workload, "status")
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveResourceVersion() {
|
||||
if _, ok := w.workload["metadata"]; !ok {
|
||||
return
|
||||
}
|
||||
meta, _ := w.workload["metadata"].(map[string]interface{})
|
||||
delete(meta, "resourceVersion")
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveLabel(key string) {
|
||||
w.RemoveMetadata([]string{"metadata"}, "labels", key)
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveAnnotation(key string) {
|
||||
w.RemoveMetadata([]string{"metadata"}, "annotations", key)
|
||||
}
|
||||
|
||||
func (w *Workload) RemovePodAnnotation(key string) {
|
||||
w.RemoveMetadata(PodMetadata(w.GetKind()), "annotations", key)
|
||||
}
|
||||
|
||||
func (w *Workload) RemovePodLabel(key string) {
|
||||
w.RemoveMetadata(PodMetadata(w.GetKind()), "labels", key)
|
||||
}
|
||||
|
||||
func (w *Workload) RemoveMetadata(scope []string, metadata, key string) {
|
||||
|
||||
workload := w.workload
|
||||
for i := range scope {
|
||||
if _, ok := workload[scope[i]]; !ok {
|
||||
return
|
||||
}
|
||||
workload, _ = workload[scope[i]].(map[string]interface{})
|
||||
}
|
||||
|
||||
if _, ok := workload[metadata]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
labels, _ := workload[metadata].(map[string]interface{})
|
||||
delete(labels, key)
|
||||
|
||||
}
|
||||
|
||||
// ========================================= SET =========================================
|
||||
|
||||
func (w *Workload) SetWorkload(workload map[string]interface{}) {
|
||||
w.workload = workload
|
||||
}
|
||||
|
||||
func (w *Workload) SetKind(kind string) {
|
||||
w.workload["kind"] = kind
|
||||
}
|
||||
|
||||
func (w *Workload) SetInject() {
|
||||
w.SetPodLabel(cautils.ArmoAttach, cautils.BoolToString(true))
|
||||
}
|
||||
|
||||
func (w *Workload) SetJobID(jobTracking apis.JobTracking) {
|
||||
w.SetPodAnnotation(cautils.ArmoJobIDPath, jobTracking.JobID)
|
||||
w.SetPodAnnotation(cautils.ArmoJobParentPath, jobTracking.ParentID)
|
||||
w.SetPodAnnotation(cautils.ArmoJobActionPath, fmt.Sprintf("%d", jobTracking.LastActionNumber))
|
||||
}
|
||||
|
||||
func (w *Workload) SetIgnore() {
|
||||
w.SetPodLabel(cautils.ArmoAttach, cautils.BoolToString(false))
|
||||
}
|
||||
|
||||
func (w *Workload) SetCompatible() {
|
||||
w.SetPodAnnotation(cautils.ArmoCompatibleAnnotation, cautils.BoolToString(true))
|
||||
}
|
||||
|
||||
func (w *Workload) SetIncompatible() {
|
||||
w.SetPodAnnotation(cautils.ArmoCompatibleAnnotation, cautils.BoolToString(false))
|
||||
}
|
||||
|
||||
func (w *Workload) SetReplaceheaders() {
|
||||
w.SetPodAnnotation(cautils.ArmoReplaceheaders, cautils.BoolToString(true))
|
||||
}
|
||||
|
||||
func (w *Workload) SetWlid(wlid string) {
|
||||
w.SetPodAnnotation(cautils.ArmoWlid, wlid)
|
||||
}
|
||||
|
||||
func (w *Workload) SetUpdateTime() {
|
||||
w.SetPodAnnotation(cautils.ArmoUpdate, string(time.Now().UTC().Format("02-01-2006 15:04:05")))
|
||||
}
|
||||
|
||||
func (w *Workload) SetNamespace(namespace string) {
|
||||
w.SetMetadata([]string{"metadata"}, "namespace", namespace)
|
||||
}
|
||||
|
||||
func (w *Workload) SetName(name string) {
|
||||
w.SetMetadata([]string{"metadata"}, "name", name)
|
||||
}
|
||||
|
||||
func (w *Workload) SetLabel(key, value string) {
|
||||
w.SetMetadata([]string{"metadata", "labels"}, key, value)
|
||||
}
|
||||
|
||||
func (w *Workload) SetPodLabel(key, value string) {
|
||||
w.SetMetadata(append(PodMetadata(w.GetKind()), "labels"), key, value)
|
||||
}
|
||||
func (w *Workload) SetAnnotation(key, value string) {
|
||||
w.SetMetadata([]string{"metadata", "annotations"}, key, value)
|
||||
}
|
||||
func (w *Workload) SetPodAnnotation(key, value string) {
|
||||
w.SetMetadata(append(PodMetadata(w.GetKind()), "annotations"), key, value)
|
||||
}
|
||||
|
||||
func (w *Workload) SetMetadata(scope []string, key string, val interface{}) {
|
||||
workload := w.workload
|
||||
for i := range scope {
|
||||
if _, ok := workload[scope[i]]; !ok {
|
||||
workload[scope[i]] = make(map[string]interface{})
|
||||
}
|
||||
workload, _ = workload[scope[i]].(map[string]interface{})
|
||||
}
|
||||
|
||||
workload[key] = val
|
||||
}
|
||||
|
||||
// ========================================= GET =========================================
|
||||
func (w *Workload) GetWorkload() map[string]interface{} {
|
||||
return w.workload
|
||||
}
|
||||
func (w *Workload) GetNamespace() string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "namespace"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetName() string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "name"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetApiVersion() string {
|
||||
if v, ok := InspectWorkload(w.workload, "apiVersion"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetVersion() string {
|
||||
apiVersion := w.GetApiVersion()
|
||||
splitted := strings.Split(apiVersion, "/")
|
||||
if len(splitted) == 1 {
|
||||
return splitted[0]
|
||||
} else if len(splitted) == 2 {
|
||||
return splitted[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetGroup() string {
|
||||
apiVersion := w.GetApiVersion()
|
||||
splitted := strings.Split(apiVersion, "/")
|
||||
if len(splitted) == 2 {
|
||||
return splitted[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetGenerateName() string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "generateName"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetReplicas() int {
|
||||
if v, ok := InspectWorkload(w.workload, "spec", "replicas"); ok {
|
||||
replicas, isok := v.(float64)
|
||||
if isok {
|
||||
return int(replicas)
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (w *Workload) GetKind() string {
|
||||
if v, ok := InspectWorkload(w.workload, "kind"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (w *Workload) GetSelector() (*metav1.LabelSelector, error) {
|
||||
selector := &metav1.LabelSelector{}
|
||||
if v, ok := InspectWorkload(w.workload, "spec", "selector", "matchLabels"); ok && v != nil {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return selector, err
|
||||
}
|
||||
if err := json.Unmarshal(b, selector); err != nil {
|
||||
return selector, err
|
||||
}
|
||||
return selector, nil
|
||||
}
|
||||
return selector, nil
|
||||
}
|
||||
|
||||
func (w *Workload) GetAnnotation(annotation string) (string, bool) {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "annotations", annotation); ok {
|
||||
return v.(string), ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
func (w *Workload) GetLabel(label string) (string, bool) {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "labels", label); ok {
|
||||
return v.(string), ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (w *Workload) GetPodLabel(label string) (string, bool) {
|
||||
if v, ok := InspectWorkload(w.workload, append(PodMetadata(w.GetKind()), "labels", label)...); ok && v != nil {
|
||||
return v.(string), ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (w *Workload) GetLabels() map[string]string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "labels"); ok && v != nil {
|
||||
labels := make(map[string]string)
|
||||
for k, i := range v.(map[string]interface{}) {
|
||||
labels[k] = i.(string)
|
||||
}
|
||||
return labels
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInnerLabels - DEPRECATED
|
||||
func (w *Workload) GetInnerLabels() map[string]string {
|
||||
return w.GetPodLabels()
|
||||
}
|
||||
|
||||
func (w *Workload) GetPodLabels() map[string]string {
|
||||
if v, ok := InspectWorkload(w.workload, append(PodMetadata(w.GetKind()), "labels")...); ok && v != nil {
|
||||
labels := make(map[string]string)
|
||||
for k, i := range v.(map[string]interface{}) {
|
||||
labels[k] = i.(string)
|
||||
}
|
||||
return labels
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInnerAnnotations - DEPRECATED
|
||||
func (w *Workload) GetInnerAnnotations() map[string]string {
|
||||
return w.GetPodAnnotations()
|
||||
}
|
||||
|
||||
// GetPodAnnotations
|
||||
func (w *Workload) GetPodAnnotations() map[string]string {
|
||||
if v, ok := InspectWorkload(w.workload, append(PodMetadata(w.GetKind()), "annotations")...); ok && v != nil {
|
||||
annotations := make(map[string]string)
|
||||
for k, i := range v.(map[string]interface{}) {
|
||||
annotations[k] = fmt.Sprintf("%v", i)
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInnerAnnotation DEPRECATED
|
||||
func (w *Workload) GetInnerAnnotation(annotation string) (string, bool) {
|
||||
return w.GetPodAnnotation(annotation)
|
||||
}
|
||||
|
||||
func (w *Workload) GetPodAnnotation(annotation string) (string, bool) {
|
||||
if v, ok := InspectWorkload(w.workload, append(PodMetadata(w.GetKind()), "annotations", annotation)...); ok && v != nil {
|
||||
return v.(string), ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (w *Workload) GetAnnotations() map[string]string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "annotations"); ok && v != nil {
|
||||
annotations := make(map[string]string)
|
||||
for k, i := range v.(map[string]interface{}) {
|
||||
annotations[k] = fmt.Sprintf("%v", i)
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVolumes -
|
||||
func (w *Workload) GetVolumes() ([]corev1.Volume, error) {
|
||||
volumes := []corev1.Volume{}
|
||||
|
||||
interVolumes, _ := InspectWorkload(w.workload, append(PodSpec(w.GetKind()), "volumes")...)
|
||||
if interVolumes == nil {
|
||||
return volumes, nil
|
||||
}
|
||||
volumesBytes, err := json.Marshal(interVolumes)
|
||||
if err != nil {
|
||||
return volumes, err
|
||||
}
|
||||
err = json.Unmarshal(volumesBytes, &volumes)
|
||||
|
||||
return volumes, err
|
||||
}
|
||||
|
||||
func (w *Workload) GetServiceAccountName() string {
|
||||
|
||||
if v, ok := InspectWorkload(w.workload, append(PodSpec(w.GetKind()), "serviceAccountName")...); ok && v != nil {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetPodSpec() (*corev1.PodSpec, error) {
|
||||
podSpec := &corev1.PodSpec{}
|
||||
podSepcRaw, _ := InspectWorkload(w.workload, PodSpec(w.GetKind())...)
|
||||
if podSepcRaw == nil {
|
||||
return podSpec, fmt.Errorf("no PodSpec for workload: %v", w)
|
||||
}
|
||||
b, err := json.Marshal(podSepcRaw)
|
||||
if err != nil {
|
||||
return podSpec, err
|
||||
}
|
||||
err = json.Unmarshal(b, podSpec)
|
||||
|
||||
return podSpec, err
|
||||
}
|
||||
|
||||
func (w *Workload) GetImagePullSecret() ([]corev1.LocalObjectReference, error) {
|
||||
imgPullSecrets := []corev1.LocalObjectReference{}
|
||||
|
||||
iImgPullSecrets, _ := InspectWorkload(w.workload, append(PodSpec(w.GetKind()), "imagePullSecrets")...)
|
||||
b, err := json.Marshal(iImgPullSecrets)
|
||||
if err != nil {
|
||||
return imgPullSecrets, err
|
||||
}
|
||||
err = json.Unmarshal(b, &imgPullSecrets)
|
||||
|
||||
return imgPullSecrets, err
|
||||
}
|
||||
|
||||
// GetContainers -
|
||||
func (w *Workload) GetContainers() ([]corev1.Container, error) {
|
||||
containers := []corev1.Container{}
|
||||
|
||||
interContainers, _ := InspectWorkload(w.workload, append(PodSpec(w.GetKind()), "containers")...)
|
||||
if interContainers == nil {
|
||||
return containers, nil
|
||||
}
|
||||
containersBytes, err := json.Marshal(interContainers)
|
||||
if err != nil {
|
||||
return containers, err
|
||||
}
|
||||
err = json.Unmarshal(containersBytes, &containers)
|
||||
|
||||
return containers, err
|
||||
}
|
||||
|
||||
// GetInitContainers -
|
||||
func (w *Workload) GetInitContainers() ([]corev1.Container, error) {
|
||||
containers := []corev1.Container{}
|
||||
|
||||
interContainers, _ := InspectWorkload(w.workload, append(PodSpec(w.GetKind()), "initContainers")...)
|
||||
if interContainers == nil {
|
||||
return containers, nil
|
||||
}
|
||||
containersBytes, err := json.Marshal(interContainers)
|
||||
if err != nil {
|
||||
return containers, err
|
||||
}
|
||||
err = json.Unmarshal(containersBytes, &containers)
|
||||
|
||||
return containers, err
|
||||
}
|
||||
|
||||
// GetOwnerReferences -
|
||||
func (w *Workload) GetOwnerReferences() ([]metav1.OwnerReference, error) {
|
||||
ownerReferences := []metav1.OwnerReference{}
|
||||
interOwnerReferences, ok := InspectWorkload(w.workload, "metadata", "ownerReferences")
|
||||
if !ok {
|
||||
return ownerReferences, nil
|
||||
}
|
||||
|
||||
ownerReferencesBytes, err := json.Marshal(interOwnerReferences)
|
||||
if err != nil {
|
||||
return ownerReferences, err
|
||||
}
|
||||
err = json.Unmarshal(ownerReferencesBytes, &ownerReferences)
|
||||
if err != nil {
|
||||
return ownerReferences, err
|
||||
|
||||
}
|
||||
return ownerReferences, nil
|
||||
}
|
||||
func (w *Workload) GetResourceVersion() string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "resourceVersion"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (w *Workload) GetUID() string {
|
||||
if v, ok := InspectWorkload(w.workload, "metadata", "uid"); ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (w *Workload) GetWlid() string {
|
||||
if wlid, ok := w.GetAnnotation(cautils.ArmoWlid); ok {
|
||||
return wlid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Workload) GetJobID() *apis.JobTracking {
|
||||
jobTracking := apis.JobTracking{}
|
||||
if job, ok := w.GetPodAnnotation(cautils.ArmoJobIDPath); ok {
|
||||
jobTracking.JobID = job
|
||||
}
|
||||
if parent, ok := w.GetPodAnnotation(cautils.ArmoJobParentPath); ok {
|
||||
jobTracking.ParentID = parent
|
||||
}
|
||||
if action, ok := w.GetPodAnnotation(cautils.ArmoJobActionPath); ok {
|
||||
if i, err := strconv.Atoi(action); err == nil {
|
||||
jobTracking.LastActionNumber = i
|
||||
}
|
||||
}
|
||||
if jobTracking.LastActionNumber == 0 { // start the counter at 1
|
||||
jobTracking.LastActionNumber = 1
|
||||
}
|
||||
return &jobTracking
|
||||
}
|
||||
|
||||
// func (w *Workload) GetJobID() string {
|
||||
// if status, ok := w.GetAnnotation(cautils.ArmoJobID); ok {
|
||||
// return status
|
||||
// }
|
||||
// return ""
|
||||
// }
|
||||
|
||||
// ========================================= IS =========================================
|
||||
|
||||
func (w *Workload) IsInject() bool {
|
||||
return w.IsAttached()
|
||||
}
|
||||
|
||||
func (w *Workload) IsIgnore() bool {
|
||||
if attach := cautils.IsAttached(w.GetPodLabels()); attach != nil {
|
||||
return !(*attach)
|
||||
}
|
||||
if attach := cautils.IsAttached(w.GetLabels()); attach != nil {
|
||||
return !(*attach)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Workload) IsCompatible() bool {
|
||||
if c, ok := w.GetPodAnnotation(cautils.ArmoCompatibleAnnotation); ok {
|
||||
return cautils.StringToBool(c)
|
||||
|
||||
}
|
||||
if c, ok := w.GetAnnotation(cautils.ArmoCompatibleAnnotation); ok {
|
||||
return cautils.StringToBool(c)
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Workload) IsIncompatible() bool {
|
||||
if c, ok := w.GetPodAnnotation(cautils.ArmoCompatibleAnnotation); ok {
|
||||
return !cautils.StringToBool(c)
|
||||
}
|
||||
if c, ok := w.GetAnnotation(cautils.ArmoCompatibleAnnotation); ok {
|
||||
return !cautils.StringToBool(c)
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (w *Workload) IsAttached() bool {
|
||||
if attach := cautils.IsAttached(w.GetPodLabels()); attach != nil {
|
||||
return *attach
|
||||
}
|
||||
if attach := cautils.IsAttached(w.GetLabels()); attach != nil {
|
||||
return *attach
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Workload) IsReplaceheaders() bool {
|
||||
if c, ok := w.GetPodAnnotation(cautils.ArmoReplaceheaders); ok {
|
||||
return cautils.StringToBool(c)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ======================================= UTILS =========================================
|
||||
|
||||
// InspectWorkload -
|
||||
func InspectWorkload(workload interface{}, scopes ...string) (val interface{}, k bool) {
|
||||
|
||||
val, k = nil, false
|
||||
if len(scopes) == 0 {
|
||||
if workload != nil {
|
||||
return workload, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
if data, ok := workload.(map[string]interface{}); ok {
|
||||
val, k = InspectWorkload(data[scopes[0]], scopes[1:]...)
|
||||
}
|
||||
return val, k
|
||||
|
||||
}
|
||||
155
cautils/k8sinterface/workloadmethods_test.go
Normal file
155
cautils/k8sinterface/workloadmethods_test.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package k8sinterface
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ========================================= IS =========================================
|
||||
|
||||
func TestLabels(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"labels":{"app":"demoservice-server","cyberarmor.inject":"true"},"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if workload.GetKind() != "Deployment" {
|
||||
t.Errorf("wrong kind")
|
||||
}
|
||||
if workload.GetNamespace() != "default" {
|
||||
t.Errorf("wrong namespace")
|
||||
}
|
||||
if workload.GetName() != "demoservice-server" {
|
||||
t.Errorf("wrong name")
|
||||
}
|
||||
if !workload.IsInject() {
|
||||
t.Errorf("expect to find inject label")
|
||||
}
|
||||
if workload.IsIgnore() {
|
||||
t.Errorf("expect to find ignore label")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetNamespace(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"demoservice-server"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"demoservice-server"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
workload.SetNamespace("default")
|
||||
if workload.GetNamespace() != "default" {
|
||||
t.Errorf("wrong namespace")
|
||||
}
|
||||
}
|
||||
func TestSetLabels(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
workload.SetLabel("bla", "daa")
|
||||
v, ok := workload.GetLabel("bla")
|
||||
if !ok || v != "daa" {
|
||||
t.Errorf("expect to find label")
|
||||
}
|
||||
workload.RemoveLabel("bla")
|
||||
v2, ok2 := workload.GetLabel("bla")
|
||||
if ok2 || v2 == "daa" {
|
||||
t.Errorf("label not deleted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetAnnotations(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
workload.SetAnnotation("bla", "daa")
|
||||
v, ok := workload.GetAnnotation("bla")
|
||||
if !ok || v != "daa" {
|
||||
t.Errorf("expect to find annotation")
|
||||
}
|
||||
workload.RemoveAnnotation("bla")
|
||||
v2, ok2 := workload.GetAnnotation("bla")
|
||||
if ok2 || v2 == "daa" {
|
||||
t.Errorf("annotation not deleted")
|
||||
}
|
||||
}
|
||||
func TestSetPodLabels(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
workload.SetPodLabel("bla", "daa")
|
||||
v, ok := workload.GetPodLabel("bla")
|
||||
if !ok || v != "daa" {
|
||||
t.Errorf("expect to find label")
|
||||
}
|
||||
workload.RemovePodLabel("bla")
|
||||
v2, ok2 := workload.GetPodLabel("bla")
|
||||
if ok2 || v2 == "daa" {
|
||||
t.Errorf("label not deleted")
|
||||
}
|
||||
}
|
||||
func TestRemoveArmo(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server", "armo.attach": "true"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if !workload.IsAttached() {
|
||||
t.Errorf("expect to be attached")
|
||||
}
|
||||
workload.RemoveArmoMetadata()
|
||||
if workload.IsAttached() {
|
||||
t.Errorf("expect to be clear")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSetWlid(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
workload.SetWlid("wlid://bla")
|
||||
// t.Errorf(workload.Json())
|
||||
|
||||
}
|
||||
|
||||
func TestGetResourceVersion(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if workload.GetResourceVersion() != "1016043" {
|
||||
t.Errorf("wrong resourceVersion")
|
||||
}
|
||||
|
||||
}
|
||||
func TestGetUID(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"1"},"creationTimestamp":"2021-05-03T13:10:32Z","generation":1,"managedFields":[{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:labels":{".":{},"f:app":{},"f:cyberarmor.inject":{}}},"f:spec":{"f:progressDeadlineSeconds":{},"f:replicas":{},"f:revisionHistoryLimit":{},"f:selector":{},"f:strategy":{"f:rollingUpdate":{".":{},"f:maxSurge":{},"f:maxUnavailable":{}},"f:type":{}},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"demoservice\"}":{".":{},"f:env":{".":{},"k:{\"name\":\"ARMO_TEST_NAME\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"CAA_ENABLE_CRASH_REPORTER\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"DEMO_FOLDERS\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SERVER_PORT\"}":{".":{},"f:name":{},"f:value":{}},"k:{\"name\":\"SLEEP_DURATION\"}":{".":{},"f:name":{},"f:value":{}}},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8089,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}},"manager":"OpenAPI-Generator","operation":"Update","time":"2021-05-03T13:10:32Z"},{"apiVersion":"apps/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:deployment.kubernetes.io/revision":{}}},"f:status":{"f:availableReplicas":{},"f:conditions":{".":{},"k:{\"type\":\"Available\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"Progressing\"}":{".":{},"f:lastTransitionTime":{},"f:lastUpdateTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReplicas":{}}},"manager":"kube-controller-manager","operation":"Update","time":"2021-05-03T13:52:58Z"}],"name":"demoservice-server","namespace":"default","resourceVersion":"1016043","uid":"e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"demoservice-server"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"demoservice-server"}},"spec":{"containers":[{"env":[{"name":"SERVER_PORT","value":"8089"},{"name":"SLEEP_DURATION","value":"1"},{"name":"DEMO_FOLDERS","value":"/app"},{"name":"ARMO_TEST_NAME","value":"auto_attach_deployment"},{"name":"CAA_ENABLE_CRASH_REPORTER","value":"1"}],"image":"quay.io/armosec/demoservice:v25","imagePullPolicy":"IfNotPresent","name":"demoservice","ports":[{"containerPort":8089,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"terminationGracePeriodSeconds":30}}},"status":{"availableReplicas":1,"conditions":[{"lastTransitionTime":"2021-05-03T13:10:32Z","lastUpdateTime":"2021-05-03T13:10:37Z","message":"ReplicaSet \"demoservice-server-7d478b6998\" has successfully progressed.","reason":"NewReplicaSetAvailable","status":"True","type":"Progressing"},{"lastTransitionTime":"2021-05-03T13:52:58Z","lastUpdateTime":"2021-05-03T13:52:58Z","message":"Deployment has minimum availability.","reason":"MinimumReplicasAvailable","status":"True","type":"Available"}],"observedGeneration":1,"readyReplicas":1,"replicas":1,"updatedReplicas":1}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if workload.GetUID() != "e9e8a3e9-6cb4-4301-ace1-2c0cef3bd61e" {
|
||||
t.Errorf("wrong UID")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIsAttached(t *testing.T) {
|
||||
w := `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"deployment.kubernetes.io/revision":"3"},"creationTimestamp":"2021-06-21T04:52:05Z","generation":3,"name":"emailservice","namespace":"default"},"spec":{"progressDeadlineSeconds":600,"replicas":1,"revisionHistoryLimit":10,"selector":{"matchLabels":{"app":"emailservice"}},"strategy":{"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"},"template":{"metadata":{"annotations":{"armo.last-update":"21-06-2021 06:40:42","armo.wlid":"wlid://cluster-david-demo/namespace-default/deployment-emailservice"},"creationTimestamp":null,"labels":{"app":"emailservice","armo.attach":"true"}},"spec":{"containers":[{"env":[{"name":"PORT","value":"8080"},{"name":"DISABLE_PROFILER","value":"1"}],"image":"gcr.io/google-samples/microservices-demo/emailservice:v0.2.3","imagePullPolicy":"IfNotPresent","livenessProbe":{"exec":{"command":["/bin/grpc_health_probe","-addr=:8080"]},"failureThreshold":3,"periodSeconds":5,"successThreshold":1,"timeoutSeconds":1},"name":"server","ports":[{"containerPort":8080,"protocol":"TCP"}],"readinessProbe":{"exec":{"command":["/bin/grpc_health_probe","-addr=:8080"]},"failureThreshold":3,"periodSeconds":5,"successThreshold":1,"timeoutSeconds":1},"resources":{"limits":{"cpu":"200m","memory":"128Mi"},"requests":{"cpu":"100m","memory":"64Mi"}},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File"}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always","schedulerName":"default-scheduler","securityContext":{},"serviceAccount":"default","serviceAccountName":"default","terminationGracePeriodSeconds":5}}}}`
|
||||
workload, err := NewWorkload([]byte(w))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
if !workload.IsAttached() {
|
||||
t.Errorf("expected attached")
|
||||
}
|
||||
|
||||
}
|
||||
23
cautils/k8sinterface/workloadmethodsutils.go
Normal file
23
cautils/k8sinterface/workloadmethodsutils.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package k8sinterface
|
||||
|
||||
func PodSpec(kind string) []string {
|
||||
switch kind {
|
||||
case "Pod", "Namespace":
|
||||
return []string{"spec"}
|
||||
case "CronJob":
|
||||
return []string{"spec", "jobTemplate", "spec", "template", "spec"}
|
||||
default:
|
||||
return []string{"spec", "template", "spec"}
|
||||
}
|
||||
}
|
||||
|
||||
func PodMetadata(kind string) []string {
|
||||
switch kind {
|
||||
case "Pod", "Namespace", "Secret":
|
||||
return []string{"metadata"}
|
||||
case "CronJob":
|
||||
return []string{"spec", "jobTemplate", "spec", "template", "metadata"}
|
||||
default:
|
||||
return []string{"spec", "template", "metadata"}
|
||||
}
|
||||
}
|
||||
7
cautils/opapolicy/apis.go
Normal file
7
cautils/opapolicy/apis.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package opapolicy
|
||||
|
||||
const (
|
||||
PostureRestAPIPathV1 = "/v1/posture"
|
||||
PostureRedisPrefix = "_postureReportv1"
|
||||
K8sPostureNotification = "/k8srestapi/v1/newPostureReport"
|
||||
)
|
||||
161
cautils/opapolicy/datastructures.go
Normal file
161
cautils/opapolicy/datastructures.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package opapolicy
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
armotypes "github.com/armosec/kubescape/cautils/armotypes"
|
||||
)
|
||||
|
||||
type AlertScore float32
|
||||
type RuleLanguages string
|
||||
|
||||
const (
|
||||
RegoLanguage RuleLanguages = "Rego"
|
||||
RegoLanguage2 RuleLanguages = "rego"
|
||||
)
|
||||
|
||||
// RegoResponse the expected response of single run of rego policy
|
||||
type RuleResponse struct {
|
||||
AlertMessage string `json:"alertMessage"`
|
||||
RuleStatus string `json:"ruleStatus"`
|
||||
PackageName string `json:"packagename"`
|
||||
AlertScore AlertScore `json:"alertScore"`
|
||||
AlertObject AlertObject `json:"alertObject"`
|
||||
Context []string `json:"context,omitempty"` // TODO - Remove
|
||||
Rulename string `json:"rulename,omitempty"` // TODO - Remove
|
||||
ExceptionName string `json:"exceptionName,omitempty"` // Not in use
|
||||
Exception *armotypes.PostureExceptionPolicy `json:"exception,omitempty"`
|
||||
}
|
||||
|
||||
type AlertObject struct {
|
||||
K8SApiObjects []map[string]interface{} `json:"k8sApiObjects,omitempty"`
|
||||
ExternalObjects map[string]interface{} `json:"externalObjects,omitempty"`
|
||||
}
|
||||
|
||||
type FrameworkReport struct {
|
||||
Name string `json:"name"`
|
||||
ControlReports []ControlReport `json:"controlReports"`
|
||||
Score float32 `json:"score,omitempty"`
|
||||
ARMOImprovement float32 `json:"ARMOImprovement,omitempty"`
|
||||
WCSScore float32 `json:"wcsScore,omitempty"`
|
||||
}
|
||||
type ControlReport struct {
|
||||
armotypes.PortalBase `json:",inline"`
|
||||
ControlID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
RuleReports []RuleReport `json:"ruleReports"`
|
||||
Remediation string `json:"remediation"`
|
||||
Description string `json:"description"`
|
||||
Score float32 `json:"score"`
|
||||
BaseScore float32 `json:"baseScore,omitempty"`
|
||||
ARMOImprovement float32 `json:"ARMOImprovement,omitempty"`
|
||||
}
|
||||
type RuleReport struct {
|
||||
Name string `json:"name"`
|
||||
Remediation string `json:"remediation"`
|
||||
RuleStatus RuleStatus `json:"ruleStatus"` // did we run the rule or not (if there where compile errors, the value will be failed)
|
||||
RuleResponses []RuleResponse `json:"ruleResponses"`
|
||||
ListInputResources []map[string]interface{} `json:"-"`
|
||||
ListInputKinds []string `json:"-"`
|
||||
}
|
||||
type RuleStatus struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// PostureReport
|
||||
type PostureReport struct {
|
||||
CustomerGUID string `json:"customerGUID"`
|
||||
ClusterName string `json:"clusterName"`
|
||||
ReportID string `json:"reportID"`
|
||||
JobID string `json:"jobID"`
|
||||
ReportGenerationTime time.Time `json:"generationTime"`
|
||||
FrameworkReports []FrameworkReport `json:"frameworks"`
|
||||
}
|
||||
|
||||
// RuleMatchObjects defines which objects this rule applied on
|
||||
type RuleMatchObjects struct {
|
||||
APIGroups []string `json:"apiGroups"` // apps
|
||||
APIVersions []string `json:"apiVersions"` // v1/ v1beta1 / *
|
||||
Resources []string `json:"resources"` // dep.., pods,
|
||||
}
|
||||
|
||||
// RuleMatchObjects defines which objects this rule applied on
|
||||
type RuleDependency struct {
|
||||
PackageName string `json:"packageName"` // package name
|
||||
}
|
||||
|
||||
// PolicyRule represents single rule, the fundamental executable block of policy
|
||||
type PolicyRule struct {
|
||||
armotypes.PortalBase `json:",inline"`
|
||||
CreationTime string `json:"creationTime"`
|
||||
Rule string `json:"rule"` // multiline string!
|
||||
RuleLanguage RuleLanguages `json:"ruleLanguage"`
|
||||
Match []RuleMatchObjects `json:"match"`
|
||||
RuleDependencies []RuleDependency `json:"ruleDependencies"`
|
||||
Description string `json:"description"`
|
||||
Remediation string `json:"remediation"`
|
||||
RuleQuery string `json:"ruleQuery"` // default "armo_builtins" - DEPRECATED
|
||||
}
|
||||
|
||||
// Control represents a collection of rules which are combined together to single purpose
|
||||
type Control struct {
|
||||
armotypes.PortalBase `json:",inline"`
|
||||
ControlID string `json:"id"`
|
||||
CreationTime string `json:"creationTime"`
|
||||
Description string `json:"description"`
|
||||
Remediation string `json:"remediation"`
|
||||
Rules []PolicyRule `json:"rules"`
|
||||
// for new list of rules in POST/UPADTE requests
|
||||
RulesIDs *[]string `json:"rulesIDs,omitempty"`
|
||||
}
|
||||
|
||||
type UpdatedControl struct {
|
||||
Control `json:",inline"`
|
||||
Rules []interface{} `json:"rules"`
|
||||
}
|
||||
|
||||
// Framework represents a collection of controls which are combined together to expose comprehensive behavior
|
||||
type Framework struct {
|
||||
armotypes.PortalBase `json:",inline"`
|
||||
CreationTime string `json:"creationTime"`
|
||||
Description string `json:"description"`
|
||||
Controls []Control `json:"controls"`
|
||||
// for new list of controls in POST/UPADTE requests
|
||||
ControlsIDs *[]string `json:"controlsIDs,omitempty"`
|
||||
}
|
||||
|
||||
type UpdatedFramework struct {
|
||||
Framework `json:",inline"`
|
||||
Controls []interface{} `json:"controls"`
|
||||
}
|
||||
|
||||
type NotificationPolicyType string
|
||||
type NotificationPolicyKind string
|
||||
|
||||
// Supported NotificationTypes
|
||||
const (
|
||||
TypeValidateRules NotificationPolicyType = "validateRules"
|
||||
TypeExecPostureScan NotificationPolicyType = "execPostureScan"
|
||||
TypeUpdateRules NotificationPolicyType = "updateRules"
|
||||
)
|
||||
|
||||
// Supported NotificationKinds
|
||||
const (
|
||||
KindFramework NotificationPolicyKind = "Framework"
|
||||
KindControl NotificationPolicyKind = "Control"
|
||||
KindRule NotificationPolicyKind = "Rule"
|
||||
)
|
||||
|
||||
type PolicyNotification struct {
|
||||
NotificationType NotificationPolicyType `json:"notificationType"`
|
||||
Rules []PolicyIdentifier `json:"rules"`
|
||||
ReportID string `json:"reportID"`
|
||||
JobID string `json:"jobID"`
|
||||
Designators armotypes.PortalDesignator `json:"designators"`
|
||||
}
|
||||
|
||||
type PolicyIdentifier struct {
|
||||
Kind NotificationPolicyKind `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
301
cautils/opapolicy/datastructures_mock.go
Normal file
301
cautils/opapolicy/datastructures_mock.go
Normal file
@@ -0,0 +1,301 @@
|
||||
package opapolicy
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
armotypes "github.com/armosec/kubescape/cautils/armotypes"
|
||||
)
|
||||
|
||||
// Mock A
|
||||
var (
|
||||
AMockCustomerGUID = "5d817063-096f-4d91-b39b-8665240080af"
|
||||
AMockJobID = "36b6f9e1-3b63-4628-994d-cbe16f81e9c7"
|
||||
AMockReportID = "2c31e4da-c6fe-440d-9b8a-785b80c8576a"
|
||||
AMockClusterName = "clusterA"
|
||||
AMockFrameworkName = "testFrameworkA"
|
||||
AMockControlName = "testControlA"
|
||||
AMockRuleName = "testRuleA"
|
||||
AMockPortalBase = *armotypes.MockPortalBase(AMockCustomerGUID, "", nil)
|
||||
)
|
||||
|
||||
func MockRuleResponseA() *RuleResponse {
|
||||
return &RuleResponse{
|
||||
AlertMessage: "test alert message A",
|
||||
AlertScore: 0,
|
||||
Rulename: AMockRuleName,
|
||||
PackageName: "test.package.name.A",
|
||||
Context: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func MockFrameworkReportA() *FrameworkReport {
|
||||
return &FrameworkReport{
|
||||
Name: AMockFrameworkName,
|
||||
ControlReports: []ControlReport{
|
||||
{
|
||||
ControlID: "C-0010",
|
||||
Name: AMockControlName,
|
||||
RuleReports: []RuleReport{
|
||||
{
|
||||
Name: AMockRuleName,
|
||||
Remediation: "remove privilegedContainer: True flag from your pod spec",
|
||||
RuleResponses: []RuleResponse{
|
||||
*MockRuleResponseA(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MockPostureReportA() *PostureReport {
|
||||
return &PostureReport{
|
||||
CustomerGUID: AMockCustomerGUID,
|
||||
ClusterName: AMockClusterName,
|
||||
ReportID: AMockReportID,
|
||||
JobID: AMockJobID,
|
||||
ReportGenerationTime: time.Now().UTC(),
|
||||
FrameworkReports: []FrameworkReport{*MockFrameworkReportA()},
|
||||
}
|
||||
}
|
||||
|
||||
func MockFrameworkA() *Framework {
|
||||
return &Framework{
|
||||
PortalBase: *armotypes.MockPortalBase("aaaaaaaa-096f-4d91-b39b-8665240080af", AMockFrameworkName, nil),
|
||||
CreationTime: "",
|
||||
Description: "mock framework descryption",
|
||||
Controls: []Control{
|
||||
{
|
||||
PortalBase: *armotypes.MockPortalBase("aaaaaaaa-aaaa-4d91-b39b-8665240080af", AMockControlName, nil),
|
||||
Rules: []PolicyRule{
|
||||
*MockRuleA(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MockRuleUntrustedRegistries() *PolicyRule {
|
||||
return &PolicyRule{
|
||||
PortalBase: *armotypes.MockPortalBase("aaaaaaaa-aaaa-aaaa-b39b-8665240080af", AMockControlName, nil),
|
||||
Rule: `
|
||||
package armo_builtins
|
||||
# Check for images from blacklisted repos
|
||||
|
||||
untrusted_registries(z) = x {
|
||||
x := ["015253967648.dkr.ecr.eu-central-1.amazonaws.com/"]
|
||||
}
|
||||
|
||||
public_registries(z) = y{
|
||||
y := ["quay.io/kiali/","quay.io/datawire/","quay.io/keycloak/","quay.io/bitnami/"]
|
||||
}
|
||||
|
||||
untrustedImageRepo[msga] {
|
||||
pod := input[_]
|
||||
k := pod.kind
|
||||
k == "Pod"
|
||||
container := pod.spec.containers[_]
|
||||
image := container.image
|
||||
repo_prefix := untrusted_registries(image)[_]
|
||||
startswith(image, repo_prefix)
|
||||
selfLink := pod.metadata.selfLink
|
||||
containerName := container.name
|
||||
|
||||
msga := {
|
||||
"alertMessage": sprintf("image '%v' in container '%s' in [%s] comes from untrusted registry", [image, containerName, selfLink]),
|
||||
"alert": true,
|
||||
"prevent": false,
|
||||
"alertScore": 2,
|
||||
"alertObject": [{"pod":pod}]
|
||||
}
|
||||
}
|
||||
|
||||
untrustedImageRepo[msga] {
|
||||
pod := input[_]
|
||||
k := pod.kind
|
||||
k == "Pod"
|
||||
container := pod.spec.containers[_]
|
||||
image := container.image
|
||||
repo_prefix := public_registries(image)[_]
|
||||
startswith(pod, repo_prefix)
|
||||
selfLink := input.metadata.selfLink
|
||||
containerName := container.name
|
||||
|
||||
msga := {
|
||||
"alertMessage": sprintf("image '%v' in container '%s' in [%s] comes from public registry", [image, containerName, selfLink]),
|
||||
"alert": true,
|
||||
"prevent": false,
|
||||
"alertScore": 1,
|
||||
"alertObject": [{"pod":pod}]
|
||||
}
|
||||
}
|
||||
`,
|
||||
RuleLanguage: RegoLanguage,
|
||||
Match: []RuleMatchObjects{
|
||||
{
|
||||
APIVersions: []string{"v1"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
},
|
||||
RuleDependencies: []RuleDependency{
|
||||
{
|
||||
PackageName: "kubernetes.api.client",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MockRuleA() *PolicyRule {
|
||||
return &PolicyRule{
|
||||
PortalBase: *armotypes.MockPortalBase("aaaaaaaa-aaaa-aaaa-b39b-8665240080af", AMockControlName, nil),
|
||||
Rule: MockRegoPrivilegedPods(), //
|
||||
RuleLanguage: RegoLanguage,
|
||||
Match: []RuleMatchObjects{
|
||||
{
|
||||
APIVersions: []string{"v1"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
},
|
||||
RuleDependencies: []RuleDependency{
|
||||
{
|
||||
PackageName: "kubernetes.api.client",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MockRuleB() *PolicyRule {
|
||||
return &PolicyRule{
|
||||
PortalBase: *armotypes.MockPortalBase("bbbbbbbb-aaaa-aaaa-b39b-8665240080af", AMockControlName, nil),
|
||||
Rule: MockExternalFacingService(), //
|
||||
RuleLanguage: RegoLanguage,
|
||||
Match: []RuleMatchObjects{
|
||||
{
|
||||
APIVersions: []string{"v1"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
},
|
||||
RuleDependencies: []RuleDependency{
|
||||
{
|
||||
PackageName: "kubernetes.api.client",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MockPolicyNotificationA() *PolicyNotification {
|
||||
return &PolicyNotification{
|
||||
NotificationType: TypeExecPostureScan,
|
||||
ReportID: AMockReportID,
|
||||
JobID: AMockJobID,
|
||||
Designators: armotypes.PortalDesignator{},
|
||||
Rules: []PolicyIdentifier{
|
||||
{
|
||||
Kind: KindFramework,
|
||||
Name: AMockFrameworkName,
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func MockTemp() string {
|
||||
return `
|
||||
package armo_builtins
|
||||
import data.kubernetes.api.client as client
|
||||
deny[msga] {
|
||||
#object := input[_]
|
||||
object := client.query_all("pods")
|
||||
obj := object.body.items[_]
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": "found object",
|
||||
"alertScore": 3,
|
||||
"alertObject": {"object": obj},
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
func MockRegoPrivilegedPods() string {
|
||||
return `package armo_builtins
|
||||
|
||||
import data.kubernetes.api.client as client
|
||||
|
||||
# Deny mutating action unless user is in group owning the resource
|
||||
|
||||
#privileged pods
|
||||
deny[msga] {
|
||||
|
||||
pod := input[_]
|
||||
containers := pod.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following pods are defined as privileged: %v", [pod]),
|
||||
"alertScore": 3,
|
||||
"alertObject": pod,
|
||||
}
|
||||
}
|
||||
|
||||
#handles majority of workload resources
|
||||
deny[msga] {
|
||||
wl := input[_]
|
||||
spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"}
|
||||
spec_template_spec_patterns[wl.kind]
|
||||
containers := wl.spec.template.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following workloads are defined as privileged: %v", [wl]),
|
||||
"alertScore": 3,
|
||||
"alertObject": wl,
|
||||
}
|
||||
}
|
||||
|
||||
#handles cronjob
|
||||
deny[msga] {
|
||||
wl := input[_]
|
||||
wl.kind == "CronJob"
|
||||
containers := wl.spec.jobTemplate.spec.template.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following cronjobs are defined as privileged: %v", [wl]),
|
||||
"alertScore": 3,
|
||||
"alertObject": wl,
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
func MockExternalFacingService() string {
|
||||
return "\n\tpackage armo_builtins\n\n\timport data.kubernetes.api.client as client\n\timport data.cautils as cautils\n\ndeny[msga] {\n\n\twl := input[_]\n\tcluster_resource := client.query_all(\n\t\t\"services\"\n\t)\n\n\tlabels := wl.metadata.labels\n\tfiltered_labels := json.remove(labels, [\"pod-template-hash\"])\n \n#service := cluster_resource.body.items[i]\nservices := [svc | cluster_resource.body.items[i].metadata.namespace == wl.metadata.namespace; svc := cluster_resource.body.items[i]]\nservice := services[_]\nnp_or_lb := {\"NodePort\", \"LoadBalancer\"}\nnp_or_lb[service.spec.type]\ncautils.is_subobject(service.spec.selector,filtered_labels)\n\n msga := {\n\t\t\"alertMessage\": sprintf(\"%v pod %v expose external facing service: %v\",[wl.metadata.namespace, wl.metadata.name, service.metadata.name]),\n\t\t\"alertScore\": 2,\n\t\t\"packagename\": \"armo_builtins\",\n\t\t\"alertObject\": {\"srvc\":service}\n\t}\n}\n\t"
|
||||
}
|
||||
func GetRuntimePods() string {
|
||||
return `
|
||||
package armo_builtins
|
||||
|
||||
import data.kubernetes.api.client as client
|
||||
|
||||
|
||||
deny[msga] {
|
||||
|
||||
|
||||
cluster_resource := client.query_all(
|
||||
"pods"
|
||||
)
|
||||
|
||||
pod := cluster_resource.body.items[i]
|
||||
msga := {
|
||||
"alertMessage": "got something",
|
||||
"alertScore": 2,
|
||||
"packagename": "armo_builtins",
|
||||
"alertObject": {"pod": pod}
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
}
|
||||
42
cautils/opapolicy/datastructures_test.go
Normal file
42
cautils/opapolicy/datastructures_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package opapolicy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMockPolicyNotificationA(t *testing.T) {
|
||||
policy := MockPolicyNotificationA()
|
||||
bp, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Logf("%s\n", string(bp))
|
||||
// t.Errorf("%s\n", string(bp))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMockFrameworkA(t *testing.T) {
|
||||
policy := MockFrameworkA()
|
||||
bp, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Logf("%s\n", string(bp))
|
||||
// t.Errorf("%s\n", string(bp))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMockPostureReportA(t *testing.T) {
|
||||
policy := MockPostureReportA()
|
||||
bp, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
// t.Errorf("%s\n", string(bp))
|
||||
t.Logf("%s\n", string(bp))
|
||||
}
|
||||
|
||||
}
|
||||
236
cautils/opapolicy/datastructuresmethods.go
Normal file
236
cautils/opapolicy/datastructuresmethods.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package opapolicy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/armosec/kubescape/cautils/k8sinterface"
|
||||
)
|
||||
|
||||
func (pn *PolicyNotification) ToJSONBytesBuffer() (*bytes.Buffer, error) {
|
||||
res, err := json.Marshal(pn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewBuffer(res), err
|
||||
}
|
||||
|
||||
func (RuleResponse *RuleResponse) GetSingleResultStatus() string {
|
||||
if RuleResponse.Exception != nil {
|
||||
if RuleResponse.Exception.IsAlertOnly() {
|
||||
return "warning"
|
||||
}
|
||||
if RuleResponse.Exception.IsDisable() {
|
||||
return "ignore"
|
||||
}
|
||||
}
|
||||
return "failed"
|
||||
|
||||
}
|
||||
|
||||
func (ruleReport *RuleReport) GetRuleStatus() (string, []RuleResponse, []RuleResponse) {
|
||||
if len(ruleReport.RuleResponses) == 0 {
|
||||
return "success", nil, nil
|
||||
}
|
||||
exceptions := make([]RuleResponse, 0)
|
||||
failed := make([]RuleResponse, 0)
|
||||
|
||||
for _, rule := range ruleReport.RuleResponses {
|
||||
if rule.ExceptionName != "" {
|
||||
exceptions = append(exceptions, rule)
|
||||
} else if rule.Exception != nil {
|
||||
exceptions = append(exceptions, rule)
|
||||
} else {
|
||||
failed = append(failed, rule)
|
||||
}
|
||||
}
|
||||
|
||||
status := "failed"
|
||||
if len(failed) == 0 && len(exceptions) > 0 {
|
||||
status = "warning"
|
||||
}
|
||||
return status, failed, exceptions
|
||||
}
|
||||
|
||||
func (controlReport *ControlReport) GetNumberOfResources() int {
|
||||
sum := 0
|
||||
for i := range controlReport.RuleReports {
|
||||
sum += controlReport.RuleReports[i].GetNumberOfResources()
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (controlReport *ControlReport) GetNumberOfFailedResources() int {
|
||||
sum := 0
|
||||
for i := range controlReport.RuleReports {
|
||||
sum += controlReport.RuleReports[i].GetNumberOfFailedResources()
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (controlReport *ControlReport) GetNumberOfWarningResources() int {
|
||||
sum := 0
|
||||
for i := range controlReport.RuleReports {
|
||||
sum += controlReport.RuleReports[i].GetNumberOfWarningResources()
|
||||
}
|
||||
return sum
|
||||
}
|
||||
func (controlReport *ControlReport) ListControlsInputKinds() []string {
|
||||
listControlsInputKinds := []string{}
|
||||
for i := range controlReport.RuleReports {
|
||||
listControlsInputKinds = append(listControlsInputKinds, controlReport.RuleReports[i].ListInputKinds...)
|
||||
}
|
||||
return listControlsInputKinds
|
||||
}
|
||||
|
||||
func (controlReport *ControlReport) Passed() bool {
|
||||
for i := range controlReport.RuleReports {
|
||||
if len(controlReport.RuleReports[i].RuleResponses) != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (controlReport *ControlReport) Warning() bool {
|
||||
if controlReport.Passed() || controlReport.Failed() {
|
||||
return false
|
||||
}
|
||||
for i := range controlReport.RuleReports {
|
||||
if status, _, _ := controlReport.RuleReports[i].GetRuleStatus(); status == "warning" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (controlReport *ControlReport) Failed() bool {
|
||||
if controlReport.Passed() {
|
||||
return false
|
||||
}
|
||||
for i := range controlReport.RuleReports {
|
||||
if status, _, _ := controlReport.RuleReports[i].GetRuleStatus(); status == "failed" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ruleReport *RuleReport) GetNumberOfResources() int {
|
||||
return len(ruleReport.ListInputResources)
|
||||
}
|
||||
|
||||
func (ruleReport *RuleReport) GetNumberOfFailedResources() int {
|
||||
sum := 0
|
||||
for i := len(ruleReport.RuleResponses) - 1; i >= 0; i-- {
|
||||
if ruleReport.RuleResponses[i].GetSingleResultStatus() == "failed" {
|
||||
if !ruleReport.DeleteIfRedundantResponse(&ruleReport.RuleResponses[i], i) {
|
||||
sum++
|
||||
}
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (ruleReport *RuleReport) DeleteIfRedundantResponse(RuleResponse *RuleResponse, index int) bool {
|
||||
if b, rr := ruleReport.IsDuplicateResponseOfResource(RuleResponse, index); b {
|
||||
rr.AddMessageToResponse(RuleResponse.AlertMessage)
|
||||
ruleReport.RuleResponses = removeResponse(ruleReport.RuleResponses, index)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ruleResponse *RuleResponse) AddMessageToResponse(message string) {
|
||||
ruleResponse.AlertMessage += message
|
||||
}
|
||||
|
||||
func (ruleReport *RuleReport) IsDuplicateResponseOfResource(RuleResponse *RuleResponse, index int) (bool, *RuleResponse) {
|
||||
for i := range ruleReport.RuleResponses {
|
||||
if i != index {
|
||||
for j := range ruleReport.RuleResponses[i].AlertObject.K8SApiObjects {
|
||||
for k := range RuleResponse.AlertObject.K8SApiObjects {
|
||||
w1 := k8sinterface.NewWorkloadObj(ruleReport.RuleResponses[i].AlertObject.K8SApiObjects[j])
|
||||
w2 := k8sinterface.NewWorkloadObj(RuleResponse.AlertObject.K8SApiObjects[k])
|
||||
if w1.GetName() == w2.GetName() && w1.GetNamespace() == w2.GetNamespace() && w1.GetKind() != "Role" && w1.GetKind() != "ClusterRole" {
|
||||
return true, &ruleReport.RuleResponses[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func removeResponse(slice []RuleResponse, index int) []RuleResponse {
|
||||
return append(slice[:index], slice[index+1:]...)
|
||||
}
|
||||
|
||||
func (ruleReport *RuleReport) GetNumberOfWarningResources() int {
|
||||
sum := 0
|
||||
for i := range ruleReport.RuleResponses {
|
||||
if ruleReport.RuleResponses[i].GetSingleResultStatus() == "warning" {
|
||||
sum += 1
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (postureReport *PostureReport) RemoveData() {
|
||||
for i := range postureReport.FrameworkReports {
|
||||
postureReport.FrameworkReports[i].RemoveData()
|
||||
}
|
||||
}
|
||||
func (frameworkReport *FrameworkReport) RemoveData() {
|
||||
for i := range frameworkReport.ControlReports {
|
||||
frameworkReport.ControlReports[i].RemoveData()
|
||||
}
|
||||
}
|
||||
func (controlReport *ControlReport) RemoveData() {
|
||||
for i := range controlReport.RuleReports {
|
||||
controlReport.RuleReports[i].RemoveData()
|
||||
}
|
||||
}
|
||||
|
||||
func (ruleReport *RuleReport) RemoveData() {
|
||||
for i := range ruleReport.RuleResponses {
|
||||
ruleReport.RuleResponses[i].RemoveData()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RuleResponse) RemoveData() {
|
||||
r.AlertObject.ExternalObjects = nil
|
||||
|
||||
keepFields := []string{"kind", "apiVersion", "metadata"}
|
||||
keepMetadataFields := []string{"name", "namespace", "labels"}
|
||||
|
||||
for i := range r.AlertObject.K8SApiObjects {
|
||||
deleteFromMap(r.AlertObject.K8SApiObjects[i], keepFields)
|
||||
for k := range r.AlertObject.K8SApiObjects[i] {
|
||||
if k == "metadata" {
|
||||
if b, ok := r.AlertObject.K8SApiObjects[i][k].(map[string]interface{}); ok {
|
||||
deleteFromMap(b, keepMetadataFields)
|
||||
r.AlertObject.K8SApiObjects[i][k] = b
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deleteFromMap(m map[string]interface{}, keepFields []string) {
|
||||
for k := range m {
|
||||
if StringInSlice(keepFields, k) {
|
||||
continue
|
||||
}
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
|
||||
func StringInSlice(strSlice []string, str string) bool {
|
||||
for i := range strSlice {
|
||||
if strSlice[i] == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
47
cautils/opapolicy/gojayunmarshaller.go
Normal file
47
cautils/opapolicy/gojayunmarshaller.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package opapolicy
|
||||
|
||||
import (
|
||||
"github.com/francoispqt/gojay"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
responsible on fast unmarshaling of various COMMON containerscan structures and substructures
|
||||
|
||||
*/
|
||||
// UnmarshalJSONObject - File inside a pkg
|
||||
func (r *PostureReport) UnmarshalJSONObject(dec *gojay.Decoder, key string) (err error) {
|
||||
|
||||
switch key {
|
||||
case "customerGUID":
|
||||
err = dec.String(&(r.CustomerGUID))
|
||||
|
||||
case "clusterName":
|
||||
err = dec.String(&(r.ClusterName))
|
||||
|
||||
case "reportID":
|
||||
err = dec.String(&(r.ReportID))
|
||||
case "jobID":
|
||||
err = dec.String(&(r.JobID))
|
||||
case "generationTime":
|
||||
err = dec.Time(&(r.ReportGenerationTime), time.RFC3339)
|
||||
r.ReportGenerationTime = r.ReportGenerationTime.Local()
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
// func (files *PkgFiles) UnmarshalJSONArray(dec *gojay.Decoder) error {
|
||||
// lae := PackageFile{}
|
||||
// if err := dec.Object(&lae); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// *files = append(*files, lae)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (file *PostureReport) NKeys() int {
|
||||
return 0
|
||||
}
|
||||
//------------------------
|
||||
219
cautils/opapolicy/resources/dependencies.go
Normal file
219
cautils/opapolicy/resources/dependencies.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package resources
|
||||
|
||||
var RegoCAUtils = `
|
||||
package cautils
|
||||
|
||||
list_contains(lista,element) {
|
||||
some i
|
||||
lista[i] == element
|
||||
}
|
||||
|
||||
# getPodName(metadata) = name {
|
||||
# name := metadata.generateName
|
||||
#}
|
||||
getPodName(metadata) = name {
|
||||
name := metadata.name
|
||||
}
|
||||
|
||||
#returns subobject ,sub1 is partial to parent, e.g parent = {a:a,b:b,c:c,d:d}
|
||||
# sub1 = {b:b,c:c} - result is {b:b,c:c}, if sub1={b:b,e:f} returns {b:b}
|
||||
object_intersection(parent,sub1) = r{
|
||||
|
||||
r := {k:p | p := sub1[k]
|
||||
parent[k]== p
|
||||
}
|
||||
}
|
||||
|
||||
#returns if parent contains sub(both are objects not sets!!)
|
||||
is_subobject(sub,parent) {
|
||||
object_intersection(sub,parent) == sub
|
||||
}
|
||||
`
|
||||
|
||||
var RegoDesignators = `
|
||||
package designators
|
||||
|
||||
import data.cautils
|
||||
#functions that related to designators
|
||||
|
||||
#allowed_namespace
|
||||
#@input@: receive as part of the input object "included_namespaces" list
|
||||
#@input@: item's namespace as "namespace"
|
||||
#returns true if namespace exists in that list
|
||||
included_namespaces(namespace){
|
||||
cautils.list_contains(["default"],namespace)
|
||||
}
|
||||
|
||||
#forbidden_namespaces
|
||||
#@input@: receive as part of the input object "forbidden_namespaces" list
|
||||
#@input@: item's namespace as "namespace"
|
||||
#returns true if namespace exists in that list
|
||||
excluded_namespaces(namespace){
|
||||
not cautils.list_contains(["excluded"],namespace)
|
||||
}
|
||||
|
||||
forbidden_wlids(wlid){
|
||||
input.forbidden_wlids[_] == wlid
|
||||
}
|
||||
|
||||
filter_k8s_object(obj) = filtered {
|
||||
#put
|
||||
filtered := obj
|
||||
#filtered := [ x | cautils.list_contains(["default"],obj[i].metadata.namespace) ; x := obj[i] ]
|
||||
# filtered := [ x | not cautils.list_contains([],filter1Set[i].metadata.namespace); x := filter1Set[i]]
|
||||
|
||||
}
|
||||
`
|
||||
var RegoKubernetesApiClient = `
|
||||
package kubernetes.api.client
|
||||
|
||||
# service account token
|
||||
token := data.k8sconfig.token
|
||||
|
||||
# Cluster host
|
||||
host := data.k8sconfig.host
|
||||
|
||||
# default certificate path
|
||||
# crt_file := "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||||
crt_file := data.k8sconfig.crtfile
|
||||
|
||||
client_crt_file := data.k8sconfig.clientcrtfile
|
||||
client_key_file := data.k8sconfig.clientkeyfile
|
||||
|
||||
|
||||
# This information could be retrieved from the kubernetes API
|
||||
# too, but would essentially require a request per API group,
|
||||
# so for now use a lookup table for the most common resources.
|
||||
resource_group_mapping := {
|
||||
"services": "api/v1",
|
||||
"pods": "api/v1",
|
||||
"configmaps": "api/v1",
|
||||
"secrets": "api/v1",
|
||||
"persistentvolumeclaims": "api/v1",
|
||||
"daemonsets": "apis/apps/v1",
|
||||
"deployments": "apis/apps/v1",
|
||||
"statefulsets": "apis/apps/v1",
|
||||
"horizontalpodautoscalers": "api/autoscaling/v1",
|
||||
"jobs": "apis/batch/v1",
|
||||
"cronjobs": "apis/batch/v1beta1",
|
||||
"ingresses": "api/extensions/v1beta1",
|
||||
"replicasets": "apis/apps/v1",
|
||||
"networkpolicies": "apis/networking.k8s.io/v1",
|
||||
"clusterroles": "apis/rbac.authorization.k8s.io/v1",
|
||||
"clusterrolebindings": "apis/rbac.authorization.k8s.io/v1",
|
||||
"roles": "apis/rbac.authorization.k8s.io/v1",
|
||||
"rolebindings": "apis/rbac.authorization.k8s.io/v1",
|
||||
"serviceaccounts": "api/v1"
|
||||
}
|
||||
|
||||
# Query for given resource/name in provided namespace
|
||||
# Example: query_ns("deployments", "my-app", "default")
|
||||
query_name_ns(resource, name, namespace) = http.send({
|
||||
"url": sprintf("%v/%v/namespaces/%v/%v/%v", [
|
||||
host,
|
||||
resource_group_mapping[resource],
|
||||
namespace,
|
||||
resource,
|
||||
name,
|
||||
]),
|
||||
"method": "get",
|
||||
"headers": {"authorization": token},
|
||||
"tls_client_cert_file": client_crt_file,
|
||||
"tls_client_key_file": client_key_file,
|
||||
"tls_ca_cert_file": crt_file,
|
||||
"raise_error": true,
|
||||
})
|
||||
|
||||
# Query for given resource type using label selectors
|
||||
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api
|
||||
# Example: query_label_selector_ns("deployments", {"app": "opa-kubernetes-api-client"}, "default")
|
||||
query_label_selector_ns(resource, selector, namespace) = http.send({
|
||||
"url": sprintf("%v/%v/namespaces/%v/%v?labelSelector=%v", [
|
||||
host,
|
||||
resource_group_mapping[resource],
|
||||
namespace,
|
||||
resource,
|
||||
label_map_to_query_string(selector),
|
||||
]),
|
||||
"method": "get",
|
||||
"headers": {"authorization": token},
|
||||
"tls_client_cert_file": client_crt_file,
|
||||
"tls_client_key_file": client_key_file,
|
||||
"tls_ca_cert_file": crt_file,
|
||||
"raise_error": true,
|
||||
})
|
||||
|
||||
# x := field_transform_to_qry_param("spec.selector",input)
|
||||
# input = {"app": "acmefit", "service": "catalog-db"}
|
||||
# result: "spec.selector.app%3Dacmefit,spec.selector.service%3Dcatalog-db"
|
||||
|
||||
|
||||
query_field_selector_ns(resource, field, selector, namespace) = http.send({
|
||||
"url": sprintf("%v/%v/namespaces/%v/%v?fieldSelector=%v", [
|
||||
host,
|
||||
resource_group_mapping[resource],
|
||||
namespace,
|
||||
resource,
|
||||
field_transform_to_qry_param(field,selector),
|
||||
]),
|
||||
"method": "get",
|
||||
"headers": {"authorization": token},
|
||||
"tls_client_cert_file": client_crt_file,
|
||||
"tls_client_key_file": client_key_file,
|
||||
"tls_ca_cert_file": crt_file,
|
||||
"raise_error": true,
|
||||
|
||||
})
|
||||
|
||||
# # Query for all resources of type resource in all namespaces
|
||||
# # Example: query_all("deployments")
|
||||
# query_all(resource) = http.send({
|
||||
# "url": sprintf("https://%v:%v/%v/%v", [
|
||||
# ip,
|
||||
# port,
|
||||
# resource_group_mapping[resource],
|
||||
# resource,
|
||||
# ]),
|
||||
# "method": "get",
|
||||
# "headers": {"authorization": sprintf("Bearer %v", [token])},
|
||||
# "tls_client_cert_file": crt_file,
|
||||
# "raise_error": true,
|
||||
# })
|
||||
|
||||
# Query for all resources of type resource in all namespaces
|
||||
# Example: query_all("deployments")
|
||||
query_all(resource) = http.send({
|
||||
"url": sprintf("%v/%v/%v", [
|
||||
host,
|
||||
resource_group_mapping[resource],
|
||||
resource,
|
||||
]),
|
||||
"method": "get",
|
||||
"headers": {"authorization": token},
|
||||
"tls_client_cert_file": client_crt_file,
|
||||
"tls_client_key_file": client_key_file,
|
||||
"tls_ca_cert_file": crt_file,
|
||||
"raise_error": true,
|
||||
})
|
||||
|
||||
|
||||
|
||||
# Query for all resources of type resource in all namespaces - without authentication
|
||||
# Example: query_all("deployments")
|
||||
query_all_no_auth(resource) = http.send({
|
||||
"url": sprintf("%v/%v/namespaces/default/%v", [
|
||||
host,
|
||||
resource_group_mapping[resource],
|
||||
resource,
|
||||
]),
|
||||
"method": "get",
|
||||
"raise_error": true,
|
||||
"tls_insecure_skip_verify" : true,
|
||||
})
|
||||
|
||||
field_transform_to_qry_param(field,map) = finala {
|
||||
mid := {concat(".",[field,key]): val | val := map[key]}
|
||||
finala := label_map_to_query_string(mid)
|
||||
}
|
||||
label_map_to_query_string(map) = concat(",", [str | val := map[key]; str := concat("%3D", [key, val])])
|
||||
`
|
||||
20
cautils/opapolicy/resources/rego/modules/cronJob.reg
Executable file
20
cautils/opapolicy/resources/rego/modules/cronJob.reg
Executable file
@@ -0,0 +1,20 @@
|
||||
package armo_builtins
|
||||
|
||||
# import data.kubernetes.api.client as client
|
||||
import data.cautils as cautils
|
||||
|
||||
|
||||
# alert cronjobs
|
||||
|
||||
#handles cronjob
|
||||
deny[msga] {
|
||||
|
||||
wl := input[_]
|
||||
wl.kind == "CronJob"
|
||||
msga := {
|
||||
"alertMessage": sprintf("the following cronjobs are defined: %v", [wl]),
|
||||
"alertScore": 2,
|
||||
"packagename": "armo_builtins",
|
||||
"alertObject": wl
|
||||
}
|
||||
}
|
||||
44
cautils/opapolicy/resources/rego/modules/externalfacing.reg
Executable file
44
cautils/opapolicy/resources/rego/modules/externalfacing.reg
Executable file
@@ -0,0 +1,44 @@
|
||||
package armo_builtins
|
||||
|
||||
import data.kubernetes.api.client as client
|
||||
|
||||
|
||||
# input: pod
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns the external facing services of that pod
|
||||
#
|
||||
#
|
||||
deny[msga] {
|
||||
pod := input[_]
|
||||
podns := pod.metadata.namespace
|
||||
podname := getName(pod.metadata)
|
||||
# pod := client.query_name_ns("pods","frontend-86c5ffb485-kfp9d", "default")
|
||||
labels := pod.body.metadata.labels
|
||||
filtered_labels := json.remove(labels, ["pod-template-hash"])
|
||||
|
||||
cluster_resource := client.query_all(
|
||||
"services"
|
||||
)
|
||||
|
||||
|
||||
services := [svc | cluster_resource.body.items[i].metadata.namespace == podns; svc := cluster_resource.body.items[i]]
|
||||
service := services[_]
|
||||
np_or_lb := {"NodePort", "LoadBalancer"}
|
||||
np_or_lb[service.spec.type]
|
||||
service.spec.selector == filtered_labels
|
||||
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("pod %v/%v exposed services: %v\n", [podns,podname,service]),
|
||||
"alertScore": 7,
|
||||
"alertObject": {"service":service,"labels":filtered_labels, "podname":podname,"namespace":podns}
|
||||
}
|
||||
}
|
||||
|
||||
getName(metadata) = name {
|
||||
name := metadata.generateName
|
||||
}
|
||||
getName(metadata) = name {
|
||||
name := metadata.name
|
||||
}
|
||||
57
cautils/opapolicy/resources/rego/modules/hostpath.reg
Executable file
57
cautils/opapolicy/resources/rego/modules/hostpath.reg
Executable file
@@ -0,0 +1,57 @@
|
||||
package armo_builtins
|
||||
#import data.kubernetes.api.client as client
|
||||
import data.cautils as cautils
|
||||
|
||||
# input: pod
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns hostPath volumes
|
||||
#
|
||||
#
|
||||
deny[msga] {
|
||||
pod := input[_]
|
||||
pod.kind == "Pod"
|
||||
volumes := pod.spec.volumes
|
||||
volume := volumes[_]
|
||||
# crsrcs.body.spec.containers[_].volumeMounts[_].name = volume.name
|
||||
volume.hostPath
|
||||
podname := cautils.getPodName(pod.metadata)
|
||||
obj := {"volume":volume,"podname": podname}
|
||||
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("pod: %v has {%v,%v} ashostPath volume \n\n\n", [podname, volume]),
|
||||
"alertScore": 7,
|
||||
"alertObject": [obj]
|
||||
}
|
||||
}
|
||||
|
||||
isRWMount(mount) {
|
||||
not mount.readOnly
|
||||
}
|
||||
isRWMount(mount) {
|
||||
mount.readOnly == false
|
||||
}
|
||||
|
||||
|
||||
#handles majority of workload resources
|
||||
deny[msga] {
|
||||
|
||||
wl := input[_]
|
||||
spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"}
|
||||
spec_template_spec_patterns[wl.kind]
|
||||
volumes := wl.spec.template.spec.volumes
|
||||
volume := volumes[_]
|
||||
volume.hostPath
|
||||
wlname := cautils.getPodName(wl.metadata)
|
||||
obj := {"volume":volume,"podname": wlname}
|
||||
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("%v: %v has {%v,%v} as hostPath volume\n\n\n", [wl.kind,wlname, volume]),
|
||||
"alertScore": 7,
|
||||
"alertObject": [obj]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
56
cautils/opapolicy/resources/rego/modules/privileged.reg
Executable file
56
cautils/opapolicy/resources/rego/modules/privileged.reg
Executable file
@@ -0,0 +1,56 @@
|
||||
package armo_builtins
|
||||
|
||||
#import data.kubernetes.api.client as client
|
||||
|
||||
|
||||
# Deny mutating action unless user is in group owning the resource
|
||||
|
||||
|
||||
#privileged pods
|
||||
deny[msga] {
|
||||
|
||||
|
||||
pod := input[_]
|
||||
containers := pod.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following pods are defined as privileged: %v", [pod]),
|
||||
"alertScore": 3,
|
||||
"alertObject": pod,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#handles majority of workload resources
|
||||
deny[msga] {
|
||||
|
||||
wl := input[_]
|
||||
spec_template_spec_patterns := {"Deployment","ReplicaSet","DaemonSet","StatefulSet","Job"}
|
||||
spec_template_spec_patterns[wl.kind]
|
||||
containers := wl.spec.template.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following workloads are defined as privileged: %v", [wl]),
|
||||
"alertScore": 3,
|
||||
"alertObject": wl,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#handles cronjob
|
||||
deny[msga] {
|
||||
|
||||
wl := input[_]
|
||||
wl.kind == "CronJob"
|
||||
containers := wl.spec.jobTemplate.spec.template.spec.containers[_]
|
||||
containers.securityContext.privileged == true
|
||||
msga := {
|
||||
"packagename": "armo_builtins",
|
||||
"alertMessage": sprintf("the following cronjobs are defined as privileged: %v", [wl]),
|
||||
"alertScore": 3,
|
||||
"alertObject": wl,
|
||||
}
|
||||
}
|
||||
98
cautils/opapolicy/resources/rego/modules/rbacsecrets.reg
Executable file
98
cautils/opapolicy/resources/rego/modules/rbacsecrets.reg
Executable file
@@ -0,0 +1,98 @@
|
||||
package armo_builtins
|
||||
import data.kubernetes.api.client as client
|
||||
import data.cautils as cautils
|
||||
|
||||
|
||||
# input: None
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns roles+ related subjects in rolebinding
|
||||
|
||||
|
||||
deny[msga] {
|
||||
# rsrc := client.query_all("roles")
|
||||
# role := rsrc.body.items[_]
|
||||
role := input[_]
|
||||
role.kind == "Role"
|
||||
rule := role.rules[_]
|
||||
cautils.list_contains(rule.resources,"secrets")
|
||||
canViewSecrets(rule)
|
||||
rbsrc := client.query_all("rolebindings")
|
||||
rolebinding := rbsrc.body.items[_]
|
||||
rolebinding.roleRef.kind == "Role"
|
||||
rolebinding.roleRef.name == role.metadata.name
|
||||
|
||||
|
||||
msga := {
|
||||
"alertMessage": sprintf("the following users: %v , got read secret access roles", [rolebinding.subjects]),
|
||||
"alertScore": 9,
|
||||
"packagename": "armo_builtins",
|
||||
"alertObject": {"role":role,"users":rolebinding.subjects}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# input: None
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns clusterroles+ related subjects in rolebinding
|
||||
|
||||
|
||||
deny[msga] {
|
||||
# rsrc := client.query_all("clusterroles")
|
||||
# role := rsrc.body.items[_]
|
||||
role := input[_]
|
||||
role.kind == "ClusterRole"
|
||||
rule := role.rules[_]
|
||||
cautils.list_contains(rule.resources,"secrets")
|
||||
canViewSecrets(rule)
|
||||
rbsrc := client.query_all("rolebindings")
|
||||
rolebinding := rbsrc.body.items[_]
|
||||
rolebinding.roleRef.kind == "ClusterRole"
|
||||
rolebinding.roleRef.name == role.metadata.name
|
||||
|
||||
|
||||
msga := {
|
||||
"alertMessage": sprintf("the following users: %v , got read secret access roles", [rolebinding.subjects]),
|
||||
"alertScore": 9,
|
||||
"packagename": "armo_builtins",
|
||||
"alertObject": {"clusterrole":role,"users":rolebinding.subjects}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# input: None
|
||||
# apiversion: v1
|
||||
# does:
|
||||
# returns clusterroles+ related subjects in clusterrolebinding
|
||||
#
|
||||
#
|
||||
deny[msga] {
|
||||
# rsrc := client.query_all("clusterroles")
|
||||
# role := rsrc.body.items[_]
|
||||
role := input[_]
|
||||
role.kind == "ClusterRole"
|
||||
rule := role.rules[_]
|
||||
cautils.list_contains(rule.resources,"secrets")
|
||||
canViewSecrets(rule)
|
||||
rbsrc := client.query_all("clusterrolebindings")
|
||||
rolebinding := rbsrc.body.items[_]
|
||||
rolebinding.roleRef.kind == "ClusterRole"
|
||||
rolebinding.roleRef.name == role.metadata.name
|
||||
|
||||
|
||||
msga := {
|
||||
"alertMessage": sprintf("the following users: %v , got read secret access roles", [rolebinding.subjects]),
|
||||
"alertScore": 9,
|
||||
"packagename": "armo_builtins",
|
||||
"alertObject": {"clusterrole":role,"users":rolebinding.subjects}
|
||||
}
|
||||
}
|
||||
|
||||
canViewSecrets(rule) {
|
||||
cautils.list_contains(rule.verbs,"get")
|
||||
}
|
||||
canViewSecrets(rule) {
|
||||
cautils.list_contains(rule.verbs,"watch")
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user