Compare commits

..

55 Commits

Author SHA1 Message Date
Rotem Refael
1d3fd0dc9d Merge pull request #527 from vladklokun/add-maintenance-message 2022-06-03 19:43:27 +03:00
Vlad Klokun
8a7511cecb chore: add maintenance message 2022-06-03 19:35:33 +03:00
David Wertenteil
0af0c01ec0 Merge pull request #524 from armosec/dev
Quick fix
2022-05-26 12:21:29 +03:00
David Wertenteil
3ff2b0d6ff Merge pull request #523 from dwertent/master
Support client ID and secret key flags
2022-05-26 11:50:35 +03:00
David Wertenteil
35b2b350a0 print submit error 2022-05-26 11:42:24 +03:00
David Wertenteil
046ea1d79f support secret key and account ID from cmd 2022-05-26 11:01:26 +03:00
Rotem Refael
3081508863 Merge pull request #522 from dwertent/master
Adding http request logs
2022-05-25 17:56:50 +03:00
David Wertenteil
4a757c1bf1 adding logs 2022-05-25 17:26:12 +03:00
David Wertenteil
4f1971a63d Merge pull request #520 from armosec/dev
Extend microservice support
2022-05-24 11:07:24 +03:00
David Wertenteil
dec4bcca00 Merge pull request #521 from dwertent/master
Do not submit results every scan with Prometheus
2022-05-24 10:18:20 +03:00
David Wertenteil
95e68f49f3 do not submit results every scan with Prometheus 2022-05-24 09:33:40 +03:00
rcohencyberarmor
0c84c8f1f3 Merge pull request #518 from dwertent/master
Image vuln data integration
2022-05-23 10:51:31 +03:00
David Wertenteil
56b3239e30 loading from file fallback 2022-05-23 10:10:41 +03:00
David Wertenteil
f8e85941da update loading customer config 2022-05-23 09:47:40 +03:00
David Wertenteil
15081aa9c3 update auth url 2022-05-22 15:45:55 +03:00
David Wertenteil
ac03a2bda3 load data from config.json 2022-05-22 15:21:06 +03:00
rcohencyberarmor
b7ffa22f3a Merge pull request #517 from dwertent/master
httphandler using channel for queueing requests
2022-05-22 11:22:31 +03:00
David Wertenteil
ac5e7069da update readme 2022-05-22 10:59:48 +03:00
David Wertenteil
5a83f38bca send prometheus triggering to queue 2022-05-22 09:57:30 +03:00
David Wertenteil
3abd59e290 use channels for triggering scan 2022-05-19 14:18:11 +03:00
David Wertenteil
d08fdf2e9e update status busy to support more than one req 2022-05-19 12:01:28 +03:00
David Wertenteil
bad2f54e72 Merge pull request #515 from dwertent/master
fixed triggerd all frameworks
2022-05-18 17:29:55 +03:00
David Wertenteil
fc9b713851 fixed triggerd all frameworks 2022-05-18 17:28:46 +03:00
rcohencyberarmor
245200840d Merge pull request #514 from dwertent/master
using Buildx in githubactions
2022-05-18 16:11:47 +03:00
David Wertenteil
3f87610e8c using Buildx in githubactions 2022-05-18 16:09:29 +03:00
David Wertenteil
c285cb1bcc Merge pull request #513 from dwertent/master
REST api support
2022-05-18 15:16:00 +03:00
David Wertenteil
63968b564b update k8s-interface pkg 2022-05-18 14:36:55 +03:00
David Wertenteil
e237c48186 merged 2022-05-18 14:24:53 +03:00
David Wertenteil
622b121535 adding scan request log 2022-05-18 13:22:33 +03:00
Bezbran
20774d4a40 Merge pull request #510 from Daniel-GrunbergerCA/master
Set number of worker nodes based on scheduable nodes (based on taints) & set status to 'skipped' when there are no image vulns
2022-05-18 09:40:51 +03:00
DanielGrunbergerCA
7bb6bb85ec go mod 2022-05-18 09:35:58 +03:00
DanielGrunbergerCA
da908a84bc update k8s-iface for http handler 2022-05-18 09:35:07 +03:00
DanielGrunbergerCA
b515e259c0 Merge remote-tracking branch 'upstream/dev' 2022-05-18 09:33:38 +03:00
DanielGrunbergerCA
facd551518 update k8s-interface version 2022-05-18 09:33:13 +03:00
David Wertenteil
0fc569d9d9 fixed import 2022-05-18 00:35:45 +03:00
DanielGrunbergerCA
5d4a20f622 fix test 2022-05-17 16:01:03 +03:00
DanielGrunbergerCA
70b15a373b unit test for isEmptyImgVulns 2022-05-17 15:32:49 +03:00
DanielGrunbergerCA
01353f81b3 unit test for isMaterNodeTaints 2022-05-16 17:29:29 +03:00
DanielGrunbergerCA
22f10b6581 go mod 2022-05-16 17:02:55 +03:00
DanielGrunbergerCA
785178ffb1 show skipped for scan without imgvuln 2022-05-16 16:55:37 +03:00
DanielGrunbergerCA
f9b5c58402 pull worker nodes based on taints 2022-05-16 16:36:00 +03:00
David Wertenteil
8ed6d63ce5 Merge pull request #509 from Daniel-GrunbergerCA/fix-eks
Fix eks and support http for all endpoints
2022-05-16 15:22:49 +03:00
DanielGrunbergerCA
990a7c2052 update go mod 2022-05-16 14:28:09 +03:00
DanielGrunbergerCA
09b0c09472 support http and https for all endpoints 2022-05-16 14:13:04 +03:00
DanielGrunbergerCA
f83c38b58e update k8s-interface 2022-05-16 11:51:55 +03:00
DanielGrunbergerCA
51e600797a Merge remote-tracking branch 'upstream/dev' into fix-eks 2022-05-16 11:43:40 +03:00
Rotem Refael
afb6ea1d9c Merge pull request #507 from armosec/dev
- Adding Lens extension to readme
- Supporting --view flag
2022-05-12 10:26:14 +03:00
David Wertenteil
39d6d1fd26 Merge pull request #506 from dwertent/master
Adding `view` flag
2022-05-11 23:11:48 +03:00
DanielGrunbergerCA
6382edeb6e Merge remote-tracking branch 'upstream/dev' 2022-05-10 09:05:18 +03:00
DanielGrunbergerCA
7103c7d32c fix url 2022-05-04 11:11:29 +03:00
DanielGrunbergerCA
b4e1663cd1 make parse func 2022-05-03 16:24:19 +03:00
David Wertenteil
b3d16875d6 Merge pull request #493 from armosec/dev
Microservice support
2022-05-03 15:14:24 +03:00
DanielGrunbergerCA
60ec6e8294 support env with http 2022-05-03 12:58:33 +03:00
Rotem Refael
cf086e6614 Merge pull request #487 from armosec/dev
Remove binary from repo
2022-04-11 17:20:18 +03:00
Rotem Refael
a27d2d41f2 Merge pull request #484 from armosec/dev
CLI improvement release
2022-04-11 15:48:10 +03:00
62 changed files with 1587 additions and 397 deletions

View File

@@ -101,28 +101,28 @@ jobs:
id: image-name
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
- name: Build the Docker image
run: docker build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }}
- name: Re-Tag Image to latest
run: docker tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Quay.io
env: # Or as an environment variable
env:
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
- name: Build the Docker image
run: docker buildx build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:latest --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }} --push --platform linux/amd64,linux/arm64
# - name: Login to GitHub Container Registry
# uses: docker/login-action@v1
# with:
# registry: ghcr.io
# username: ${{ github.actor }}
# password: ${{ secrets.GITHUB_TOKEN }}
- name: Push Docker image
run: |
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:latest
# TODO - Wait for casign to support fixed tags -> https://github.com/sigstore/cosign/issues/1424
# - name: Install cosign
# uses: sigstore/cosign-installer@main

View File

@@ -80,21 +80,17 @@ jobs:
id: image-name
run: echo '::set-output name=IMAGE_NAME::quay.io/${{ github.repository_owner }}/kubescape'
- name: Build the Docker image
run: docker build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Quay.io
env:
QUAY_PASSWORD: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }}
QUAY_USERNAME: ${{ secrets.QUAYIO_REGISTRY_USERNAME }}
run: docker login -u="${QUAY_USERNAME}" -p="${QUAY_PASSWORD}" quay.io
# - name: Login to GitHub Container Registry
# uses: docker/login-action@v1
# with:
# registry: ghcr.io
# username: ${{ github.actor }}
# password: ${{ secrets.GITHUB_TOKEN }}
- name: Push Docker image
run: |
docker push ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }}
- name: Build the Docker image
run: docker buildx build . --file build/Dockerfile --tag ${{ steps.image-name.outputs.IMAGE_NAME }}:${{ steps.image-version.outputs.IMAGE_VERSION }} --build-arg image_version=${{ steps.image-version.outputs.IMAGE_VERSION }} --push --platform linux/amd64,linux/arm64

View File

@@ -4,6 +4,8 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/armosec/kubescape)](https://goreportcard.com/report/github.com/armosec/kubescape)
**WARNING: Kubescape is currently under maintenance, but well be back soon. Thank you for your patience ;)**
Kubescape is a K8s open-source tool providing a multi-cloud K8s single pane of glass, including risk analysis, security compliance, RBAC visualizer and image vulnerabilities scanning.
Kubescape scans K8s clusters, YAML files, and HELM charts, detecting misconfigurations according to multiple frameworks (such as the [NSA-CISA](https://www.armosec.io/blog/kubernetes-hardening-guidance-summary-by-armo) , [MITRE ATT&CK®](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/)), software vulnerabilities, and RBAC (role-based-access-control) violations at early stages of the CI/CD pipeline, calculates risk score instantly and shows risk trends over time.

View File

@@ -24,7 +24,9 @@ func GetDeleteCmd(ks meta.IKubescape) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
},
}
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armo.cloud/docs/authentication")
deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armo.cloud/docs/authentication")
deleteCmd.AddCommand(getExceptionsCmd(ks, &deleteInfo))

View File

@@ -26,7 +26,7 @@ func getExceptionsCmd(ks meta.IKubescape, deleteInfo *v1.Delete) *cobra.Command
if len(exceptionsNames) == 0 {
logger.L().Fatal("missing exceptions names")
}
if err := ks.DeleteExceptions(&v1.DeleteExceptions{Account: deleteInfo.Account, Exceptions: exceptionsNames}); err != nil {
if err := ks.DeleteExceptions(&v1.DeleteExceptions{Credentials: deleteInfo.Credentials, Exceptions: exceptionsNames}); err != nil {
logger.L().Fatal(err.Error())
}
},

View File

@@ -72,7 +72,10 @@ func GeDownloadCmd(ks meta.IKubescape) *cobra.Command {
return nil
},
}
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armo.cloud/docs/authentication")
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armo.cloud/docs/authentication")
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
return downloadCmd

View File

@@ -59,7 +59,9 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
return nil
},
}
listCmd.PersistentFlags().StringVar(&listPolicies.Account, "account", "", "Armo portal account ID. Default will load account ID from configMap or config file")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armo.cloud/docs/authentication")
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armo.cloud/docs/authentication")
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-printer'/'json'")
listCmd.PersistentFlags().BoolVarP(&listPolicies.ListIDs, "id", "", false, "List control ID's instead of controls names")

View File

@@ -48,8 +48,7 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd := &cobra.Command{
Use: "kubescape",
Version: cautils.BuildNumber,
Short: "Kubescape is a tool for testing Kubernetes security posture",
Long: `Based on NSA \ MITRE ATT&CK® and other frameworks specifications`,
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armo.cloud/docs",
Example: ksExamples,
}

View File

@@ -12,7 +12,6 @@ import (
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/meta"
"github.com/armosec/opa-utils/reporthandling"
"github.com/enescakir/emoji"
"github.com/spf13/cobra"
)
@@ -59,7 +58,7 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
RunE: func(cmd *cobra.Command, args []string) error {
// flagValidationControl(scanInfo)
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
scanInfo.PolicyIdentifier = []cautils.PolicyIdentifier{}
if len(args) == 0 {
scanInfo.ScanAll = true

View File

@@ -63,7 +63,9 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
},
}
scanCmd.PersistentFlags().StringVarP(&scanInfo.Account, "account", "", "", "ARMO portal account ID. Default will load account ID from configMap or config file")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armo.cloud/docs/authentication")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armo.cloud/docs/authentication")
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
@@ -91,7 +93,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
scanCmd.PersistentFlags().MarkHidden("silent") // this flag should be deprecated since we added the --logger support
// scanCmd.PersistentFlags().MarkHidden("format-version") // meant for testing different output approaches and not for common use
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valuable data from cluster nodes for certain controls. Yaml file: https://raw.githubusercontent.com/armosec/kubescape/master/hostsensorutils/hostsensor.yaml")
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valuable data from cluster nodes for certain controls. Yaml file: https://github.com/armosec/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml")
hostF.NoOptDefVal = "true"
hostF.DefValue = "false, for no TTY in stdin"

View File

@@ -21,7 +21,7 @@ func getExceptionsCmd(ks meta.IKubescape, submitInfo *metav1.Submit) *cobra.Comm
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if err := ks.SubmitExceptions(submitInfo.Account, args[0]); err != nil {
if err := ks.SubmitExceptions(&submitInfo.Credentials, args[0]); err != nil {
logger.L().Fatal(err.Error())
}
},

View File

@@ -27,7 +27,7 @@ func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
k8s := k8sinterface.NewKubernetesApi()
// get config
clusterConfig := getTenantConfig(submitInfo.Account, "", k8s)
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", k8s)
if err := clusterConfig.SetTenant(); err != nil {
logger.L().Error("failed setting account ID", helpers.Error(err))
}
@@ -60,9 +60,9 @@ func getKubernetesApi() *k8sinterface.KubernetesApi {
}
return k8sinterface.NewKubernetesApi()
}
func getTenantConfig(Account, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
func getTenantConfig(credentials *cautils.Credentials, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), Account, clusterName)
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), credentials, clusterName)
}
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), Account, clusterName)
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), credentials, clusterName)
}

View File

@@ -69,7 +69,7 @@ func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
k8s := getKubernetesApi()
// get config
clusterConfig := getTenantConfig(submitInfo.Account, "", k8s)
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", k8s)
if err := clusterConfig.SetTenant(); err != nil {
logger.L().Error("failed setting account ID", helpers.Error(err))
}

View File

@@ -20,7 +20,9 @@ func GetSubmitCmd(ks meta.IKubescape) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
},
}
submitCmd.PersistentFlags().StringVarP(&submitInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file")
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armo.cloud/docs/authentication")
submitCmd.PersistentFlags().StringVarP(&submitInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armo.cloud/docs/authentication")
submitCmd.AddCommand(getExceptionsCmd(ks, &submitInfo))
submitCmd.AddCommand(getResultsCmd(ks, &submitInfo))

View File

@@ -69,7 +69,10 @@ type ITenantConfig interface {
// getters
GetContextName() string
GetAccountID() string
GetTennatEmail() string
GetTenantEmail() string
GetToken() string
GetClientID() string
GetSecretKey() string
GetConfigObj() *ConfigObj
// GetBackendAPI() getter.IBackend
// GenerateURL()
@@ -87,8 +90,7 @@ type LocalConfig struct {
}
func NewLocalConfig(
backendAPI getter.IBackend, customerGUID, clusterName string) *LocalConfig {
var configObj *ConfigObj
backendAPI getter.IBackend, credentials *Credentials, clusterName string) *LocalConfig {
lc := &LocalConfig{
backendAPI: backendAPI,
@@ -96,20 +98,14 @@ func NewLocalConfig(
}
// get from configMap
if existsConfigFile() { // get from file
configObj, _ = loadConfigFromFile()
} else {
configObj = &ConfigObj{}
}
if configObj != nil {
lc.configObj = configObj
}
if customerGUID != "" {
lc.configObj.AccountID = customerGUID // override config customerGUID
loadConfigFromFile(lc.configObj)
}
updateCredentials(lc.configObj, credentials)
if clusterName != "" {
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
}
getAccountFromEnv(lc.configObj)
lc.backendAPI.SetAccountID(lc.configObj.AccountID)
lc.backendAPI.SetClientID(lc.configObj.ClientID)
@@ -119,9 +115,12 @@ func NewLocalConfig(
}
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
func (lc *LocalConfig) GetTennatEmail() string { return lc.configObj.CustomerAdminEMail }
func (lc *LocalConfig) GetTenantEmail() string { return lc.configObj.CustomerAdminEMail }
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
func (lc *LocalConfig) GetClientID() string { return lc.configObj.ClientID }
func (lc *LocalConfig) GetSecretKey() string { return lc.configObj.SecretKey }
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
func (lc *LocalConfig) GetToken() string { return lc.configObj.Token }
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
func (lc *LocalConfig) SetTenant() error {
@@ -190,8 +189,8 @@ type ClusterConfig struct {
configObj *ConfigObj
}
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, customerGUID, clusterName string) *ClusterConfig {
var configObj *ConfigObj
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, credentials *Credentials, clusterName string) *ClusterConfig {
// var configObj *ConfigObj
c := &ClusterConfig{
k8s: k8s,
backendAPI: backendAPI,
@@ -200,23 +199,20 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
configMapNamespace: getConfigMapNamespace(),
}
// get from configMap
// first, load from configMap
if c.existsConfigMap() {
configObj, _ = c.loadConfigFromConfigMap()
c.loadConfigFromConfigMap()
}
if configObj == nil && existsConfigFile() { // get from file
configObj, _ = loadConfigFromFile()
}
if configObj != nil {
c.configObj = configObj
}
if customerGUID != "" {
c.configObj.AccountID = customerGUID // override config customerGUID
// second, load from file
if existsConfigFile() { // get from file
loadConfigFromFile(c.configObj)
}
updateCredentials(c.configObj, credentials)
if clusterName != "" {
c.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
}
getAccountFromEnv(c.configObj)
if c.configObj.ClusterName == "" {
c.configObj.ClusterName = AdoptClusterName(k8sinterface.GetContextName())
@@ -234,7 +230,10 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
func (c *ClusterConfig) GetTennatEmail() string { return c.configObj.CustomerAdminEMail }
func (c *ClusterConfig) GetClientID() string { return c.configObj.ClientID }
func (c *ClusterConfig) GetSecretKey() string { return c.configObj.SecretKey }
func (c *ClusterConfig) GetTenantEmail() string { return c.configObj.CustomerAdminEMail }
func (c *ClusterConfig) GetToken() string { return c.configObj.Token }
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
func (c *ClusterConfig) SetTenant() error {
@@ -282,18 +281,26 @@ func (c *ClusterConfig) ToMapString() map[string]interface{} {
}
return m
}
func (c *ClusterConfig) loadConfigFromConfigMap() (*ConfigObj, error) {
func (c *ClusterConfig) loadConfigFromConfigMap() error {
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
if err != nil {
return nil, err
return err
}
if bData, err := json.Marshal(configMap.Data); err == nil {
return readConfig(bData)
}
return nil, nil
return loadConfigFromData(c.configObj, configMap.Data)
}
func loadConfigFromData(co *ConfigObj, data map[string]string) error {
var e error
if jsonConf, ok := data["config.json"]; ok {
e = readConfig([]byte(jsonConf), co)
}
if bData, err := json.Marshal(data); err == nil {
e = readConfig(bData, co)
}
return e
}
func (c *ClusterConfig) existsConfigMap() bool {
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
// TODO - check if has customerGUID
@@ -411,28 +418,27 @@ func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
}
}
}
func loadConfigFromFile() (*ConfigObj, error) {
func loadConfigFromFile(configObj *ConfigObj) error {
dat, err := os.ReadFile(ConfigFileFullPath())
if err != nil {
return nil, err
return err
}
return readConfig(dat)
return readConfig(dat, configObj)
}
func readConfig(dat []byte) (*ConfigObj, error) {
func readConfig(dat []byte, configObj *ConfigObj) error {
if len(dat) == 0 {
return nil, nil
return nil
}
configObj := &ConfigObj{}
if err := json.Unmarshal(dat, configObj); err != nil {
return nil, err
return err
}
if configObj.AccountID == "" {
configObj.AccountID = configObj.CustomerGUID
}
configObj.CustomerGUID = ""
return configObj, nil
return nil
}
// Check if the customer is submitted
@@ -479,15 +485,34 @@ func getConfigMapNamespace() string {
return "default"
}
func getAccountFromEnv(configObj *ConfigObj) {
func getAccountFromEnv(credentials *Credentials) {
// load from env
if accountID := os.Getenv("KS_ACCOUNT_ID"); accountID != "" {
configObj.AccountID = accountID
if accountID := os.Getenv("KS_ACCOUNT_ID"); credentials.Account != "" && accountID != "" {
credentials.Account = accountID
}
if clientID := os.Getenv("KS_CLIENT_ID"); clientID != "" {
configObj.ClientID = clientID
if clientID := os.Getenv("KS_CLIENT_ID"); credentials.ClientID != "" && clientID != "" {
credentials.ClientID = clientID
}
if secretKey := os.Getenv("KS_SECRET_KEY"); secretKey != "" {
configObj.SecretKey = secretKey
if secretKey := os.Getenv("KS_SECRET_KEY"); credentials.SecretKey != "" && secretKey != "" {
credentials.SecretKey = secretKey
}
}
func updateCredentials(configObj *ConfigObj, credentials *Credentials) {
if credentials == nil {
credentials = &Credentials{}
}
getAccountFromEnv(credentials)
if credentials.Account != "" {
configObj.AccountID = credentials.Account // override config Account
}
if credentials.ClientID != "" {
configObj.ClientID = credentials.ClientID // override config ClientID
}
if credentials.SecretKey != "" {
configObj.SecretKey = credentials.SecretKey // override config SecretKey
}
}

View File

@@ -0,0 +1,193 @@
package cautils
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)
func mockConfigObj() *ConfigObj {
return &ConfigObj{
AccountID: "aaa",
ClientID: "bbb",
SecretKey: "ccc",
ClusterName: "ddd",
CustomerAdminEMail: "ab@cd",
Token: "eee",
}
}
func mockLocalConfig() *LocalConfig {
return &LocalConfig{
backendAPI: nil,
configObj: mockConfigObj(),
}
}
func mockClusterConfig() *ClusterConfig {
return &ClusterConfig{
backendAPI: nil,
configObj: mockConfigObj(),
}
}
func TestConfig(t *testing.T) {
co := mockConfigObj()
cop := ConfigObj{}
assert.NoError(t, json.Unmarshal(co.Config(), &cop))
assert.Equal(t, co.AccountID, cop.AccountID)
assert.Equal(t, co.ClientID, cop.ClientID)
assert.Equal(t, co.SecretKey, cop.SecretKey)
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
assert.Equal(t, "", cop.CustomerAdminEMail) // Not copied to bytes
assert.Equal(t, "", cop.Token) // Not copied to bytes
}
func TestITenantConfig(t *testing.T) {
var lc ITenantConfig
var c ITenantConfig
lc = mockLocalConfig()
c = mockClusterConfig()
co := mockConfigObj()
// test LocalConfig methods
assert.Equal(t, co.AccountID, lc.GetAccountID())
assert.Equal(t, co.ClientID, lc.GetClientID())
assert.Equal(t, co.SecretKey, lc.GetSecretKey())
assert.Equal(t, co.ClusterName, lc.GetContextName())
assert.Equal(t, co.CustomerAdminEMail, lc.GetTenantEmail())
assert.Equal(t, co.Token, lc.GetToken())
// test ClusterConfig methods
assert.Equal(t, co.AccountID, c.GetAccountID())
assert.Equal(t, co.ClientID, c.GetClientID())
assert.Equal(t, co.SecretKey, c.GetSecretKey())
assert.Equal(t, co.ClusterName, c.GetContextName())
assert.Equal(t, co.CustomerAdminEMail, c.GetTenantEmail())
assert.Equal(t, co.Token, c.GetToken())
}
func TestUpdateConfigData(t *testing.T) {
c := mockClusterConfig()
configMap := &corev1.ConfigMap{}
c.updateConfigData(configMap)
assert.Equal(t, c.GetAccountID(), configMap.Data["accountID"])
assert.Equal(t, c.GetClientID(), configMap.Data["clientID"])
assert.Equal(t, c.GetSecretKey(), configMap.Data["secretKey"])
}
func TestReadConfig(t *testing.T) {
com := mockConfigObj()
co := &ConfigObj{}
b, e := json.Marshal(com)
assert.NoError(t, e)
readConfig(b, co)
assert.Equal(t, com.AccountID, co.AccountID)
assert.Equal(t, com.ClientID, co.ClientID)
assert.Equal(t, com.SecretKey, co.SecretKey)
assert.Equal(t, com.ClusterName, co.ClusterName)
assert.Equal(t, com.CustomerAdminEMail, co.CustomerAdminEMail)
assert.Equal(t, com.Token, co.Token)
}
func TestLoadConfigFromData(t *testing.T) {
// use case: all data is in base config
{
c := mockClusterConfig()
co := mockConfigObj()
configMap := &corev1.ConfigMap{}
c.updateConfigData(configMap)
c.configObj = &ConfigObj{}
loadConfigFromData(c.configObj, configMap.Data)
assert.Equal(t, c.GetAccountID(), co.AccountID)
assert.Equal(t, c.GetClientID(), co.ClientID)
assert.Equal(t, c.GetSecretKey(), co.SecretKey)
assert.Equal(t, c.GetContextName(), co.ClusterName)
assert.Equal(t, c.GetTenantEmail(), co.CustomerAdminEMail)
assert.Equal(t, c.GetToken(), co.Token)
}
// use case: all data is in config.json
{
c := mockClusterConfig()
co := mockConfigObj()
configMap := &corev1.ConfigMap{
Data: make(map[string]string),
}
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
c.configObj = &ConfigObj{}
loadConfigFromData(c.configObj, configMap.Data)
assert.Equal(t, c.GetAccountID(), co.AccountID)
assert.Equal(t, c.GetClientID(), co.ClientID)
assert.Equal(t, c.GetSecretKey(), co.SecretKey)
}
// use case: some data is in config.json
{
c := mockClusterConfig()
configMap := &corev1.ConfigMap{
Data: make(map[string]string),
}
// add to map
configMap.Data["clientID"] = c.configObj.ClientID
configMap.Data["secretKey"] = c.configObj.SecretKey
// delete the content
c.configObj.ClientID = ""
c.configObj.SecretKey = ""
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
loadConfigFromData(c.configObj, configMap.Data)
assert.NotEmpty(t, c.GetAccountID())
assert.NotEmpty(t, c.GetClientID())
assert.NotEmpty(t, c.GetSecretKey())
}
// use case: some data is in config.json
{
c := mockClusterConfig()
configMap := &corev1.ConfigMap{
Data: make(map[string]string),
}
c.configObj.AccountID = "tttt"
// add to map
configMap.Data["accountID"] = mockConfigObj().AccountID
configMap.Data["clientID"] = c.configObj.ClientID
configMap.Data["secretKey"] = c.configObj.SecretKey
// delete the content
c.configObj.ClientID = ""
c.configObj.SecretKey = ""
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
loadConfigFromData(c.configObj, configMap.Data)
assert.Equal(t, mockConfigObj().AccountID, c.GetAccountID())
assert.NotEmpty(t, c.GetClientID())
assert.NotEmpty(t, c.GetSecretKey())
}
}

View File

@@ -147,7 +147,8 @@ func (armoAPI *ArmoAPI) IsLoggedIn() bool { return armoAPI.loggedIn
func (armoAPI *ArmoAPI) GetClientID() string { return armoAPI.clientID }
func (armoAPI *ArmoAPI) GetSecretKey() string { return armoAPI.secretKey }
func (armoAPI *ArmoAPI) GetFrontendURL() string { return armoAPI.feURL }
func (armoAPI *ArmoAPI) GetAPIURL() string { return armoAPI.apiURL }
func (armoAPI *ArmoAPI) GetApiURL() string { return armoAPI.apiURL }
func (armoAPI *ArmoAPI) GetAuthURL() string { return armoAPI.authURL }
func (armoAPI *ArmoAPI) GetReportReceiverURL() string { return armoAPI.erURL }
func (armoAPI *ArmoAPI) SetAccountID(accountID string) { armoAPI.accountID = accountID }
func (armoAPI *ArmoAPI) SetClientID(clientID string) { armoAPI.clientID = clientID }

View File

@@ -13,8 +13,7 @@ var NativeFrameworks = []string{"nsa", "mitre", "armobest", "devopsbest"}
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.apiURL
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
u.Path = "api/v1/armoFrameworks"
q := u.Query()
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
@@ -31,8 +30,7 @@ func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
func (armoAPI *ArmoAPI) getListFrameworkURL() string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.apiURL
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
u.Path = "api/v1/armoFrameworks"
q := u.Query()
q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack())
@@ -42,8 +40,7 @@ func (armoAPI *ArmoAPI) getListFrameworkURL() string {
}
func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.apiURL
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
u.Path = "api/v1/armoPostureExceptions"
q := u.Query()
@@ -58,8 +55,7 @@ func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string {
func (armoAPI *ArmoAPI) exceptionsURL(exceptionsPolicyName string) string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.apiURL
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
u.Path = "api/v1/postureExceptionPolicy"
q := u.Query()
@@ -81,8 +77,7 @@ func (armoAPI *ArmoAPI) getAccountConfigDefault(clusterName string) string {
func (armoAPI *ArmoAPI) getAccountConfig(clusterName string) string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.apiURL
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
u.Path = "api/v1/armoCustomerConfiguration"
q := u.Query()
@@ -97,24 +92,21 @@ func (armoAPI *ArmoAPI) getAccountConfig(clusterName string) string {
func (armoAPI *ArmoAPI) getAccountURL() string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.apiURL
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
u.Path = "api/v1/createTenant"
return u.String()
}
func (armoAPI *ArmoAPI) getApiToken() string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.authURL
u.Path = "frontegg/identity/resources/auth/v1/api-token"
u.Scheme, u.Host = parseHost(armoAPI.GetAuthURL())
u.Path = "identity/resources/auth/v1/api-token"
return u.String()
}
func (armoAPI *ArmoAPI) getOpenidCustomers() string {
u := url.URL{}
u.Scheme = "https"
u.Host = armoAPI.apiURL
u.Scheme, u.Host = parseHost(armoAPI.GetApiURL())
u.Path = "api/v1/openid_customers"
return u.String()
}
@@ -173,3 +165,12 @@ func (armoAPI *ArmoAPI) getCustomerGUIDFallBack() string {
}
return "11111111-1111-1111-1111-111111111111"
}
func parseHost(host string) (string, string) {
if strings.HasPrefix(host, "http://") {
return "http", strings.Replace(host, "http://", "", 1)
}
// default scheme
return "https", strings.Replace(host, "https://", "", 1)
}

View File

@@ -8,6 +8,13 @@ type RootInfo struct {
ArmoBEURLs string // armo url
ArmoBEURLsDep string // armo url
}
type Credentials struct {
Account string
ClientID string
SecretKey string
}
// func (rootInfo *RootInfo) InitLogger() {

View File

@@ -107,7 +107,7 @@ type ScanInfo struct {
HostSensorEnabled BoolPtrFlag // Deploy ARMO K8s host scanner to collect data from certain controls
HostSensorYamlPath string // Path to hostsensor file
Local bool // Do not submit results
Account string // account ID
Credentials Credentials // account ID
KubeContext string // context name
FrameworkScan bool // false if scanning control
ScanAll bool // true if scan all frameworks

View File

@@ -8,7 +8,7 @@ import (
func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
tenant := getTenantConfig("", "", getKubernetesApi())
tenant := getTenantConfig(nil, "", getKubernetesApi())
if setConfig.Account != "" {
tenant.GetConfigObj().AccountID = setConfig.Account
@@ -25,13 +25,13 @@ func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
// View cached configurations
func (ks *Kubescape) ViewCachedConfig(viewConfig *metav1.ViewConfig) error {
tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface
tenant := getTenantConfig(nil, "", getKubernetesApi()) // change k8sinterface
fmt.Fprintf(viewConfig.Writer, "%s\n", tenant.GetConfigObj().Config())
return nil
}
func (ks *Kubescape) DeleteCachedConfig(deleteConfig *metav1.DeleteConfig) error {
tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface
tenant := getTenantConfig(nil, "", getKubernetesApi()) // change k8sinterface
return tenant.DeleteCachedConfig()
}

View File

@@ -12,7 +12,7 @@ import (
func (ks *Kubescape) DeleteExceptions(delExceptions *v1.DeleteExceptions) error {
// load cached config
getTenantConfig(delExceptions.Account, "", getKubernetesApi())
getTenantConfig(&delExceptions.Credentials, "", getKubernetesApi())
// login kubescape SaaS
armoAPI := getter.GetArmoAPIConnector()

View File

@@ -80,7 +80,7 @@ func downloadArtifacts(downloadInfo *metav1.DownloadInfo) error {
}
func downloadConfigInputs(downloadInfo *metav1.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi())
controlsInputsGetter := getConfigInputsGetter(downloadInfo.Name, tenant.GetAccountID(), nil)
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetContextName())
@@ -104,7 +104,7 @@ func downloadConfigInputs(downloadInfo *metav1.DownloadInfo) error {
func downloadExceptions(downloadInfo *metav1.DownloadInfo) error {
var err error
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi())
exceptionsGetter := getExceptionsGetter("")
exceptions := []armotypes.PostureExceptionPolicy{}
@@ -128,9 +128,9 @@ func downloadExceptions(downloadInfo *metav1.DownloadInfo) error {
func downloadFramework(downloadInfo *metav1.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi())
g := getPolicyGetter(nil, tenant.GetTennatEmail(), true, nil)
g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
if downloadInfo.Name == "" {
// if framework name not specified - download all frameworks
@@ -170,9 +170,9 @@ func downloadFramework(downloadInfo *metav1.DownloadInfo) error {
func downloadControl(downloadInfo *metav1.DownloadInfo) error {
tenant := getTenantConfig(downloadInfo.Account, "", getKubernetesApi())
tenant := getTenantConfig(&downloadInfo.Credentials, "", getKubernetesApi())
g := getPolicyGetter(nil, tenant.GetTennatEmail(), false, nil)
g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
if downloadInfo.Name == "" {
// TODO - support

View File

@@ -23,11 +23,11 @@ func getKubernetesApi() *k8sinterface.KubernetesApi {
}
return k8sinterface.NewKubernetesApi()
}
func getTenantConfig(Account, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
func getTenantConfig(credentials *cautils.Credentials, clusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), Account, clusterName)
return cautils.NewLocalConfig(getter.GetArmoAPIConnector(), credentials, clusterName)
}
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), Account, clusterName)
return cautils.NewClusterConfig(k8s, getter.GetArmoAPIConnector(), credentials, clusterName)
}
func getExceptionsGetter(useExceptions string) getter.IExceptionsGetter {

View File

@@ -44,16 +44,16 @@ func (ks *Kubescape) List(listPolicies *metav1.ListPolicies) error {
}
func listFrameworks(listPolicies *metav1.ListPolicies) ([]string, error) {
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetTennatEmail(), true, nil)
tenant := getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetTenantEmail(), true, nil)
return listFrameworksNames(g), nil
}
func listControls(listPolicies *metav1.ListPolicies) ([]string, error) {
tenant := getTenantConfig(listPolicies.Account, "", getKubernetesApi()) // change k8sinterface
tenant := getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi()) // change k8sinterface
g := getPolicyGetter(nil, tenant.GetTennatEmail(), false, nil)
g := getPolicyGetter(nil, tenant.GetTenantEmail(), false, nil)
l := getter.ListName
if listPolicies.ListIDs {
l = getter.ListID
@@ -63,7 +63,7 @@ func listControls(listPolicies *metav1.ListPolicies) ([]string, error) {
func listExceptions(listPolicies *metav1.ListPolicies) ([]string, error) {
// load tenant metav1
getTenantConfig(listPolicies.Account, "", getKubernetesApi())
getTenantConfig(&listPolicies.Credentials, "", getKubernetesApi())
var exceptionsNames []string
armoAPI := getExceptionsGetter("")

View File

@@ -43,7 +43,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
// ================== setup tenant object ======================================
tenantConfig := getTenantConfig(scanInfo.Account, scanInfo.KubeContext, k8s)
tenantConfig := getTenantConfig(&scanInfo.Credentials, scanInfo.KubeContext, k8s)
// Set submit behavior AFTER loading tenant config
setSubmitBehavior(scanInfo, tenantConfig)
@@ -124,7 +124,7 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
downloadReleasedPolicy := getter.NewDownloadReleasedPolicy() // download config inputs from github release
// set policy getter only after setting the customerGUID
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetTennatEmail(), scanInfo.FrameworkScan, downloadReleasedPolicy)
scanInfo.Getters.PolicyGetter = getPolicyGetter(scanInfo.UseFrom, interfaces.tenantConfig.GetTenantEmail(), scanInfo.FrameworkScan, downloadReleasedPolicy)
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(scanInfo.UseExceptions)

View File

@@ -29,11 +29,11 @@ func (ks *Kubescape) Submit(submitInterfaces cliinterfaces.SubmitInterfaces) err
return nil
}
func (ks *Kubescape) SubmitExceptions(accountID, excPath string) error {
func (ks *Kubescape) SubmitExceptions(credentials *cautils.Credentials, excPath string) error {
logger.L().Info("submitting exceptions", helpers.String("path", excPath))
// load cached config
tenantConfig := getTenantConfig(accountID, "", getKubernetesApi())
tenantConfig := getTenantConfig(credentials, "", getKubernetesApi())
if err := tenantConfig.SetTenant(); err != nil {
logger.L().Error("failed setting account ID", helpers.Error(err))
}

View File

@@ -1,6 +1,8 @@
package v1
import "github.com/armosec/kubescape/v2/core/cautils"
type DeleteExceptions struct {
Account string
Exceptions []string
Credentials cautils.Credentials
Exceptions []string
}

View File

@@ -1,9 +1,11 @@
package v1
import "github.com/armosec/kubescape/v2/core/cautils"
type DownloadInfo struct {
Path string // directory to save artifact. Default is "~/.kubescape/"
FileName string // can be empty
Target string // type of artifact to download
Name string // name of artifact to download
Account string // AccountID
Path string // directory to save artifact. Default is "~/.kubescape/"
FileName string // can be empty
Target string // type of artifact to download
Name string // name of artifact to download
Credentials cautils.Credentials
}

View File

@@ -1,10 +1,12 @@
package v1
import "github.com/armosec/kubescape/v2/core/cautils"
type ListPolicies struct {
Target string
ListIDs bool
Account string
Format string
Target string
ListIDs bool
Format string
Credentials cautils.Credentials
}
type ListResponse struct {

View File

@@ -1,9 +1,11 @@
package v1
import "github.com/armosec/kubescape/v2/core/cautils"
type Submit struct {
Account string
Credentials cautils.Credentials
}
type Delete struct {
Account string
Credentials cautils.Credentials
}

View File

@@ -15,8 +15,8 @@ type IKubescape interface {
Download(downloadInfo *metav1.DownloadInfo) error // TODO - return downloaded policies
// submit
Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error // TODO - func should receive object
SubmitExceptions(accountID, excPath string) error // TODO - remove
Submit(submitInterfaces cliinterfaces.SubmitInterfaces) error // TODO - func should receive object
SubmitExceptions(credentials *cautils.Credentials, excPath string) error // TODO - remove
// config
SetCachedConfig(setConfig *metav1.SetConfig) error

View File

@@ -14,7 +14,7 @@ var (
LinuxKernelVariables = "LinuxKernelVariables"
KubeletCommandLine = "KubeletCommandLine"
MapResourceToApiGroup = map[string]string{
MapHostSensorResourceToApiGroup = map[string]string{
KubeletConfiguration: "hostdata.kubescape.cloud/v1beta0",
OsReleaseFile: "hostdata.kubescape.cloud/v1beta0",
KubeletCommandLine: "hostdata.kubescape.cloud/v1beta0",
@@ -26,7 +26,7 @@ var (
)
func addInfoToMap(resource string, infoMap map[string]apis.StatusInfo, err error) {
group, version := k8sinterface.SplitApiVersion(MapResourceToApiGroup[resource])
group, version := k8sinterface.SplitApiVersion(MapHostSensorResourceToApiGroup[resource])
r := k8sinterface.JoinResourceTriplets(group, version, resource)
infoMap[r] = apis.StatusInfo{
InnerStatus: apis.StatusSkipped,

View File

@@ -51,7 +51,7 @@ func (armoCivAdaptor *ArmoCivAdaptor) GetImageVulnerability(imageID *registryvul
pageNumber := 1
request := V2ListRequest{PageSize: &pageSize, PageNum: &pageNumber, InnerFilters: filter, OrderBy: "timestamp:desc"}
requestBody, _ := json.Marshal(request)
requestUrl := fmt.Sprintf("https://%s/api/v1/vulnerability/scanResultsDetails?customerGUID=%s", armoCivAdaptor.armoAPI.GetAPIURL(), armoCivAdaptor.armoAPI.GetAccountID())
requestUrl := fmt.Sprintf("https://%s/api/v1/vulnerability/scanResultsDetails?customerGUID=%s", armoCivAdaptor.armoAPI.GetApiURL(), armoCivAdaptor.armoAPI.GetAccountID())
resp, err := armoCivAdaptor.armoAPI.Post(requestUrl, map[string]string{"Content-Type": "application/json"}, requestBody)
if err != nil {
@@ -83,8 +83,7 @@ func (armoCivAdaptor *ArmoCivAdaptor) GetImageVulnerability(imageID *registryvul
}
func (armoCivAdaptor *ArmoCivAdaptor) DescribeAdaptor() string {
// TODO
return ""
return "armo image vulnerabilities scanner, docs: https://hub.armo.cloud/docs/cluster-vulnerability-scanning"
}
func (armoCivAdaptor *ArmoCivAdaptor) GetImagesInformation(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageInformation, error) {

View File

@@ -14,7 +14,7 @@ func (armoCivAdaptor *ArmoCivAdaptor) getImageLastScanId(imageID *registryvulner
pageNumber := 1
request := V2ListRequest{PageSize: &pageSize, PageNum: &pageNumber, InnerFilters: filter, OrderBy: "timestamp:desc"}
requestBody, _ := json.Marshal(request)
requestUrl := fmt.Sprintf("https://%s/api/v1/vulnerability/scanResultsSumSummary?customerGUID=%s", armoCivAdaptor.armoAPI.GetAPIURL(), armoCivAdaptor.armoAPI.GetAccountID())
requestUrl := fmt.Sprintf("https://%s/api/v1/vulnerability/scanResultsSumSummary?customerGUID=%s", armoCivAdaptor.armoAPI.GetApiURL(), armoCivAdaptor.armoAPI.GetAccountID())
resp, err := armoCivAdaptor.armoAPI.Post(requestUrl, map[string]string{"Content-Type": "application/json"}, requestBody)
if err != nil {

View File

@@ -18,6 +18,7 @@ import (
"github.com/armosec/armoapi-go/armotypes"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8slabels "k8s.io/apimachinery/pkg/labels"
@@ -85,6 +86,11 @@ func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessio
if len(imgVulnResources) > 0 {
if err := k8sHandler.registryAdaptors.collectImagesVulnerabilities(k8sResourcesMap, allResources, armoResourceMap); err != nil {
logger.L().Warning("failed to collect image vulnerabilities", helpers.Error(err))
cautils.SetInfoMapForResources(fmt.Sprintf("failed to pull image scanning data: %s", err.Error()), imgVulnResources, sessionObj.InfoMap)
} else {
if isEmptyImgVulns(*armoResourceMap) {
cautils.SetInfoMapForResources("image scanning is not configured. for more information: https://hub.armo.cloud/docs/cluster-vulnerability-scanning", imgVulnResources, sessionObj.InfoMap)
}
}
}
@@ -103,7 +109,7 @@ func (k8sHandler *K8sResourceHandler) GetResources(sessionObj *cautils.OPASessio
sessionObj.InfoMap = infoMap
}
} else {
cautils.SetInfoMapForResources("enable-host-scan flag not used", hostResources, sessionObj.InfoMap)
cautils.SetInfoMapForResources("enable-host-scan flag not used. For more information: https://hub.armo.cloud/docs/host-sensor", hostResources, sessionObj.InfoMap)
}
}
@@ -287,17 +293,31 @@ func getCloudProviderDescription(allResources map[string]workloadinterface.IMeta
}
func (k8sHandler *K8sResourceHandler) pullWorkerNodesNumber() (int, error) {
// labels used for control plane
listOptions := metav1.ListOptions{
LabelSelector: "!node-role.kubernetes.io/control-plane,!node-role.kubernetes.io/master",
nodesList, err := k8sHandler.k8s.KubernetesClient.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
scheduableNodes := v1.NodeList{}
if nodesList != nil {
for _, node := range nodesList.Items {
if len(node.Spec.Taints) == 0 {
scheduableNodes.Items = append(scheduableNodes.Items, node)
} else {
if !isMasterNodeTaints(node.Spec.Taints) {
scheduableNodes.Items = append(scheduableNodes.Items, node)
}
}
}
}
nodesList, err := k8sHandler.k8s.KubernetesClient.CoreV1().Nodes().List(context.TODO(), listOptions)
if err != nil {
return 0, err
}
nodesNumber := 0
if nodesList != nil {
nodesNumber = len(nodesList.Items)
}
return nodesNumber, nil
return len(scheduableNodes.Items), nil
}
// NoSchedule taint with empty value is usually applied to controlplane
func isMasterNodeTaints(taints []v1.Taint) bool {
for _, taint := range taints {
if taint.Effect == v1.TaintEffectNoSchedule && taint.Value == "" {
return true
}
}
return false
}

View File

@@ -0,0 +1,532 @@
package resourcehandler
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
)
func TestIsMasterNodeTaints(t *testing.T) {
noTaintNode := `
{
"apiVersion": "v1",
"kind": "Node",
"metadata": {
"annotations": {
"kubeadm.alpha.kubernetes.io/cri-socket": "/var/run/dockershim.sock",
"node.alpha.kubernetes.io/ttl": "0",
"volumes.kubernetes.io/controller-managed-attach-detach": "true"
},
"creationTimestamp": "2022-05-16T10:52:32Z",
"labels": {
"beta.kubernetes.io/arch": "amd64",
"beta.kubernetes.io/os": "linux",
"kubernetes.io/arch": "amd64",
"kubernetes.io/hostname": "danielg-minikube",
"kubernetes.io/os": "linux",
"minikube.k8s.io/commit": "3e64b11ed75e56e4898ea85f96b2e4af0301f43d",
"minikube.k8s.io/name": "danielg-minikube",
"minikube.k8s.io/updated_at": "2022_05_16T13_52_35_0700",
"minikube.k8s.io/version": "v1.25.1",
"node-role.kubernetes.io/control-plane": "",
"node-role.kubernetes.io/master": "",
"node.kubernetes.io/exclude-from-external-load-balancers": ""
},
"name": "danielg-minikube",
"resourceVersion": "9432",
"uid": "fc4afcb6-4ca4-4038-ba54-5e16065a614a"
},
"spec": {
"podCIDR": "10.244.0.0/24",
"podCIDRs": [
"10.244.0.0/24"
]
},
"status": {
"addresses": [
{
"address": "192.168.49.2",
"type": "InternalIP"
},
{
"address": "danielg-minikube",
"type": "Hostname"
}
],
"allocatable": {
"cpu": "4",
"ephemeral-storage": "94850516Ki",
"hugepages-2Mi": "0",
"memory": "10432976Ki",
"pods": "110"
},
"capacity": {
"cpu": "4",
"ephemeral-storage": "94850516Ki",
"hugepages-2Mi": "0",
"memory": "10432976Ki",
"pods": "110"
},
"conditions": [
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has sufficient memory available",
"reason": "KubeletHasSufficientMemory",
"status": "False",
"type": "MemoryPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has no disk pressure",
"reason": "KubeletHasNoDiskPressure",
"status": "False",
"type": "DiskPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has sufficient PID available",
"reason": "KubeletHasSufficientPID",
"status": "False",
"type": "PIDPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:14:31Z",
"lastTransitionTime": "2022-05-16T10:52:45Z",
"message": "kubelet is posting ready status",
"reason": "KubeletReady",
"status": "True",
"type": "Ready"
}
],
"daemonEndpoints": {
"kubeletEndpoint": {
"Port": 10250
}
},
"images": [
{
"names": [
"requarks/wiki@sha256:dd83fff15e77843ff934b25c28c865ac000edf7653e5d11adad1dd51df87439d"
],
"sizeBytes": 441083858
},
{
"names": [
"mariadb@sha256:821d0411208eaa88f9e1f0daccd1d534f88d19baf724eb9a2777cbedb10b6c66"
],
"sizeBytes": 400782682
},
{
"names": [
"k8s.gcr.io/etcd@sha256:64b9ea357325d5db9f8a723dcf503b5a449177b17ac87d69481e126bb724c263",
"k8s.gcr.io/etcd:3.5.1-0"
],
"sizeBytes": 292558922
},
{
"names": [
"kubernetesui/dashboard@sha256:ec27f462cf1946220f5a9ace416a84a57c18f98c777876a8054405d1428cc92e",
"kubernetesui/dashboard:v2.3.1"
],
"sizeBytes": 220033604
},
{
"names": [
"k8s.gcr.io/kube-apiserver@sha256:f54681a71cce62cbc1b13ebb3dbf1d880f849112789811f98b6aebd2caa2f255",
"k8s.gcr.io/kube-apiserver:v1.23.1"
],
"sizeBytes": 135162256
},
{
"names": [
"k8s.gcr.io/kube-controller-manager@sha256:a7ed87380108a2d811f0d392a3fe87546c85bc366e0d1e024dfa74eb14468604",
"k8s.gcr.io/kube-controller-manager:v1.23.1"
],
"sizeBytes": 124971684
},
{
"names": [
"k8s.gcr.io/kube-proxy@sha256:e40f3a28721588affcf187f3f246d1e078157dabe274003eaa2957a83f7170c8",
"k8s.gcr.io/kube-proxy:v1.23.1"
],
"sizeBytes": 112327826
},
{
"names": [
"quay.io/armosec/kubescape@sha256:6196f766be50d94b45d903a911f5ee95ac99bc392a1324c3e063bec41efd98ba",
"quay.io/armosec/kubescape:v2.0.153"
],
"sizeBytes": 110345054
},
{
"names": [
"nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d"
],
"sizeBytes": 109129446
},
{
"names": [
"quay.io/armosec/action-trigger@sha256:b93707d10ff86aac8dfa42ad37192d6bcf9aceeb4321b21756e438389c26e07c",
"quay.io/armosec/action-trigger:v0.0.5"
],
"sizeBytes": 65127067
},
{
"names": [
"quay.io/armosec/images-vulnerabilities-scan@sha256:a5f9ddc04a7fdce6d52ef85a21f0de567d8e04d418c2bc5bf5d72b151c997625",
"quay.io/armosec/images-vulnerabilities-scan:v0.0.7"
],
"sizeBytes": 61446712
},
{
"names": [
"quay.io/armosec/images-vulnerabilities-scan@sha256:2f879858da89f6542e3223fb18d6d793810cc2ad6e398b66776475e4218b6af5",
"quay.io/armosec/images-vulnerabilities-scan:v0.0.8"
],
"sizeBytes": 61446528
},
{
"names": [
"quay.io/armosec/cluster-collector@sha256:2c4f733d09f7f4090ace04585230bdfacbbc29a3ade38a2e1233d2c0f730d9b6",
"quay.io/armosec/cluster-collector:v0.0.9"
],
"sizeBytes": 53699576
},
{
"names": [
"k8s.gcr.io/kube-scheduler@sha256:8be4eb1593cf9ff2d91b44596633b7815a3753696031a1eb4273d1b39427fa8c",
"k8s.gcr.io/kube-scheduler:v1.23.1"
],
"sizeBytes": 53488305
},
{
"names": [
"k8s.gcr.io/coredns/coredns@sha256:5b6ec0d6de9baaf3e92d0f66cd96a25b9edbce8716f5f15dcd1a616b3abd590e",
"k8s.gcr.io/coredns/coredns:v1.8.6"
],
"sizeBytes": 46829283
},
{
"names": [
"kubernetesui/metrics-scraper@sha256:36d5b3f60e1a144cc5ada820910535074bdf5cf73fb70d1ff1681537eef4e172",
"kubernetesui/metrics-scraper:v1.0.7"
],
"sizeBytes": 34446077
},
{
"names": [
"gcr.io/k8s-minikube/storage-provisioner@sha256:18eb69d1418e854ad5a19e399310e52808a8321e4c441c1dddad8977a0d7a944",
"gcr.io/k8s-minikube/storage-provisioner:v5"
],
"sizeBytes": 31465472
},
{
"names": [
"quay.io/armosec/notification-server@sha256:b6e9b296cd53bd3b2b42c516d8ab43db998acff1124a57aff8d66b3dd7881979",
"quay.io/armosec/notification-server:v0.0.3"
],
"sizeBytes": 20209940
},
{
"names": [
"quay.io/armosec/kube-host-sensor@sha256:82139d2561039726be060df2878ef023c59df7c536fbd7f6d766af5a99569fee",
"quay.io/armosec/kube-host-sensor:latest"
],
"sizeBytes": 11796788
},
{
"names": [
"k8s.gcr.io/pause@sha256:3d380ca8864549e74af4b29c10f9cb0956236dfb01c40ca076fb6c37253234db",
"k8s.gcr.io/pause:3.6"
],
"sizeBytes": 682696
}
],
"nodeInfo": {
"architecture": "amd64",
"bootID": "828cbe73-120b-43cf-aae0-9e2d15b8c873",
"containerRuntimeVersion": "docker://20.10.12",
"kernelVersion": "5.13.0-40-generic",
"kubeProxyVersion": "v1.23.1",
"kubeletVersion": "v1.23.1",
"machineID": "8de776e053e140d6a14c2d2def3d6bb8",
"operatingSystem": "linux",
"osImage": "Ubuntu 20.04.2 LTS",
"systemUUID": "da12dc19-10bf-4033-a440-2d9aa33d6fe3"
}
}
}
`
var l v1.Node
_ = json.Unmarshal([]byte(noTaintNode), &l)
assert.False(t, isMasterNodeTaints(l.Spec.Taints))
taintNode :=
`
{
"apiVersion": "v1",
"kind": "Node",
"metadata": {
"annotations": {
"kubeadm.alpha.kubernetes.io/cri-socket": "/var/run/dockershim.sock",
"node.alpha.kubernetes.io/ttl": "0",
"volumes.kubernetes.io/controller-managed-attach-detach": "true"
},
"creationTimestamp": "2022-05-16T10:52:32Z",
"labels": {
"beta.kubernetes.io/arch": "amd64",
"beta.kubernetes.io/os": "linux",
"kubernetes.io/arch": "amd64",
"kubernetes.io/hostname": "danielg-minikube",
"kubernetes.io/os": "linux",
"minikube.k8s.io/commit": "3e64b11ed75e56e4898ea85f96b2e4af0301f43d",
"minikube.k8s.io/name": "danielg-minikube",
"minikube.k8s.io/updated_at": "2022_05_16T13_52_35_0700",
"minikube.k8s.io/version": "v1.25.1",
"node-role.kubernetes.io/control-plane": "",
"node-role.kubernetes.io/master": "",
"node.kubernetes.io/exclude-from-external-load-balancers": ""
},
"name": "danielg-minikube",
"resourceVersion": "9871",
"uid": "fc4afcb6-4ca4-4038-ba54-5e16065a614a"
},
"spec": {
"podCIDR": "10.244.0.0/24",
"podCIDRs": [
"10.244.0.0/24"
],
"taints": [
{
"effect": "NoSchedule",
"key": "key1",
"value": ""
}
]
},
"status": {
"addresses": [
{
"address": "192.168.49.2",
"type": "InternalIP"
},
{
"address": "danielg-minikube",
"type": "Hostname"
}
],
"allocatable": {
"cpu": "4",
"ephemeral-storage": "94850516Ki",
"hugepages-2Mi": "0",
"memory": "10432976Ki",
"pods": "110"
},
"capacity": {
"cpu": "4",
"ephemeral-storage": "94850516Ki",
"hugepages-2Mi": "0",
"memory": "10432976Ki",
"pods": "110"
},
"conditions": [
{
"lastHeartbeatTime": "2022-05-16T14:24:45Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has sufficient memory available",
"reason": "KubeletHasSufficientMemory",
"status": "False",
"type": "MemoryPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:24:45Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has no disk pressure",
"reason": "KubeletHasNoDiskPressure",
"status": "False",
"type": "DiskPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:24:45Z",
"lastTransitionTime": "2022-05-16T10:52:29Z",
"message": "kubelet has sufficient PID available",
"reason": "KubeletHasSufficientPID",
"status": "False",
"type": "PIDPressure"
},
{
"lastHeartbeatTime": "2022-05-16T14:24:45Z",
"lastTransitionTime": "2022-05-16T10:52:45Z",
"message": "kubelet is posting ready status",
"reason": "KubeletReady",
"status": "True",
"type": "Ready"
}
],
"daemonEndpoints": {
"kubeletEndpoint": {
"Port": 10250
}
},
"images": [
{
"names": [
"requarks/wiki@sha256:dd83fff15e77843ff934b25c28c865ac000edf7653e5d11adad1dd51df87439d"
],
"sizeBytes": 441083858
},
{
"names": [
"mariadb@sha256:821d0411208eaa88f9e1f0daccd1d534f88d19baf724eb9a2777cbedb10b6c66"
],
"sizeBytes": 400782682
},
{
"names": [
"k8s.gcr.io/etcd@sha256:64b9ea357325d5db9f8a723dcf503b5a449177b17ac87d69481e126bb724c263",
"k8s.gcr.io/etcd:3.5.1-0"
],
"sizeBytes": 292558922
},
{
"names": [
"kubernetesui/dashboard@sha256:ec27f462cf1946220f5a9ace416a84a57c18f98c777876a8054405d1428cc92e",
"kubernetesui/dashboard:v2.3.1"
],
"sizeBytes": 220033604
},
{
"names": [
"k8s.gcr.io/kube-apiserver@sha256:f54681a71cce62cbc1b13ebb3dbf1d880f849112789811f98b6aebd2caa2f255",
"k8s.gcr.io/kube-apiserver:v1.23.1"
],
"sizeBytes": 135162256
},
{
"names": [
"k8s.gcr.io/kube-controller-manager@sha256:a7ed87380108a2d811f0d392a3fe87546c85bc366e0d1e024dfa74eb14468604",
"k8s.gcr.io/kube-controller-manager:v1.23.1"
],
"sizeBytes": 124971684
},
{
"names": [
"k8s.gcr.io/kube-proxy@sha256:e40f3a28721588affcf187f3f246d1e078157dabe274003eaa2957a83f7170c8",
"k8s.gcr.io/kube-proxy:v1.23.1"
],
"sizeBytes": 112327826
},
{
"names": [
"quay.io/armosec/kubescape@sha256:6196f766be50d94b45d903a911f5ee95ac99bc392a1324c3e063bec41efd98ba",
"quay.io/armosec/kubescape:v2.0.153"
],
"sizeBytes": 110345054
},
{
"names": [
"nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d"
],
"sizeBytes": 109129446
},
{
"names": [
"quay.io/armosec/action-trigger@sha256:b93707d10ff86aac8dfa42ad37192d6bcf9aceeb4321b21756e438389c26e07c",
"quay.io/armosec/action-trigger:v0.0.5"
],
"sizeBytes": 65127067
},
{
"names": [
"quay.io/armosec/images-vulnerabilities-scan@sha256:a5f9ddc04a7fdce6d52ef85a21f0de567d8e04d418c2bc5bf5d72b151c997625",
"quay.io/armosec/images-vulnerabilities-scan:v0.0.7"
],
"sizeBytes": 61446712
},
{
"names": [
"quay.io/armosec/images-vulnerabilities-scan@sha256:2f879858da89f6542e3223fb18d6d793810cc2ad6e398b66776475e4218b6af5",
"quay.io/armosec/images-vulnerabilities-scan:v0.0.8"
],
"sizeBytes": 61446528
},
{
"names": [
"quay.io/armosec/cluster-collector@sha256:2c4f733d09f7f4090ace04585230bdfacbbc29a3ade38a2e1233d2c0f730d9b6",
"quay.io/armosec/cluster-collector:v0.0.9"
],
"sizeBytes": 53699576
},
{
"names": [
"k8s.gcr.io/kube-scheduler@sha256:8be4eb1593cf9ff2d91b44596633b7815a3753696031a1eb4273d1b39427fa8c",
"k8s.gcr.io/kube-scheduler:v1.23.1"
],
"sizeBytes": 53488305
},
{
"names": [
"k8s.gcr.io/coredns/coredns@sha256:5b6ec0d6de9baaf3e92d0f66cd96a25b9edbce8716f5f15dcd1a616b3abd590e",
"k8s.gcr.io/coredns/coredns:v1.8.6"
],
"sizeBytes": 46829283
},
{
"names": [
"kubernetesui/metrics-scraper@sha256:36d5b3f60e1a144cc5ada820910535074bdf5cf73fb70d1ff1681537eef4e172",
"kubernetesui/metrics-scraper:v1.0.7"
],
"sizeBytes": 34446077
},
{
"names": [
"gcr.io/k8s-minikube/storage-provisioner@sha256:18eb69d1418e854ad5a19e399310e52808a8321e4c441c1dddad8977a0d7a944",
"gcr.io/k8s-minikube/storage-provisioner:v5"
],
"sizeBytes": 31465472
},
{
"names": [
"quay.io/armosec/notification-server@sha256:b6e9b296cd53bd3b2b42c516d8ab43db998acff1124a57aff8d66b3dd7881979",
"quay.io/armosec/notification-server:v0.0.3"
],
"sizeBytes": 20209940
},
{
"names": [
"quay.io/armosec/kube-host-sensor@sha256:82139d2561039726be060df2878ef023c59df7c536fbd7f6d766af5a99569fee",
"quay.io/armosec/kube-host-sensor:latest"
],
"sizeBytes": 11796788
},
{
"names": [
"k8s.gcr.io/pause@sha256:3d380ca8864549e74af4b29c10f9cb0956236dfb01c40ca076fb6c37253234db",
"k8s.gcr.io/pause:3.6"
],
"sizeBytes": 682696
}
],
"nodeInfo": {
"architecture": "amd64",
"bootID": "828cbe73-120b-43cf-aae0-9e2d15b8c873",
"containerRuntimeVersion": "docker://20.10.12",
"kernelVersion": "5.13.0-40-generic",
"kubeProxyVersion": "v1.23.1",
"kubeletVersion": "v1.23.1",
"machineID": "8de776e053e140d6a14c2d2def3d6bb8",
"operatingSystem": "linux",
"osImage": "Ubuntu 20.04.2 LTS",
"systemUUID": "da12dc19-10bf-4033-a440-2d9aa33d6fe3"
}
}
}
`
_ = json.Unmarshal([]byte(taintNode), &l)
assert.True(t, isMasterNodeTaints(l.Spec.Taints))
}

View File

@@ -4,7 +4,6 @@ import (
"strings"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/pkg/hostsensorutils"
"github.com/armosec/opa-utils/reporthandling"
"k8s.io/utils/strings/slices"
@@ -12,12 +11,43 @@ import (
)
var (
ClusterDescribe = "ClusterDescribe"
ClusterDescribe = "ClusterDescribe"
KubeletConfiguration = "KubeletConfiguration"
OsReleaseFile = "OsReleaseFile"
KernelVersion = "KernelVersion"
LinuxSecurityHardeningStatus = "LinuxSecurityHardeningStatus"
OpenPortsList = "OpenPortsList"
LinuxKernelVariables = "LinuxKernelVariables"
KubeletCommandLine = "KubeletCommandLine"
ImageVulnerabilities = "ImageVulnerabilities"
MapResourceToApiGroup = map[string]string{
KubeletConfiguration: "hostdata.kubescape.cloud/v1beta0",
OsReleaseFile: "hostdata.kubescape.cloud/v1beta0",
KubeletCommandLine: "hostdata.kubescape.cloud/v1beta0",
KernelVersion: "hostdata.kubescape.cloud/v1beta0",
LinuxSecurityHardeningStatus: "hostdata.kubescape.cloud/v1beta0",
OpenPortsList: "hostdata.kubescape.cloud/v1beta0",
LinuxKernelVariables: "hostdata.kubescape.cloud/v1beta0",
}
MapResourceToApiGroupVuln = map[string][]string{
ImageVulnerabilities: {"armo.vuln.images/v1", "image.vulnscan.com/v1"}}
MapResourceToApiGroupCloud = map[string][]string{
ClusterDescribe: {"container.googleapis.com/v1", "eks.amazonaws.com/v1", "management.azure.com/v1"}}
)
func isEmptyImgVulns(armoResourcesMap cautils.ArmoResources) bool {
imgVulnResources := cautils.MapImageVulnResources(&armoResourcesMap)
for _, resource := range imgVulnResources {
if val, ok := armoResourcesMap[resource]; ok {
if len(val) > 0 {
return false
}
}
}
return true
}
func setK8sResourceMap(frameworks []reporthandling.Framework) *cautils.K8SResources {
k8sResources := make(cautils.K8SResources)
complexMap := setComplexK8sResourceMap(frameworks)
@@ -80,10 +110,16 @@ func setComplexArmoResourceMap(frameworks []reporthandling.Framework, resourceTo
}
func mapArmoResourceToApiGroup(resource string) []string {
if val, ok := hostsensorutils.MapResourceToApiGroup[resource]; ok {
if val, ok := MapResourceToApiGroup[resource]; ok {
return []string{val}
}
return MapResourceToApiGroupCloud[resource]
if val, ok := MapResourceToApiGroupCloud[resource]; ok {
return val
}
if val, ok := MapResourceToApiGroupVuln[resource]; ok {
return val
}
return []string{}
}
func insertControls(resource string, resourceToControl map[string][]string, control reporthandling.Control) {

View File

@@ -2,7 +2,9 @@ package resourcehandler
import (
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/opa-utils/reporthandling"
"github.com/stretchr/testify/assert"
"testing"
)
@@ -24,6 +26,18 @@ func TestSetResourceMap(t *testing.T) {
}
}
func TestSsEmptyImgVulns(t *testing.T) {
armoResourcesMap := make(cautils.ArmoResources, 0)
armoResourcesMap["container.googleapis.com/v1"] = []string{"fsdfds"}
assert.Equal(t, true, isEmptyImgVulns(armoResourcesMap))
armoResourcesMap["armo.vuln.images/v1/ImageVulnerabilities"] = []string{"dada"}
assert.Equal(t, false, isEmptyImgVulns(armoResourcesMap))
armoResourcesMap["armo.vuln.images/v1/ImageVulnerabilities"] = []string{}
armoResourcesMap["bla"] = []string{"blu"}
assert.Equal(t, true, isEmptyImgVulns(armoResourcesMap))
}
func TestInsertK8sResources(t *testing.T) {
// insertK8sResources

View File

@@ -1,12 +1,13 @@
package resourcehandler
import (
"fmt"
"github.com/armosec/k8s-interface/k8sinterface"
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
armosecadaptorv1 "github.com/armosec/kubescape/v2/core/pkg/registryadaptors/armosec/v1"
"github.com/armosec/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
@@ -45,8 +46,9 @@ func (registryAdaptors *RegistryAdaptors) collectImagesVulnerabilities(k8sResour
for i := range registryAdaptors.adaptors { // login and and get vulnerabilities
if err := registryAdaptors.adaptors[i].Login(); err != nil {
logger.L().Error("failed to login", helpers.Error(err))
continue
if err != nil {
return fmt.Errorf("failed to login, adaptor: '%s', reason: '%s'", registryAdaptors.adaptors[i].DescribeAdaptor(), err.Error())
}
}
vulnerabilities, err := registryAdaptors.adaptors[i].GetImagesVulnerabilities(imagesIdentifiers)
if err != nil {

View File

@@ -12,6 +12,7 @@ import (
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
v2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
"github.com/armosec/opa-utils/reporthandling"
"github.com/google/uuid"
)
@@ -140,8 +141,8 @@ func (report *ReportEventReceiver) generateMessage() {
message := "You can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more by registering here:"
u := url.URL{}
u.Scheme = "https"
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
v2.ParseHost(&u)
if report.customerAdminEMail != "" {
logger.L().Debug("", helpers.String("account ID", report.customerGUID))

View File

@@ -5,15 +5,16 @@ import (
"github.com/armosec/k8s-interface/workloadinterface"
"github.com/armosec/kubescape/v2/core/cautils/getter"
v2 "github.com/armosec/kubescape/v2/core/pkg/resultshandling/reporter/v2"
"github.com/armosec/opa-utils/reporthandling"
"github.com/google/uuid"
)
func (report *ReportEventReceiver) initEventReceiverURL() {
urlObj := url.URL{}
urlObj.Scheme = "https"
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
v2.ParseHost(&urlObj)
urlObj.Path = "/k8s/postureReport"
q := urlObj.Query()
q.Add("customerGUID", uuid.MustParse(report.customerGUID).String())

View File

@@ -57,8 +57,7 @@ func (report *ReportEventReceiver) Submit(opaSessionObj *cautils.OPASessionObj)
if err == nil {
report.generateMessage()
} else {
err = fmt.Errorf("failed to submit scan results. url: '%s'", report.GetURL())
err = fmt.Errorf("failed to submit scan results. url: '%s', reason: %s", report.GetURL(), err.Error())
}
logger.L().Debug("", helpers.String("account ID", report.customerGUID))
@@ -89,9 +88,8 @@ func (report *ReportEventReceiver) prepareReport(opaSessionObj *cautils.OPASessi
func (report *ReportEventReceiver) GetURL() string {
u := url.URL{}
u.Scheme = "https"
u.Host = getter.GetArmoAPIConnector().GetFrontendURL()
ParseHost(&u)
q := u.Query()
if report.customerAdminEMail != "" || report.token == "" { // data has been submitted

View File

@@ -11,11 +11,9 @@ import (
func (report *ReportEventReceiver) initEventReceiverURL() {
urlObj := url.URL{}
urlObj.Scheme = "https"
urlObj.Host = getter.GetArmoAPIConnector().GetReportReceiverURL()
ParseHost(&urlObj)
urlObj.Path = "/k8s/v2/postureReport"
q := urlObj.Query()
q.Add("customerGUID", uuid.MustParse(report.customerGUID).String())
q.Add("clusterName", report.clusterName)

View File

@@ -1,6 +1,7 @@
package v2
import (
"net/url"
"strings"
)
@@ -21,3 +22,13 @@ func maskID(id string) string {
return strings.TrimSuffix(str, sep)
}
func ParseHost(urlObj *url.URL) {
if strings.Contains(urlObj.Host, "http://") {
urlObj.Scheme = "http"
urlObj.Host = strings.Replace(urlObj.Host, "http://", "", 1)
} else {
urlObj.Scheme = "https"
urlObj.Host = strings.Replace(urlObj.Host, "https://", "", 1)
}
}

View File

@@ -0,0 +1,38 @@
package v2
import (
"net/url"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseHost(t *testing.T) {
urlObj := url.URL{}
urlObj.Host = "http://localhost:7555"
ParseHost(&urlObj)
assert.Equal(t, "http", urlObj.Scheme)
assert.Equal(t, "localhost:7555", urlObj.Host)
urlObj.Host = "https://localhost:7555"
ParseHost(&urlObj)
assert.Equal(t, "https", urlObj.Scheme)
assert.Equal(t, "localhost:7555", urlObj.Host)
urlObj.Host = "http://portal-dev.armo.cloud"
ParseHost(&urlObj)
assert.Equal(t, "http", urlObj.Scheme)
assert.Equal(t, "portal-dev.armo.cloud", urlObj.Host)
urlObj.Host = "https://portal-dev.armo.cloud"
ParseHost(&urlObj)
assert.Equal(t, "https", urlObj.Scheme)
assert.Equal(t, "portal-dev.armo.cloud", urlObj.Host)
urlObj.Host = "portal-dev.armo.cloud"
ParseHost(&urlObj)
assert.Equal(t, "https", urlObj.Scheme)
assert.Equal(t, "portal-dev.armo.cloud", urlObj.Host)
}

4
go.mod
View File

@@ -5,8 +5,8 @@ go 1.17
require (
github.com/armosec/armoapi-go v0.0.73
github.com/armosec/go-git-url v0.0.4
github.com/armosec/k8s-interface v0.0.70
github.com/armosec/opa-utils v0.0.139
github.com/armosec/k8s-interface v0.0.76
github.com/armosec/opa-utils v0.0.140
github.com/armosec/rbac-utils v0.0.14
github.com/armosec/utils-go v0.0.5
github.com/armosec/utils-k8s-go v0.0.6

8
go.sum
View File

@@ -119,11 +119,11 @@ github.com/armosec/go-git-url v0.0.4 h1:emG9Yfl53rHpuX41fXLD92ehzhRoNSSnGT6Pr7og
github.com/armosec/go-git-url v0.0.4/go.mod h1:PJqdEyJyFxTQvawBcyOM0Ies6+uezire5gpwfr1XX5M=
github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
github.com/armosec/k8s-interface v0.0.37/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
github.com/armosec/k8s-interface v0.0.70 h1:NU3UIaNl7H3hsRecwggiaQbZXTwXtOKg3GOBjq6/XJw=
github.com/armosec/k8s-interface v0.0.70/go.mod h1:8NX4xWXh8mwW7QyZdZea1czNdM2azCK9BbUNmiZYXW0=
github.com/armosec/k8s-interface v0.0.76 h1:pQaF+8BcNMm6GTYTjdG7vCM1l4BIk7oALXoT6v5gCAk=
github.com/armosec/k8s-interface v0.0.76/go.mod h1:8NX4xWXh8mwW7QyZdZea1czNdM2azCK9BbUNmiZYXW0=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.139 h1:JPxgPXVJUUIujtIoZk6TejE8PkZhX2pYnpj+E8PhcfA=
github.com/armosec/opa-utils v0.0.139/go.mod h1:VnRVJgDDPFAprGDcibTtKHf9wgkoyTU8wmX2BxEIwok=
github.com/armosec/opa-utils v0.0.140 h1:iv6inb6+D0qgeVkv7f+ZIHpy239IUpAwg6Dau0JAWzg=
github.com/armosec/opa-utils v0.0.140/go.mod h1:Hwm9ZkcW87mB2567WT6mBuSBEzaKowBNfrl3Q0IVsV8=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=

View File

@@ -1,26 +1,85 @@
# Kubescape HTTP Handler Package
> This is a beta version, we might make some changes before publishing the official Prometheus support
Running `kubescape` will start up a webserver on port `8080` which will serve the following paths:
Running `kubescape` will start up a webserver on port `8080` which will serve the following API's:
### Trigger scan
* POST `/v1/scan` - Trigger a kubescape scan. The server will return an ID and will execute the scanning asynchronously
* * `wait`: scan synchronously (return results and not ID). Use only in small clusters are with an increased timeout
* * `keep`: Do not delete results from local storage after returning
* POST `/v1/scan` - trigger a kubescape scan. The server will return an ID and will execute the scanning asynchronously. the request body should look [as followed](#trigger-scan-object).
* * `wait=true`: scan synchronously (return results and not ID). Use only in small clusters or with an increased timeout. default is `wait=false`
* * `keep=true`: do not delete results from local storage after returning. default is `keep=false`
* POST `/v1/metrics` - trigger kubescape for Prometheus support. [read more](examples/prometheus/README.md)
[Response](#response-object):
```
{
"id": <str>, // scan ID
"type": "busy", // response object type
"response": <message:string> // message indicating scanning is still in process
}
```
> When scanning was triggered with the `wait=true` query param, the response is like the [`/v1/results` API](#get-results) response
### Get results
* GET `/v1/results` - Request kubescape scan results
* * query `id=<string>` -> ID returned when triggering the scan action. If empty will return latest results
* * query `keep` -> Do not delete results from local storage after returning
* GET `/v1/results` - request kubescape scan results
* * query `id=<string>` -> request results of a specific scan ID. If empty will return latest results
* * query `keep=true` -> keep the results in the local storage after returning. default is `keep=false` - the results will be deleted from local storage after they are returned
[Response](#response-object):
When scanning was done successfully
```
{
"id": <str>, // scan ID
"type": "v1results", // response object type
"response": <object:v1results> // v1 results payload
}
```
When scanning failed
```
{
"id": <str>, // scan ID
"type": "error", // response object type
"response": <error:string> // error string
}
```
When scanning is in progress
```
{
"id": <str>, // scan ID
"type": "busy", // response object type
"response": <message:string> // message indicating scanning is still in process
}
```
### Check scanning progress status
Check the scanning status - is the scanning in progress or done. This is meant for a waiting mechanize since the API does not return the entire results object when the scanning is done
* GET `/v1/status` - Request kubescape scan status
* * query `id=<string>` -> Check status of a specific scan. If empty will check if any scan is in progress
[Response](#response-object):
When scanning is in progress
```
{
"id": <str>, // scan ID
"type": "busy", // response object type
"response": <message:string> // message indicating scanning is still in process
}
```
When scanning is not in progress
```
{
"id": <str>, // scan ID
"type": "notBusy", // response object type
"response": <message:string> // message indicating scanning is done in process
}
```
### Delete cached results
* DELETE `/v1/results` - Delete kubescape scan results from storage. If empty will delete latest results
* * query `id=<string>`: Delete ID of specific results
@@ -32,10 +91,10 @@ Check the scanning status - is the scanning in progress or done. This is meant f
* `/livez` - will respond 200 is server is alive
* `/readyz` - will respond 200 if server can receive requests
## Trigger Kubescape scan
## Objects
### Trigger scan object
POST /v1/scan
body:
```
{
"format": <str>, // results format [default: json] (same as 'kubescape scan --format')
@@ -51,7 +110,8 @@ body:
}
```
Response body:
### Response object
```
{
"id": <str>, // scan ID
@@ -59,10 +119,12 @@ Response body:
"response": <object:interface> // response payload as list of bytes
}
```
#### Response object types
Response body types:
* "v1results" - v1 results object
* "id" - id string
* "busy" - server is busy processing previous requests
* "notBusy" - server is not busy processing previous requests
* "ready" - server is done processing request and results are ready
* "error" - error object
## API Examples

View File

@@ -110,13 +110,4 @@ kubescape_control_count_resources_excluded{name="<control name>",url="<docs url>
kubescape_control_count_resources_passed{name="<control name>",url="<docs url>",severity="<control severity>"} <counter>
```
#### Resources metrics
The resources metrics give you the ability to prioritize fixing the resources by the number of controls that failed
```
# Number of controls that failed for this particular resource
kubescape_resource_count_controls_failed{apiVersion="<>",kind="<>",namespace="<>",name="<>"} <counter>
# Number of controls that where excluded for this particular resource
kubescape_resource_count_controls_excluded{apiVersion="<>",kind="<>",namespace="<>",name="<>"} <counter>
```

View File

@@ -6,12 +6,13 @@ replace github.com/armosec/kubescape/v2 => ../
require (
github.com/armosec/kubescape/v2 v2.0.0-00010101000000-000000000000
github.com/armosec/opa-utils v0.0.139
github.com/armosec/opa-utils v0.0.140
github.com/armosec/utils-go v0.0.5
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/schema v1.2.0
github.com/stretchr/testify v1.7.1
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
)
require (
@@ -31,7 +32,7 @@ require (
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/armosec/armoapi-go v0.0.73 // indirect
github.com/armosec/go-git-url v0.0.4 // indirect
github.com/armosec/k8s-interface v0.0.70 // indirect
github.com/armosec/k8s-interface v0.0.76 // indirect
github.com/armosec/rbac-utils v0.0.14 // indirect
github.com/armosec/utils-k8s-go v0.0.6 // indirect
github.com/aws/aws-sdk-go v1.41.11 // indirect
@@ -129,7 +130,6 @@ require (
k8s.io/client-go v0.23.5 // indirect
k8s.io/klog/v2 v2.30.0 // indirect
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
sigs.k8s.io/controller-runtime v0.11.1 // indirect
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect

View File

@@ -113,19 +113,17 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armosec/armoapi-go v0.0.2/go.mod h1:vIK17yoKbJRQyZXWWLe3AqfqCRITxW8qmSkApyq5xFs=
github.com/armosec/armoapi-go v0.0.23/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
github.com/armosec/armoapi-go v0.0.67/go.mod h1:/9SQAgtLbYkfFneRRm/zkIn3zz+4Y2xv6N3vtFcyF8s=
github.com/armosec/armoapi-go v0.0.73 h1:LMf+eCkkf+W9NVvOzHKFgVUEpBMvh27M7//UQP3aiO8=
github.com/armosec/armoapi-go v0.0.73/go.mod h1:/9SQAgtLbYkfFneRRm/zkIn3zz+4Y2xv6N3vtFcyF8s=
github.com/armosec/go-git-url v0.0.4 h1:emG9Yfl53rHpuX41fXLD92ehzhRoNSSnGT6Pr7ogWMY=
github.com/armosec/go-git-url v0.0.4/go.mod h1:PJqdEyJyFxTQvawBcyOM0Ies6+uezire5gpwfr1XX5M=
github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
github.com/armosec/k8s-interface v0.0.37/go.mod h1:vHxGWqD/uh6+GQb9Sqv7OGMs+Rvc2dsFVc0XtgRh1ZU=
github.com/armosec/k8s-interface v0.0.70 h1:NU3UIaNl7H3hsRecwggiaQbZXTwXtOKg3GOBjq6/XJw=
github.com/armosec/k8s-interface v0.0.70/go.mod h1:8NX4xWXh8mwW7QyZdZea1czNdM2azCK9BbUNmiZYXW0=
github.com/armosec/k8s-interface v0.0.76 h1:pQaF+8BcNMm6GTYTjdG7vCM1l4BIk7oALXoT6v5gCAk=
github.com/armosec/k8s-interface v0.0.76/go.mod h1:8NX4xWXh8mwW7QyZdZea1czNdM2azCK9BbUNmiZYXW0=
github.com/armosec/opa-utils v0.0.64/go.mod h1:6tQP8UDq2EvEfSqh8vrUdr/9QVSCG4sJfju1SXQOn4c=
github.com/armosec/opa-utils v0.0.137/go.mod h1:mCFQzz4E227f7V2jQVQ9XCivkNNK3UWCTaZ0HE5rBWk=
github.com/armosec/opa-utils v0.0.139 h1:JPxgPXVJUUIujtIoZk6TejE8PkZhX2pYnpj+E8PhcfA=
github.com/armosec/opa-utils v0.0.139/go.mod h1:VnRVJgDDPFAprGDcibTtKHf9wgkoyTU8wmX2BxEIwok=
github.com/armosec/opa-utils v0.0.140 h1:iv6inb6+D0qgeVkv7f+ZIHpy239IUpAwg6Dau0JAWzg=
github.com/armosec/opa-utils v0.0.140/go.mod h1:Hwm9ZkcW87mB2567WT6mBuSBEzaKowBNfrl3Q0IVsV8=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/rbac-utils v0.0.14 h1:CKYKcgqJEXWF2Hen/B1pVGtS3nDAG1wp9dDv6oNtq90=
github.com/armosec/rbac-utils v0.0.14/go.mod h1:Ex/IdGWhGv9HZq6Hs8N/ApzCKSIvpNe/ETqDfnuyah0=

View File

@@ -5,6 +5,7 @@ import (
apisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
utilsmetav1 "github.com/armosec/opa-utils/httpserver/meta/v1"
"k8s.io/utils/strings/slices"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
@@ -16,7 +17,7 @@ func ToScanInfo(scanRequest *utilsmetav1.PostScanRequest) *cautils.ScanInfo {
setTargetInScanInfo(scanRequest, scanInfo)
if scanRequest.Account != "" {
scanInfo.Account = scanRequest.Account
scanInfo.Credentials.Account = scanRequest.Account
}
if len(scanRequest.ExcludedNamespaces) > 0 {
scanInfo.ExcludedNamespaces = strings.Join(scanRequest.ExcludedNamespaces, ",")
@@ -59,6 +60,9 @@ func ToScanInfo(scanRequest *utilsmetav1.PostScanRequest) *cautils.ScanInfo {
}
func setTargetInScanInfo(scanRequest *utilsmetav1.PostScanRequest, scanInfo *cautils.ScanInfo) {
// remove empty targets from slice
scanRequest.TargetNames = slices.Filter(nil, scanRequest.TargetNames, func(e string) bool { return e != "" })
if scanRequest.TargetType != "" && len(scanRequest.TargetNames) > 0 {
if strings.EqualFold(string(scanRequest.TargetType), string(apisv1.KindFramework)) {
scanRequest.TargetType = apisv1.KindFramework

View File

@@ -21,7 +21,7 @@ func TestToScanInfo(t *testing.T) {
TargetNames: []string{"nsa", "mitre"},
}
s := ToScanInfo(req)
assert.Equal(t, "abc", s.Account)
assert.Equal(t, "abc", s.Credentials.Account)
assert.Equal(t, "v2", s.FormatVersion)
assert.Equal(t, "pdf", s.Format)
assert.Equal(t, 2, len(s.PolicyIdentifier))
@@ -61,6 +61,28 @@ func TestToScanInfo(t *testing.T) {
}
func TestSetTargetInScanInfo(t *testing.T) {
{
req := &utilsmetav1.PostScanRequest{
TargetType: apisv1.KindFramework,
TargetNames: []string{""},
}
scanInfo := &cautils.ScanInfo{}
setTargetInScanInfo(req, scanInfo)
assert.True(t, scanInfo.FrameworkScan)
assert.True(t, scanInfo.ScanAll)
assert.Equal(t, 0, len(scanInfo.PolicyIdentifier))
}
{
req := &utilsmetav1.PostScanRequest{
TargetType: apisv1.KindFramework,
TargetNames: []string{},
}
scanInfo := &cautils.ScanInfo{}
setTargetInScanInfo(req, scanInfo)
assert.True(t, scanInfo.FrameworkScan)
assert.True(t, scanInfo.ScanAll)
assert.Equal(t, 0, len(scanInfo.PolicyIdentifier))
}
{
req := &utilsmetav1.PostScanRequest{
TargetType: apisv1.KindFramework,

View File

@@ -9,47 +9,57 @@ import (
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/core"
utilsapisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
"github.com/google/uuid"
)
// Metrics http listener for prometheus support
func (handler *HTTPHandler) Metrics(w http.ResponseWriter, r *http.Request) {
if handler.state.isBusy() { // if already scanning the cluster
message := fmt.Sprintf("scan '%s' in action", handler.state.getID())
logger.L().Info("server is busy", helpers.String("message", message), helpers.Time())
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte(message))
return
}
handler.state.setBusy()
defer handler.state.setNotBusy()
scanID := uuid.NewString()
handler.state.setID(scanID)
handler.state.setBusy(scanID)
defer handler.state.setNotBusy(scanID)
resultsFile := filepath.Join(OutputDir, scanID)
scanInfo := getPrometheusDefaultScanCommand(scanID, resultsFile)
// trigger scanning
logger.L().Info(handler.state.getID(), helpers.String("action", "triggering scan"), helpers.Time())
ks := core.NewKubescape()
results, err := ks.Scan(getPrometheusDefaultScanCommand(scanID, resultsFile))
if err != nil {
scanParams := &scanRequestParams{
scanQueryParams: &ScanQueryParams{
ReturnResults: true,
KeepResults: false,
},
scanInfo: scanInfo,
scanID: scanID,
}
handler.scanResponseChan.set(scanID) // add scan to channel
defer handler.scanResponseChan.delete(scanID)
// send to scan queue
logger.L().Info("requesting scan", helpers.String("scanID", scanID), helpers.String("api", "v1/metrics"))
handler.scanRequestChan <- scanParams
// wait for scan to complete
results := <-handler.scanResponseChan.get(scanID)
defer removeResultsFile(scanID) // remove json format results file
defer os.Remove(resultsFile) // remove prometheus format results file
// handle response
if results.Type == utilsapisv1.ErrorScanResponseType {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("failed to complete scan. reason: %s", err.Error())))
w.Write(responseToBytes(results))
return
}
results.HandleResults()
logger.L().Info(handler.state.getID(), helpers.String("action", "done scanning"), helpers.Time())
// read prometheus format results file
f, err := os.ReadFile(resultsFile)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("failed read results from file. reason: %s", err.Error())))
results.Type = utilsapisv1.ErrorScanResponseType
results.Response = fmt.Sprintf("failed read results from file. reason: %s", err.Error())
w.Write(responseToBytes(results))
return
}
os.Remove(resultsFile)
w.WriteHeader(http.StatusOK)
w.Write(f)
@@ -57,6 +67,8 @@ func (handler *HTTPHandler) Metrics(w http.ResponseWriter, r *http.Request) {
func getPrometheusDefaultScanCommand(scanID, resultsFile string) *cautils.ScanInfo {
scanInfo := defaultScanInfo()
scanInfo.Submit = false // do not submit results every scan
scanInfo.Local = true // do not submit results every scan
scanInfo.FrameworkScan = true
scanInfo.ScanAll = true // scan all frameworks
scanInfo.ScanID = scanID // scan ID

View File

@@ -0,0 +1,110 @@
package v1
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sync"
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
utilsmetav1 "github.com/armosec/opa-utils/httpserver/meta/v1"
"github.com/gorilla/schema"
)
type scanResponseChan struct {
scanResponseChan map[string]chan *utilsmetav1.Response
mtx sync.RWMutex
}
// get response object chan
func (resChan *scanResponseChan) get(key string) chan *utilsmetav1.Response {
resChan.mtx.RLock()
defer resChan.mtx.RUnlock()
return resChan.scanResponseChan[key]
}
// set chan for response object
func (resChan *scanResponseChan) set(key string) {
resChan.mtx.Lock()
defer resChan.mtx.Unlock()
resChan.scanResponseChan[key] = make(chan *utilsmetav1.Response)
}
// push response object to chan
func (resChan *scanResponseChan) push(key string, resp *utilsmetav1.Response) {
resChan.mtx.Lock()
defer resChan.mtx.Unlock()
if _, ok := resChan.scanResponseChan[key]; ok {
resChan.scanResponseChan[key] <- resp
}
}
// delete channel
func (resChan *scanResponseChan) delete(key string) {
resChan.mtx.Lock()
defer resChan.mtx.Unlock()
delete(resChan.scanResponseChan, key)
}
func newScanResponseChan() *scanResponseChan {
return &scanResponseChan{
scanResponseChan: make(map[string]chan *utilsmetav1.Response),
mtx: sync.RWMutex{},
}
}
type ScanQueryParams struct {
ReturnResults bool `schema:"wait"` // wait for scanning to complete (synchronized request)
KeepResults bool `schema:"keep"` // do not delete results after returning (relevant only for synchronized requests)
}
type ResultsQueryParams struct {
ScanID string `schema:"id"`
KeepResults bool `schema:"keep"` // do not delete results after returning (default will delete results)
AllResults bool `schema:"all"` // delete all results
}
type StatusQueryParams struct {
ScanID string `schema:"id"`
}
// scanRequestParams params passed to channel
type scanRequestParams struct {
scanInfo *cautils.ScanInfo // request as received from api
scanQueryParams *ScanQueryParams // request as received from api
scanID string // generated scan ID
}
func getScanParamsFromRequest(r *http.Request, scanID string) (*scanRequestParams, error) {
defer r.Body.Close()
scanRequestParams := &scanRequestParams{}
scanQueryParams := &ScanQueryParams{}
if err := schema.NewDecoder().Decode(scanQueryParams, r.URL.Query()); err != nil {
return scanRequestParams, fmt.Errorf("failed to parse query params, reason: %s", err.Error())
}
readBuffer, err := ioutil.ReadAll(r.Body)
if err != nil {
// handler.writeError(w, fmt.Errorf("failed to read request body, reason: %s", err.Error()), scanID)
return scanRequestParams, fmt.Errorf("failed to read request body, reason: %s", err.Error())
}
logger.L().Info("REST API received scan request", helpers.String("body", string(readBuffer)))
scanRequest := &utilsmetav1.PostScanRequest{}
if err := json.Unmarshal(readBuffer, &scanRequest); err != nil {
return scanRequestParams, fmt.Errorf("failed to parse request payload, reason: %s", err.Error())
}
scanInfo := getScanCommand(scanRequest, scanID)
scanRequestParams.scanID = scanID
scanRequestParams.scanQueryParams = scanQueryParams
scanRequestParams.scanInfo = scanInfo
return scanRequestParams, nil
}

View File

@@ -0,0 +1,76 @@
package v1
import (
"bytes"
"encoding/json"
"net/http"
"net/url"
"testing"
utilsmetav1 "github.com/armosec/opa-utils/httpserver/meta/v1"
"github.com/armosec/utils-go/boolutils"
"github.com/stretchr/testify/assert"
)
func TestGetScanParamsFromRequest(t *testing.T) {
{
body := utilsmetav1.PostScanRequest{
Submit: boolutils.BoolPointer(true),
HostScanner: boolutils.BoolPointer(true),
Account: "aaaaaaaaaa",
}
jsonBytes, err := json.Marshal(body)
assert.NoError(t, err)
u := url.URL{
Scheme: "http",
Host: "bla",
Path: "bla",
RawQuery: "wait=true&keep=true",
}
request, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(jsonBytes))
assert.NoError(t, err)
scanID := "ccccccc"
req, err := getScanParamsFromRequest(request, scanID)
assert.NoError(t, err)
assert.Equal(t, scanID, req.scanID)
assert.True(t, req.scanQueryParams.KeepResults)
assert.True(t, req.scanQueryParams.ReturnResults)
assert.True(t, req.scanInfo.HostSensorEnabled.GetBool())
assert.True(t, req.scanInfo.Submit)
assert.Equal(t, "aaaaaaaaaa", req.scanInfo.Credentials.Account)
}
{
body := utilsmetav1.PostScanRequest{
Submit: boolutils.BoolPointer(false),
HostScanner: boolutils.BoolPointer(false),
Account: "aaaaaaaaaa",
}
jsonBytes, err := json.Marshal(body)
assert.NoError(t, err)
u := url.URL{
Scheme: "http",
Host: "bla",
Path: "bla",
}
request, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(jsonBytes))
assert.NoError(t, err)
scanID := "ccccccc"
req, err := getScanParamsFromRequest(request, scanID)
assert.NoError(t, err)
assert.Equal(t, scanID, req.scanID)
assert.False(t, req.scanQueryParams.KeepResults)
assert.False(t, req.scanQueryParams.ReturnResults)
assert.False(t, req.scanInfo.HostSensorEnabled.GetBool())
assert.False(t, req.scanInfo.Submit)
assert.Equal(t, "aaaaaaaaaa", req.scanInfo.Credentials.Account)
}
}

View File

@@ -1,11 +1,8 @@
package v1
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sync"
utilsapisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
utilsmetav1 "github.com/armosec/opa-utils/httpserver/meta/v1"
@@ -19,35 +16,27 @@ import (
var OutputDir = "./results"
var FailedOutputDir = "./failed"
type ScanQueryParams struct {
ReturnResults bool `schema:"wait"` // wait for scanning to complete (synchronized request)
KeepResults bool `schema:"keep"` // do not delete results after returning (relevant only for synchronized requests)
}
type ResultsQueryParams struct {
ScanID string `schema:"id"`
KeepResults bool `schema:"keep"` // do not delete results after returning (default will delete results)
AllResults bool `schema:"all"` // delete all results
}
type StatusQueryParams struct {
ScanID string `schema:"id"`
}
type HTTPHandler struct {
state *serverState
state *serverState
scanResponseChan *scanResponseChan
scanRequestChan chan *scanRequestParams
}
func NewHTTPHandler() *HTTPHandler {
return &HTTPHandler{
state: newServerState(),
handler := &HTTPHandler{
state: newServerState(),
scanRequestChan: make(chan *scanRequestParams),
scanResponseChan: newScanResponseChan(),
}
go handler.executeScan()
return handler
}
// ============================================== STATUS ========================================================
// Status API
func (handler *HTTPHandler) Status(w http.ResponseWriter, r *http.Request) {
defer handler.recover(w)
defer handler.recover(w, "")
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
@@ -59,117 +48,88 @@ func (handler *HTTPHandler) Status(w http.ResponseWriter, r *http.Request) {
statusQueryParams := &StatusQueryParams{}
if err := schema.NewDecoder().Decode(statusQueryParams, r.URL.Query()); err != nil {
handler.writeError(w, fmt.Errorf("failed to parse query params, reason: %s", err.Error()))
w.WriteHeader(http.StatusInternalServerError)
handler.writeError(w, fmt.Errorf("failed to parse query params, reason: %s", err.Error()), "")
return
}
logger.L().Info("requesting status", helpers.String("scanID", statusQueryParams.ScanID), helpers.String("api", "v1/status"))
if !handler.state.isBusy() {
w.WriteHeader(http.StatusOK)
if !handler.state.isBusy(statusQueryParams.ScanID) {
response.Type = utilsapisv1.NotBusyScanResponseType
logger.L().Debug("status: not busy", helpers.String("ID", statusQueryParams.ScanID))
w.Write(responseToBytes(&response))
return
}
currentScanID := handler.state.getID()
if statusQueryParams.ScanID != "" && currentScanID != statusQueryParams.ScanID {
response.Type = utilsapisv1.NotBusyScanResponseType
w.Write(responseToBytes(&response))
return
if statusQueryParams.ScanID == "" {
statusQueryParams.ScanID = handler.state.getLatestID()
}
response.Response = currentScanID
response.ID = currentScanID
response.Response = statusQueryParams.ScanID
response.ID = statusQueryParams.ScanID
response.Type = utilsapisv1.BusyScanResponseType
logger.L().Debug("status: busy", helpers.String("ID", statusQueryParams.ScanID))
w.Write(responseToBytes(&response))
}
// ============================================== SCAN ========================================================
// Scan API - TODO: break down to functions
// Scan API
func (handler *HTTPHandler) Scan(w http.ResponseWriter, r *http.Request) {
defer handler.recover(w)
// generate id
scanID := uuid.NewString()
defer r.Body.Close()
defer handler.recover(w, scanID)
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
response := utilsmetav1.Response{}
w.Header().Set("Content-Type", "application/json")
scanQueryParams := &ScanQueryParams{}
if err := schema.NewDecoder().Decode(scanQueryParams, r.URL.Query()); err != nil {
handler.writeError(w, fmt.Errorf("failed to parse query params, reason: %s", err.Error()))
return
}
if handler.state.isBusy() {
// TODO - Add to queue
w.WriteHeader(http.StatusOK)
response.Response = handler.state.getID()
response.ID = handler.state.getID()
response.Type = utilsapisv1.IDScanResponseType
w.Write(responseToBytes(&response))
return
}
handler.state.setBusy()
// generate id
scanID := uuid.NewString()
handler.state.setID(scanID)
response.ID = scanID
response.Type = utilsapisv1.IDScanResponseType
readBuffer, err := ioutil.ReadAll(r.Body)
scanRequestParams, err := getScanParamsFromRequest(r, scanID)
if err != nil {
handler.writeError(w, fmt.Errorf("failed to read request body, reason: %s", err.Error()))
return
}
scanRequest := utilsmetav1.PostScanRequest{}
if err := json.Unmarshal(readBuffer, &scanRequest); err != nil {
handler.writeError(w, fmt.Errorf("failed to parse request payload, reason: %s", err.Error()))
handler.writeError(w, err, "")
return
}
var wg sync.WaitGroup
if scanQueryParams.ReturnResults {
wg.Add(1)
} else {
wg.Add(0)
}
statusCode := http.StatusOK
handler.state.setBusy(scanID)
response := &utilsmetav1.Response{}
response.ID = scanID
response.Type = utilsapisv1.BusyScanResponseType
response.Response = fmt.Sprintf("scanning '%s' is in progress", scanID)
handler.scanResponseChan.set(scanID) // add channel
defer handler.scanResponseChan.delete(scanID)
// you must use a goroutine since the executeScan function is not always listening to the channel
go func() {
// execute scan in the background
// send to scanning handler
logger.L().Info("requesting scan", helpers.String("scanID", scanID), helpers.String("api", "v1/scan"))
handler.scanRequestChan <- scanRequestParams
}()
logger.L().Info("scan triggered", helpers.String("ID", scanID))
if scanRequestParams.scanQueryParams.ReturnResults {
// wait for scan to complete
response = <-handler.scanResponseChan.get(scanID)
results, err := scan(&scanRequest, scanID)
if err != nil {
logger.L().Error("scanning failed", helpers.String("ID", scanID), helpers.Error(err))
if scanQueryParams.ReturnResults {
response.Type = utilsapisv1.ErrorScanResponseType
response.Response = err.Error()
statusCode = http.StatusInternalServerError
}
} else {
logger.L().Success("done scanning", helpers.String("ID", scanID))
if scanQueryParams.ReturnResults {
response.Type = utilsapisv1.ResultsV1ScanResponseType
response.Response = results
wg.Done()
}
}
if scanQueryParams.ReturnResults && !scanQueryParams.KeepResults {
if scanRequestParams.scanQueryParams.KeepResults {
// delete results after returning
logger.L().Debug("deleting results", helpers.String("ID", scanID))
removeResultsFile(scanID)
}
handler.state.setNotBusy()
}()
}
wg.Wait()
statusCode := http.StatusOK
if response.Type == utilsapisv1.ErrorScanResponseType {
statusCode = http.StatusInternalServerError
}
w.WriteHeader(statusCode)
w.Write(responseToBytes(&response))
w.Write(responseToBytes(response))
}
// ============================================== RESULTS ========================================================
@@ -179,15 +139,16 @@ func (handler *HTTPHandler) Results(w http.ResponseWriter, r *http.Request) {
response := utilsmetav1.Response{}
w.Header().Set("Content-Type", "application/json")
defer handler.recover(w)
defer handler.recover(w, "")
defer r.Body.Close()
resultsQueryParams := &ResultsQueryParams{}
if err := schema.NewDecoder().Decode(resultsQueryParams, r.URL.Query()); err != nil {
handler.writeError(w, fmt.Errorf("failed to parse query params, reason: %s", err.Error()))
handler.writeError(w, fmt.Errorf("failed to parse query params, reason: %s", err.Error()), "")
return
}
logger.L().Info("requesting results", helpers.String("scanID", resultsQueryParams.ScanID), helpers.String("api", "v1/results"), helpers.String("method", r.Method))
if resultsQueryParams.ScanID == "" {
resultsQueryParams.ScanID = handler.state.getLatestID()
@@ -195,22 +156,22 @@ func (handler *HTTPHandler) Results(w http.ResponseWriter, r *http.Request) {
if resultsQueryParams.ScanID == "" { // if no scan found
logger.L().Info("empty scan ID")
w.WriteHeader(http.StatusBadRequest) // Should we return ok?
response.Response = "latest scan not found. trigger again"
w.WriteHeader(http.StatusBadRequest)
response.Response = "latest scan not found"
response.Type = utilsapisv1.ErrorScanResponseType
w.Write(responseToBytes(&response))
return
}
response.ID = resultsQueryParams.ScanID
if handler.state.isBusy() { // if requested ID is still scanning
if resultsQueryParams.ScanID == handler.state.getID() {
logger.L().Info("scan in process", helpers.String("ID", resultsQueryParams.ScanID))
w.WriteHeader(http.StatusOK)
response.Response = "scanning in progress"
w.Write(responseToBytes(&response))
return
}
if handler.state.isBusy(resultsQueryParams.ScanID) { // if requested ID is still scanning
logger.L().Info("scan in process", helpers.String("ID", resultsQueryParams.ScanID))
w.WriteHeader(http.StatusOK)
response.Type = utilsapisv1.BusyScanResponseType
response.Response = fmt.Sprintf("scanning '%s' in progress", resultsQueryParams.ScanID)
w.Write(responseToBytes(&response))
return
}
switch r.Method {
@@ -256,15 +217,10 @@ func (handler *HTTPHandler) Ready(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func responseToBytes(res *utilsmetav1.Response) []byte {
b, _ := json.Marshal(res)
return b
}
func (handler *HTTPHandler) recover(w http.ResponseWriter) {
func (handler *HTTPHandler) recover(w http.ResponseWriter, scanID string) {
response := utilsmetav1.Response{}
if err := recover(); err != nil {
handler.state.setNotBusy()
handler.state.setNotBusy(scanID)
logger.L().Error("recover", helpers.Error(fmt.Errorf("%v", err)))
w.WriteHeader(http.StatusInternalServerError)
response.Response = fmt.Sprintf("%v", err)
@@ -273,11 +229,11 @@ func (handler *HTTPHandler) recover(w http.ResponseWriter) {
}
}
func (handler *HTTPHandler) writeError(w http.ResponseWriter, err error) {
func (handler *HTTPHandler) writeError(w http.ResponseWriter, err error, scanID string) {
response := utilsmetav1.Response{}
w.WriteHeader(http.StatusBadRequest)
response.Response = err.Error()
response.Type = utilsapisv1.ErrorScanResponseType
w.Write(responseToBytes(&response))
handler.state.setNotBusy()
handler.state.setNotBusy(scanID)
}

View File

@@ -0,0 +1,32 @@
package v1
// ============================================== STATUS ========================================================
// Status API
// func TestStatus(t *testing.T) {
// {
// httpHandler := NewHTTPHandler()
// u := url.URL{
// Scheme: "http",
// Host: "bla",
// Path: "bla",
// RawQuery: "wait=true&keep=true",
// }
// request, err := http.NewRequest(http.MethodPost, u.String(), nil)
// httpHandler.Status(nil, request)
// assert.NoError(t, err)
// scanID := "ccccccc"
// req, err := getScanParamsFromRequest(request, scanID)
// assert.NoError(t, err)
// assert.Equal(t, scanID, req.scanID)
// assert.True(t, req.scanQueryParams.KeepResults)
// assert.True(t, req.scanQueryParams.ReturnResults)
// assert.True(t, *req.scanRequest.HostScanner)
// assert.True(t, *req.scanRequest.Submit)
// assert.Equal(t, "aaaaaaaaaa", req.scanRequest.Account)
// }
// }

View File

@@ -11,7 +11,7 @@ import (
func TestDefaultScanInfo(t *testing.T) {
s := defaultScanInfo()
assert.Equal(t, "", s.Account)
assert.Equal(t, "", s.Credentials.Account)
assert.Equal(t, "v2", s.FormatVersion)
assert.Equal(t, "json", s.Format)
assert.False(t, s.HostSensorEnabled.GetBool())
@@ -24,7 +24,7 @@ func TestGetScanCommand(t *testing.T) {
TargetType: apisv1.KindFramework,
}
s := getScanCommand(&req, "abc")
assert.Equal(t, "", s.Account)
assert.Equal(t, "", s.Credentials.Account)
assert.Equal(t, "abc", s.ScanID)
assert.Equal(t, "v2", s.FormatVersion)
assert.Equal(t, "json", s.Format)

View File

@@ -9,14 +9,48 @@ import (
"github.com/armosec/kubescape/v2/core/cautils"
"github.com/armosec/kubescape/v2/core/cautils/getter"
"github.com/armosec/kubescape/v2/core/cautils/logger"
"github.com/armosec/kubescape/v2/core/cautils/logger/helpers"
"github.com/armosec/kubescape/v2/core/core"
utilsapisv1 "github.com/armosec/opa-utils/httpserver/apis/v1"
utilsmetav1 "github.com/armosec/opa-utils/httpserver/meta/v1"
reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2"
"github.com/armosec/utils-go/boolutils"
)
func scan(scanRequest *utilsmetav1.PostScanRequest, scanID string) (*reporthandlingv2.PostureReport, error) {
scanInfo := getScanCommand(scanRequest, scanID)
// executeScan execute the scan request passed in the channel
func (handler *HTTPHandler) executeScan() {
for {
scanReq := <-handler.scanRequestChan
logger.L().Info("triggering scan", helpers.String("scanID", scanReq.scanID))
response := &utilsmetav1.Response{}
logger.L().Info("scan triggered", helpers.String("ID", scanReq.scanID))
results, err := scan(scanReq.scanInfo, scanReq.scanID)
if err != nil {
logger.L().Error("scanning failed", helpers.String("ID", scanReq.scanID), helpers.Error(err))
if scanReq.scanQueryParams.ReturnResults {
response.Type = utilsapisv1.ErrorScanResponseType
response.Response = err.Error()
}
} else {
logger.L().Success("done scanning", helpers.String("ID", scanReq.scanID))
if scanReq.scanQueryParams.ReturnResults {
response.Type = utilsapisv1.ResultsV1ScanResponseType
response.Response = results
}
}
handler.state.setNotBusy(scanReq.scanID)
// return results
handler.scanResponseChan.push(scanReq.scanID, response)
}
}
func scan(scanInfo *cautils.ScanInfo, scanID string) (*reporthandlingv2.PostureReport, error) {
ks := core.NewKubescape()
result, err := ks.Scan(scanInfo)
@@ -103,15 +137,15 @@ func getScanCommand(scanRequest *utilsmetav1.PostScanRequest, scanID string) *ca
func defaultScanInfo() *cautils.ScanInfo {
scanInfo := &cautils.ScanInfo{}
scanInfo.FailThreshold = 100
scanInfo.Account = envToString("KS_ACCOUNT", "") // publish results to Kubescape SaaS
scanInfo.ExcludedNamespaces = envToString("KS_EXCLUDE_NAMESPACES", "") // namespace to exclude
scanInfo.HostSensorYamlPath = envToString("KS_HOST_SCAN_YAML", "") // namespace to exclude
scanInfo.IncludeNamespaces = envToString("KS_INCLUDE_NAMESPACES", "") // namespace to include
scanInfo.Credentials.Account = envToString("KS_ACCOUNT", "") // publish results to Kubescape SaaS
scanInfo.ExcludedNamespaces = envToString("KS_EXCLUDE_NAMESPACES", "") // namespaces to exclude
scanInfo.IncludeNamespaces = envToString("KS_INCLUDE_NAMESPACES", "") // namespaces to include
scanInfo.HostSensorYamlPath = envToString("KS_HOST_SCAN_YAML", "") // path to host scan YAML
scanInfo.FormatVersion = envToString("KS_FORMAT_VERSION", "v2") // output format version
scanInfo.Format = envToString("KS_FORMAT", "json") // default output should be json
scanInfo.Submit = envToBool("KS_SUBMIT", false) // publish results to Kubescape SaaS
scanInfo.HostSensorEnabled.SetBool(envToBool("KS_ENABLE_HOST_SCANNER", false)) // enable host scanner
scanInfo.Local = envToBool("KS_KEEP_LOCAL", false) // do not publish results to Kubescape SaaS
scanInfo.HostSensorEnabled.SetBool(envToBool("KS_ENABLE_HOST_SCANNER", false)) // enable host scanner
if !envToBool("KS_DOWNLOAD_ARTIFACTS", false) {
scanInfo.UseArtifactsFrom = getter.DefaultLocalStore // Load files from cache (this will prevent kubescape fom downloading the artifacts every time)
}
@@ -136,7 +170,7 @@ func writeScanErrorToFile(err error, scanID string) error {
if e := os.MkdirAll(filepath.Dir(FailedOutputDir), os.ModePerm); e != nil {
return fmt.Errorf("failed to scan. reason: '%s'. failed to save error in file - failed to create directory. reason: %s", err.Error(), e.Error())
}
f, e := os.Create(filepath.Join(FailedOutputDir, scanID))
f, e := os.Create(filepath.Join(filepath.Dir(FailedOutputDir), scanID))
if e != nil {
return fmt.Errorf("failed to scan. reason: '%s'. failed to save error in file - failed to open file for writing. reason: %s", err.Error(), e.Error())
}
@@ -147,3 +181,9 @@ func writeScanErrorToFile(err error, scanID string) error {
}
return fmt.Errorf("failed to scan. reason: '%s'", err.Error())
}
// responseToBytes convert response object to bytes
func responseToBytes(res *utilsmetav1.Response) []byte {
b, _ := json.Marshal(res)
return b
}

View File

@@ -3,44 +3,32 @@ package v1
import "sync"
type serverState struct {
// response string
busy bool
id string
statusID map[string]bool
latestID string
mtx sync.RWMutex
}
func (s *serverState) isBusy() bool {
// isBusy is server busy with ID, if id is empty will check for latest ID
func (s *serverState) isBusy(id string) bool {
s.mtx.RLock()
busy := s.busy
if id == "" {
id = s.latestID
}
busy := s.statusID[id]
s.mtx.RUnlock()
return busy
}
func (s *serverState) setBusy() {
func (s *serverState) setBusy(id string) {
s.mtx.Lock()
s.busy = true
s.statusID[id] = true
s.latestID = id
s.mtx.Unlock()
}
func (s *serverState) setNotBusy() {
func (s *serverState) setNotBusy(id string) {
s.mtx.Lock()
s.busy = false
s.latestID = s.id
s.id = ""
s.mtx.Unlock()
}
func (s *serverState) getID() string {
s.mtx.RLock()
id := s.id
s.mtx.RUnlock()
return id
}
func (s *serverState) setID(id string) {
s.mtx.Lock()
s.id = id
delete(s.statusID, id)
s.mtx.Unlock()
}
@@ -51,9 +39,16 @@ func (s *serverState) getLatestID() string {
return id
}
func (s *serverState) len() int {
s.mtx.RLock()
l := len(s.statusID)
s.mtx.RUnlock()
return l
}
func newServerState() *serverState {
return &serverState{
busy: false,
mtx: sync.RWMutex{},
statusID: make(map[string]bool),
mtx: sync.RWMutex{},
}
}