mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
Compare commits
16 Commits
github-act
...
v2.9.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
076aa7f8fe | ||
|
|
df035ea5fc | ||
|
|
58553688e9 | ||
|
|
776173653d | ||
|
|
26c47d501c | ||
|
|
6a8a338945 | ||
|
|
53f23b663b | ||
|
|
592e0e2b43 | ||
|
|
92449bf564 | ||
|
|
8d1547163b | ||
|
|
d16abf376d | ||
|
|
150967eae8 | ||
|
|
150dc61ec7 | ||
|
|
7b46cdd480 | ||
|
|
b67fd95e31 | ||
|
|
b88e4f6169 |
11
.github/dependabot.yaml
vendored
Normal file
11
.github/dependabot.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -292,6 +292,7 @@ jobs:
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # ratchet:actions/checkout@v3
|
||||
with:
|
||||
repository: armosec/system-tests
|
||||
ref: remove-urls
|
||||
path: .
|
||||
|
||||
- uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # ratchet:actions/setup-python@v4
|
||||
|
||||
@@ -23,14 +23,8 @@ var (
|
||||
# Set account id
|
||||
%[1]s config set accountID <account id>
|
||||
|
||||
# Set client id
|
||||
%[1]s config set clientID <client id>
|
||||
|
||||
# Set access key
|
||||
%[1]s config set secretKey <access key>
|
||||
|
||||
# Set cloudAPIURL
|
||||
%[1]s config set cloudAPIURL <cloud API URL>
|
||||
# Set cloud report URL
|
||||
%[1]s config set cloudReportURL <cloud Report URL>
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
|
||||
@@ -34,12 +34,8 @@ func getSetCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
var supportConfigSet = map[string]func(*metav1.SetConfig, string){
|
||||
"accountID": func(s *metav1.SetConfig, account string) { s.Account = account },
|
||||
"clientID": func(s *metav1.SetConfig, clientID string) { s.ClientID = clientID },
|
||||
"secretKey": func(s *metav1.SetConfig, secretKey string) { s.SecretKey = secretKey },
|
||||
"cloudAPIURL": func(s *metav1.SetConfig, cloudAPIURL string) { s.CloudAPIURL = cloudAPIURL },
|
||||
"cloudAuthURL": func(s *metav1.SetConfig, cloudAuthURL string) { s.CloudAuthURL = cloudAuthURL },
|
||||
"cloudReportURL": func(s *metav1.SetConfig, cloudReportURL string) { s.CloudReportURL = cloudReportURL },
|
||||
"cloudUIURL": func(s *metav1.SetConfig, cloudUIURL string) { s.CloudUIURL = cloudUIURL },
|
||||
}
|
||||
|
||||
func stringKeysToSlice(m map[string]func(*metav1.SetConfig, string)) []string {
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package delete
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var deleteExceptionsExamples = fmt.Sprintf(`
|
||||
# Delete single exception
|
||||
%[1]s delete exceptions "exception name"
|
||||
|
||||
# Delete multiple exceptions
|
||||
%[1]s delete exceptions "first exception;second exception;third exception"
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetDeleteCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var deleteInfo v1.Delete
|
||||
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete <command>",
|
||||
Short: "Delete configurations in Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
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.armosec.io/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.armosec.io/docs/authentication")
|
||||
|
||||
deleteCmd.AddCommand(getExceptionsCmd(ks, &deleteInfo))
|
||||
|
||||
return deleteCmd
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package delete
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func getExceptionsCmd(ks meta.IKubescape, deleteInfo *v1.Delete) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "exceptions <exception name>",
|
||||
Short: fmt.Sprintf("Delete exceptions from Kubescape SaaS version. Run '%[1]s list exceptions' for all exceptions names", cautils.ExecName()),
|
||||
Example: deleteExceptionsExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("missing exceptions names")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
if err := flagValidationDelete(deleteInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
exceptionsNames := strings.Split(args[0], ";")
|
||||
if len(exceptionsNames) == 0 {
|
||||
logger.L().Fatal("missing exceptions names")
|
||||
}
|
||||
if err := ks.DeleteExceptions(&v1.DeleteExceptions{Credentials: deleteInfo.Credentials, Exceptions: exceptionsNames}); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the flag entered are valid
|
||||
func flagValidationDelete(deleteInfo *v1.Delete) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return deleteInfo.Credentials.Validate()
|
||||
}
|
||||
@@ -83,9 +83,9 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
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.armosec.io/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.armosec.io/docs/authentication")
|
||||
downloadCmd.PersistentFlags().StringVarP(&downloadInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
downloadCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
downloadCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If not specified, will save in `~/.kubescape/<policy name>.json`")
|
||||
|
||||
return downloadCmd
|
||||
@@ -95,5 +95,5 @@ func GetDownloadCmd(ks meta.IKubescape) *cobra.Command {
|
||||
func flagValidationDownload(downloadInfo *v1.DownloadInfo) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return downloadInfo.Credentials.Validate()
|
||||
return cautils.ValidateAccountID(downloadInfo.AccountID)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func GetFixCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
fixCmd := &cobra.Command{
|
||||
Use: "fix <report output file>",
|
||||
Short: "Fix misconfiguration in files",
|
||||
Short: "Propose a fix for the misconfiguration found when scanning Kubernetes manifest files",
|
||||
Long: ``,
|
||||
Example: fixCmdExamples,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -63,7 +63,7 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
listCmd.PersistentFlags().StringVarP(&listPolicies.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
listCmd.PersistentFlags().StringVar(&listPolicies.Format, "format", "pretty-print", "output format. supported: 'pretty-print'/'json'")
|
||||
listCmd.PersistentFlags().MarkDeprecated("id", "Control ID's are included in list outputs")
|
||||
|
||||
@@ -74,5 +74,5 @@ func GetListCmd(ks meta.IKubescape) *cobra.Command {
|
||||
func flagValidationList(listPolicies *v1.ListPolicies) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return listPolicies.Credentials.Validate()
|
||||
return cautils.ValidateAccountID(listPolicies.AccountID)
|
||||
}
|
||||
|
||||
42
cmd/root.go
42
cmd/root.go
@@ -6,14 +6,13 @@ import (
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/cmd/completion"
|
||||
"github.com/kubescape/kubescape/v2/cmd/config"
|
||||
"github.com/kubescape/kubescape/v2/cmd/delete"
|
||||
"github.com/kubescape/kubescape/v2/cmd/download"
|
||||
"github.com/kubescape/kubescape/v2/cmd/fix"
|
||||
"github.com/kubescape/kubescape/v2/cmd/list"
|
||||
"github.com/kubescape/kubescape/v2/cmd/scan"
|
||||
"github.com/kubescape/kubescape/v2/cmd/submit"
|
||||
"github.com/kubescape/kubescape/v2/cmd/update"
|
||||
"github.com/kubescape/kubescape/v2/cmd/version"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
@@ -27,11 +26,11 @@ import (
|
||||
var rootInfo cautils.RootInfo
|
||||
|
||||
var ksExamples = fmt.Sprintf(`
|
||||
# Scan command
|
||||
# Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations
|
||||
%[1]s scan
|
||||
|
||||
# List supported frameworks
|
||||
%[1]s list frameworks
|
||||
# List supported controls
|
||||
%[1]s list controls
|
||||
|
||||
# Download artifacts (air-gapped environment support)
|
||||
%[1]s download artifacts
|
||||
@@ -51,6 +50,13 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
Use: "kubescape",
|
||||
Short: "Kubescape is a tool for testing Kubernetes security posture. Docs: https://hub.armosec.io/docs",
|
||||
Example: ksExamples,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
k8sinterface.SetClusterContextName(rootInfo.KubeContext)
|
||||
initLogger()
|
||||
initLoggerLevel()
|
||||
initEnvironment()
|
||||
initCacheDir()
|
||||
},
|
||||
}
|
||||
|
||||
if cautils.IsKrewPlugin() {
|
||||
@@ -63,9 +69,10 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
rootCmd.SetUsageTemplate(newUsageTemplate)
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLsDep, "environment", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.KSCloudBEURLs, "env", "", envFlagUsage)
|
||||
rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead")
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.DiscoveryServerURL, "server", "api.armosec.io", "Backend discovery server URL") // TODO: remove default value
|
||||
|
||||
rootCmd.PersistentFlags().MarkDeprecated("environment", "'environment' is no longer supported, Use 'server' instead. Feel free to contact the Kubescape maintainers for more information.")
|
||||
rootCmd.PersistentFlags().MarkDeprecated("env", "'env' is no longer supported, Use 'server' instead. Feel free to contact the Kubescape maintainers for more information.")
|
||||
rootCmd.PersistentFlags().MarkHidden("environment")
|
||||
rootCmd.PersistentFlags().MarkHidden("env")
|
||||
|
||||
@@ -74,23 +81,30 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/")))
|
||||
rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]")
|
||||
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable Color output for logging")
|
||||
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable Color output for logging")
|
||||
|
||||
cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir)
|
||||
rootCmd.PersistentFlags().BoolVarP(&rootInfo.DisableColor, "disable-color", "", false, "Disable color output for logging")
|
||||
rootCmd.PersistentFlags().BoolVarP(&rootInfo.EnableColor, "enable-color", "", false, "Force enable color output for logging")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&rootInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
|
||||
// Supported commands
|
||||
rootCmd.AddCommand(scan.GetScanCommand(ks))
|
||||
rootCmd.AddCommand(download.GetDownloadCmd(ks))
|
||||
rootCmd.AddCommand(delete.GetDeleteCmd(ks))
|
||||
rootCmd.AddCommand(list.GetListCmd(ks))
|
||||
rootCmd.AddCommand(submit.GetSubmitCmd(ks))
|
||||
rootCmd.AddCommand(completion.GetCompletionCmd())
|
||||
rootCmd.AddCommand(version.GetVersionCmd())
|
||||
rootCmd.AddCommand(config.GetConfigCmd(ks))
|
||||
rootCmd.AddCommand(update.GetUpdateCmd())
|
||||
rootCmd.AddCommand(fix.GetFixCmd(ks))
|
||||
|
||||
// deprecated commands
|
||||
rootCmd.AddCommand(&cobra.Command{
|
||||
Use: "submit",
|
||||
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
|
||||
})
|
||||
rootCmd.AddCommand(&cobra.Command{
|
||||
Use: "delete",
|
||||
Deprecated: "This command is deprecated. Contact Kubescape maintainers for more information.",
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,19 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
v1 "github.com/kubescape/backend/pkg/client/v1"
|
||||
"github.com/kubescape/backend/pkg/servicediscovery"
|
||||
sdClientV1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/go-logger/iconlogger"
|
||||
"github.com/kubescape/go-logger/zaplogger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
const envFlagUsage = "Send report results to specific URL. Format:<ReportReceiver>,<Backend>,<Frontend>.\n\t\tExample:report.armo.cloud,api.armo.cloud,portal.armo.cloud"
|
||||
|
||||
func initLogger() {
|
||||
logger.DisableColor(rootInfo.DisableColor)
|
||||
logger.EnableColor(rootInfo.EnableColor)
|
||||
@@ -23,9 +27,9 @@ func initLogger() {
|
||||
rootInfo.LoggerName = l
|
||||
} else {
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
rootInfo.LoggerName = "pretty"
|
||||
rootInfo.LoggerName = iconlogger.LoggerName
|
||||
} else {
|
||||
rootInfo.LoggerName = "zap"
|
||||
rootInfo.LoggerName = zaplogger.LoggerName
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,40 +60,50 @@ func initCacheDir() {
|
||||
logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore))
|
||||
}
|
||||
func initEnvironment() {
|
||||
if rootInfo.KSCloudBEURLs == "" {
|
||||
rootInfo.KSCloudBEURLs = rootInfo.KSCloudBEURLsDep
|
||||
if rootInfo.DiscoveryServerURL == "" {
|
||||
return
|
||||
}
|
||||
urlSlices := strings.Split(rootInfo.KSCloudBEURLs, ",")
|
||||
if len(urlSlices) != 1 && len(urlSlices) < 3 {
|
||||
logger.L().Fatal("expected at least 3 URLs (report, api, frontend, auth)")
|
||||
}
|
||||
switch len(urlSlices) {
|
||||
case 1:
|
||||
switch urlSlices[0] {
|
||||
case "dev", "development":
|
||||
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIDev())
|
||||
case "stage", "staging":
|
||||
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIStaging())
|
||||
case "":
|
||||
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPIProd())
|
||||
default:
|
||||
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
|
||||
}
|
||||
case 2:
|
||||
logger.L().Fatal("--environment flag usage: " + envFlagUsage)
|
||||
case 3, 4:
|
||||
var ksAuthURL string
|
||||
ksEventReceiverURL := urlSlices[0] // mandatory
|
||||
ksBackendURL := urlSlices[1] // mandatory
|
||||
ksFrontendURL := urlSlices[2] // mandatory
|
||||
if len(urlSlices) >= 4 {
|
||||
ksAuthURL = urlSlices[3]
|
||||
}
|
||||
|
||||
getter.SetKSCloudAPIConnector(getter.NewKSCloudAPICustomized(
|
||||
ksBackendURL, ksAuthURL,
|
||||
getter.WithReportURL(ksEventReceiverURL),
|
||||
getter.WithFrontendURL(ksFrontendURL),
|
||||
))
|
||||
logger.L().Debug("fetching URLs from service discovery server", helpers.String("server", rootInfo.DiscoveryServerURL))
|
||||
|
||||
client, err := sdClientV1.NewServiceDiscoveryClientV1(rootInfo.DiscoveryServerURL)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to create service discovery client", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
|
||||
return
|
||||
}
|
||||
|
||||
services, err := servicediscovery.GetServices(
|
||||
client,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to to get services from server", helpers.Error(err), helpers.String("server", rootInfo.DiscoveryServerURL))
|
||||
return
|
||||
}
|
||||
|
||||
logger.L().Debug("configuring service discovery URLs", helpers.String("cloudAPIURL", services.GetApiServerUrl()), helpers.String("cloudReportURL", services.GetReportReceiverHttpUrl()))
|
||||
|
||||
tenant := cautils.GetTenantConfig("", "", "", nil)
|
||||
if services.GetApiServerUrl() != "" {
|
||||
tenant.GetConfigObj().CloudAPIURL = services.GetApiServerUrl()
|
||||
}
|
||||
if services.GetReportReceiverHttpUrl() != "" {
|
||||
tenant.GetConfigObj().CloudReportURL = services.GetReportReceiverHttpUrl()
|
||||
}
|
||||
|
||||
if err = tenant.UpdateCachedConfig(); err != nil {
|
||||
logger.L().Error("failed to update cached config", helpers.Error(err))
|
||||
}
|
||||
|
||||
ksCloud, err := v1.NewKSCloudAPI(
|
||||
services.GetApiServerUrl(),
|
||||
services.GetReportReceiverHttpUrl(),
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
getter.SetKSCloudAPIConnector(ksCloud)
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
|
||||
}
|
||||
|
||||
scanInfo.FrameworkScan = false
|
||||
scanInfo.SetScanType(cautils.ScanTypeControl)
|
||||
|
||||
if err := validateControlScanInfo(scanInfo); err != nil {
|
||||
return err
|
||||
|
||||
@@ -42,7 +42,11 @@ var (
|
||||
Run '%[1]s list frameworks' for the list of supported frameworks
|
||||
`, cautils.ExecName())
|
||||
|
||||
ErrUnknownSeverity = errors.New("unknown severity")
|
||||
ErrUnknownSeverity = errors.New("unknown severity")
|
||||
ErrSecurityViewNotSupported = errors.New("security view is not supported for framework scan")
|
||||
ErrBadThreshold = errors.New("bad argument: out of range threshold")
|
||||
ErrKeepLocalOrSubmit = errors.New("you can use `keep-local` or `submit`, but not both")
|
||||
ErrOmitRawResourcesOrSubmit = errors.New("you can use `omit-raw-resources` or `submit`, but not both")
|
||||
)
|
||||
|
||||
func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
|
||||
@@ -208,17 +212,21 @@ func validateSeverity(severity string) error {
|
||||
|
||||
// validateFrameworkScanInfo validates the scan info struct for the `scan framework` command
|
||||
func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
|
||||
if scanInfo.View == string(cautils.SecurityViewType) {
|
||||
return ErrSecurityViewNotSupported
|
||||
}
|
||||
|
||||
if scanInfo.Submit && scanInfo.Local {
|
||||
return fmt.Errorf("you can use `keep-local` or `submit`, but not both")
|
||||
return ErrKeepLocalOrSubmit
|
||||
}
|
||||
if 100 < scanInfo.ComplianceThreshold || 0 > scanInfo.ComplianceThreshold {
|
||||
return fmt.Errorf("bad argument: out of range threshold")
|
||||
return ErrBadThreshold
|
||||
}
|
||||
if 100 < scanInfo.FailThreshold || 0 > scanInfo.FailThreshold {
|
||||
return fmt.Errorf("bad argument: out of range threshold")
|
||||
return ErrBadThreshold
|
||||
}
|
||||
if scanInfo.Submit && scanInfo.OmitRawResources {
|
||||
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
|
||||
return ErrOmitRawResourcesOrSubmit
|
||||
}
|
||||
severity := scanInfo.FailThresholdSeverity
|
||||
if err := validateSeverity(severity); severity != "" && err != nil {
|
||||
@@ -226,5 +234,5 @@ func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
|
||||
}
|
||||
|
||||
// Validate the user's credentials
|
||||
return scanInfo.Credentials.Validate()
|
||||
return cautils.ValidateAccountID(scanInfo.AccountID)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/iconlogger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/core"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
@@ -23,7 +22,7 @@ type imageScanInfo struct {
|
||||
// TODO(vladklokun): document image scanning on the Kubescape Docs Hub?
|
||||
var (
|
||||
imageExample = fmt.Sprintf(`
|
||||
This command is still in BETA. Feel free to contact the kubescape maintainers for more information.
|
||||
This command is still in BETA. Feel free to contact the Kubescape maintainers for more information.
|
||||
|
||||
Scan an image for vulnerabilities.
|
||||
|
||||
@@ -55,8 +54,6 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *im
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
logger.InitLogger(iconlogger.LoggerName)
|
||||
|
||||
dbCfg, _ := imagescan.NewDefaultDBConfig()
|
||||
svc := imagescan.NewScanService(dbCfg)
|
||||
|
||||
@@ -77,9 +74,9 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *im
|
||||
|
||||
scanInfo.SetScanType(cautils.ScanTypeImage)
|
||||
|
||||
outputPrinters := core.GetOutputPrinters(scanInfo, ctx)
|
||||
outputPrinters := core.GetOutputPrinters(scanInfo, ctx, "")
|
||||
|
||||
uiPrinter := core.GetUIPrinter(ctx, scanInfo)
|
||||
uiPrinter := core.GetUIPrinter(ctx, scanInfo, "")
|
||||
|
||||
resultsHandler := resultshandling.NewResultsHandler(nil, outputPrinters, uiPrinter)
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
@@ -17,14 +16,14 @@ import (
|
||||
var scanCmdExamples = fmt.Sprintf(`
|
||||
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
|
||||
|
||||
# Scan current cluster with all frameworks
|
||||
# Scan current cluster
|
||||
%[1]s scan
|
||||
|
||||
# Scan kubernetes YAML manifest files
|
||||
# Scan kubernetes manifest files
|
||||
%[1]s scan .
|
||||
|
||||
# Scan and save the results in the JSON format
|
||||
%[1]s scan --format json --output results.json --format-version=v2
|
||||
%[1]s scan --format json --output results.json
|
||||
|
||||
# Display all resources
|
||||
%[1]s scan --verbose
|
||||
@@ -39,18 +38,9 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
// scanCmd represents the scan command
|
||||
scanCmd := &cobra.Command{
|
||||
Use: "scan",
|
||||
Short: "Scan the current running cluster or yaml files",
|
||||
Short: "Scan a Kubernetes cluster or YAML files for image vulnerabilities and misconfigurations",
|
||||
Long: `The action you want to perform`,
|
||||
Example: scanCmdExamples,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
// setting input patterns for framework scan is only relevancy for non-security view
|
||||
if len(args) > 0 && scanInfo.View != string(cautils.SecurityViewType) {
|
||||
if args[0] != "framework" && args[0] != "control" {
|
||||
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if scanInfo.View == string(cautils.SecurityViewType) {
|
||||
setSecurityViewScanInfo(args, &scanInfo)
|
||||
@@ -58,23 +48,17 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
return securityScan(scanInfo, ks)
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, []string{strings.Join(getter.NativeFrameworks, ",")})
|
||||
if len(args) == 0 || (args[0] != "framework" && args[0] != "control") {
|
||||
return getFrameworkCmd(ks, &scanInfo).RunE(cmd, append([]string{strings.Join(getter.NativeFrameworks, ",")}, args...))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
k8sinterface.SetClusterContextName(scanInfo.KubeContext)
|
||||
|
||||
},
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
// TODO - revert context
|
||||
},
|
||||
}
|
||||
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.Account, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
scanCmd.PersistentFlags().BoolVar(&scanInfo.CreateAccount, "create-account", false, "Create a Kubescape SaaS account ID account ID is not found in cache. After creating the account, the account ID will be saved in cache. In addition, the scanning results will be uploaded to the Kubescape SaaS")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.KubeContext, "kube-context", "", "", "Kube context. Default will use the current-context")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.AccountID, "account", "", "", "Kubescape SaaS account ID. Default will load account ID from cache")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
|
||||
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
|
||||
@@ -103,10 +87,9 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("fail-threshold", "use '--compliance-threshold' flag instead. Flag will be removed at 1.Dec.2023")
|
||||
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.ClientID, "client-id", "", "", "Kubescape SaaS client ID. Default will load client ID from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
scanCmd.PersistentFlags().StringVarP(&scanInfo.Credentials.SecretKey, "secret-key", "", "", "Kubescape SaaS secret key. Default will load secret key from cache, read more - https://hub.armosec.io/docs/authentication")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("client-id", "login to Kubescape SaaS will be unsupported, please contact the Kubescape maintainers for more information")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("secret-key", "login to Kubescape SaaS will be unsupported, please contact the Kubescape maintainers for more information")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("client-id", "Client ID is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("create-account", "Create account is no longer supported. In case of a missing Account ID and a configured backend server, a new account id will be generated automatically by Kubescape. Feel free to contact the Kubescape maintainers for more information.")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("secret-key", "Secret Key is no longer supported. Feel free to contact the Kubescape maintainers for more information.")
|
||||
|
||||
// hidden flags
|
||||
scanCmd.PersistentFlags().MarkHidden("omit-raw-resources")
|
||||
|
||||
@@ -68,6 +68,16 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
|
||||
&cautils.ScanInfo{FailThresholdSeverity: "Unknown"},
|
||||
ErrUnknownSeverity,
|
||||
},
|
||||
{
|
||||
"Security view should be invalid for scan info",
|
||||
&cautils.ScanInfo{View: string(cautils.SecurityViewType)},
|
||||
ErrSecurityViewNotSupported,
|
||||
},
|
||||
{
|
||||
"Empty view should be valid for scan info",
|
||||
&cautils.ScanInfo{},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
var (
|
||||
workloadExample = fmt.Sprintf(`
|
||||
This command is still in BETA. Feel free to contact the kubescape maintainers for more information.
|
||||
This command is still in BETA. Feel free to contact the Kubescape maintainers for more information.
|
||||
|
||||
Scan a workload for misconfigurations and image vulnerabilities.
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func getExceptionsCmd(ks meta.IKubescape, submitInfo *metav1.Submit) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "exceptions <full path to exceptions file>",
|
||||
Short: "Submit exceptions to the Kubescape SaaS version",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("missing full path to exceptions file")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err := ks.SubmitExceptions(context.TODO(), &submitInfo.Credentials, args[0]); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
|
||||
"github.com/kubescape/rbac-utils/rbacscanner"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
rbacExamples = fmt.Sprintf(`
|
||||
# Submit cluster's Role-Based Access Control(RBAC)
|
||||
%[1]s submit rbac
|
||||
|
||||
# Submit cluster's Role-Based Access Control(RBAC) with account ID
|
||||
%[1]s submit rbac --account <account-id>
|
||||
`, cautils.ExecName())
|
||||
)
|
||||
|
||||
// getRBACCmd represents the RBAC command
|
||||
func getRBACCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "rbac",
|
||||
Deprecated: "This command is deprecated and will not be supported after 1/Jan/2023. Please use the 'scan' command instead.",
|
||||
Example: rbacExamples,
|
||||
Short: "Submit cluster's Role-Based Access Control(RBAC)",
|
||||
Long: ``,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
if clusterConfig.GetAccountID() == "" {
|
||||
return fmt.Errorf("account ID is not set, run '%[1]s submit rbac --account <account-id>'", cautils.ExecName())
|
||||
}
|
||||
|
||||
// list RBAC
|
||||
rbacObjects := cautils.NewRBACObjects(rbacscanner.NewRbacScannerFromK8sAPI(k8s, clusterConfig.GetAccountID(), clusterConfig.GetContextName()))
|
||||
|
||||
// submit resources
|
||||
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextRBAC)
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
SubmitObjects: rbacObjects,
|
||||
Reporter: r,
|
||||
}
|
||||
|
||||
if err := ks.Submit(context.TODO(), submitInterfaces); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// getKubernetesApi
|
||||
func getKubernetesApi() *k8sinterface.KubernetesApi {
|
||||
if !k8sinterface.IsConnectedToCluster() {
|
||||
return nil
|
||||
}
|
||||
return k8sinterface.NewKubernetesApi()
|
||||
}
|
||||
func getTenantConfig(credentials *cautils.Credentials, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||
}
|
||||
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||
}
|
||||
|
||||
// Check if the flag entered are valid
|
||||
func flagValidationSubmit(submitInfo *v1.Submit) error {
|
||||
|
||||
// Validate the user's credentials
|
||||
return submitInfo.Credentials.Validate()
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
reporterv2 "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter/v2"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var formatVersion string
|
||||
|
||||
type ResultsObject struct {
|
||||
filePath string
|
||||
customerGUID string
|
||||
clusterName string
|
||||
}
|
||||
|
||||
func NewResultsObject(customerGUID, clusterName, filePath string) *ResultsObject {
|
||||
return &ResultsObject{
|
||||
filePath: filePath,
|
||||
customerGUID: customerGUID,
|
||||
clusterName: clusterName,
|
||||
}
|
||||
}
|
||||
|
||||
func (resultsObject *ResultsObject) SetResourcesReport() (*reporthandlingv2.PostureReport, error) {
|
||||
// load framework results from json file
|
||||
report, err := loadResultsFromFile(resultsObject.filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (resultsObject *ResultsObject) ListAllResources() (map[string]workloadinterface.IMetadata, error) {
|
||||
return map[string]workloadinterface.IMetadata{}, nil
|
||||
}
|
||||
|
||||
func getResultsCmd(ks meta.IKubescape, submitInfo *v1.Submit) *cobra.Command {
|
||||
var resultsCmd = &cobra.Command{
|
||||
Use: fmt.Sprintf("results <json file>\nExample:\n$ %[1]s submit results path/to/results.json --format-version v2", cautils.ExecName()),
|
||||
Short: "Submit a pre scanned results file. The file must be in json format",
|
||||
Long: ``,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if err := flagValidationSubmit(submitInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("missing results file")
|
||||
}
|
||||
|
||||
k8s := getKubernetesApi()
|
||||
|
||||
// get config
|
||||
clusterConfig := getTenantConfig(&submitInfo.Credentials, "", "", k8s)
|
||||
if err := clusterConfig.SetTenant(); err != nil {
|
||||
logger.L().Error("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
resultsObjects := NewResultsObject(clusterConfig.GetAccountID(), clusterConfig.GetContextName(), args[0])
|
||||
|
||||
r := reporterv2.NewReportEventReceiver(clusterConfig.GetConfigObj(), uuid.NewString(), reporterv2.SubmitContextScan)
|
||||
|
||||
submitInterfaces := cliinterfaces.SubmitInterfaces{
|
||||
ClusterConfig: clusterConfig,
|
||||
SubmitObjects: resultsObjects,
|
||||
Reporter: r,
|
||||
}
|
||||
|
||||
if err := ks.Submit(context.TODO(), submitInterfaces); err != nil {
|
||||
logger.L().Fatal(err.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
resultsCmd.PersistentFlags().StringVar(&formatVersion, "format-version", "v2", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
|
||||
|
||||
return resultsCmd
|
||||
}
|
||||
func loadResultsFromFile(filePath string) (*reporthandlingv2.PostureReport, error) {
|
||||
report := &reporthandlingv2.PostureReport{}
|
||||
f, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(f, report); err != nil {
|
||||
return report, fmt.Errorf("failed to unmarshal results file: %s, make sure you run kubescape with '--format=json --format-version=v2'", err.Error())
|
||||
}
|
||||
return report, nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package submit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta"
|
||||
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var submitCmdExamples = fmt.Sprintf(`
|
||||
# Submit Kubescape scan results file
|
||||
%[1]s submit results
|
||||
|
||||
# Submit exceptions file to Kubescape SaaS
|
||||
%[1]s submit exceptions
|
||||
`, cautils.ExecName())
|
||||
|
||||
func GetSubmitCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var submitInfo metav1.Submit
|
||||
|
||||
submitCmd := &cobra.Command{
|
||||
Use: "submit <command>",
|
||||
Short: "Submit an object to the Kubescape SaaS version",
|
||||
Long: ``,
|
||||
Example: submitCmdExamples,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
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.armosec.io/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.armosec.io/docs/authentication")
|
||||
|
||||
submitCmd.AddCommand(getExceptionsCmd(ks, &submitInfo))
|
||||
submitCmd.AddCommand(getResultsCmd(ks, &submitInfo))
|
||||
submitCmd.AddCommand(getRBACCmd(ks, &submitInfo))
|
||||
|
||||
return submitCmd
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -24,16 +25,16 @@ var updateCmdExamples = fmt.Sprintf(`
|
||||
func GetUpdateCmd() *cobra.Command {
|
||||
updateCmd := &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update your version",
|
||||
Short: "Update to latest release version",
|
||||
Long: ``,
|
||||
Example: updateCmdExamples,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
//Checking the user's version of kubescape to the latest release
|
||||
if cautils.BuildNumber == cautils.LatestReleaseVersion {
|
||||
//your version == latest version
|
||||
logger.L().Info(("You are in the latest version"))
|
||||
logger.L().Info(("Nothing to update, you are running the latest version"), helpers.String("Version", cautils.BuildNumber))
|
||||
} else {
|
||||
fmt.Printf("please refer to our installation docs in the following link: %s", installationLink)
|
||||
fmt.Printf("Please refer to our installation docs in the following link: %s", installationLink)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -19,10 +20,10 @@ func GetVersionCmd() *cobra.Command {
|
||||
v := cautils.NewIVersionCheckHandler(ctx)
|
||||
v.CheckLatestVersion(ctx, cautils.NewVersionCheckRequest(cautils.BuildNumber, "", "", "version"))
|
||||
fmt.Fprintf(os.Stdout,
|
||||
"Your current version is: %s [git enabled in build: %t]\n",
|
||||
"Your current version is: %s\n",
|
||||
cautils.BuildNumber,
|
||||
isGitEnabled(),
|
||||
)
|
||||
logger.L().Debug(fmt.Sprintf("git enabled in build: %t", isGitEnabled()))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,13 +3,16 @@ package cautils
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/google/uuid"
|
||||
v1 "github.com/kubescape/backend/pkg/client/v1"
|
||||
"github.com/kubescape/backend/pkg/servicediscovery"
|
||||
servicediscoveryv1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
@@ -18,9 +21,18 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
configFileName string = "config"
|
||||
kubescapeNamespace string = "kubescape"
|
||||
kubescapeConfigMapName string = "kubescape-config"
|
||||
configFileName string = "config"
|
||||
kubescapeNamespace string = "kubescape"
|
||||
kubescapeConfigMapName string = "kubescape-config"
|
||||
kubescapeCloudConfigMapName string = "ks-cloud-config"
|
||||
|
||||
// env vars
|
||||
defaultConfigMapNameEnvVar string = "KS_DEFAULT_CONFIGMAP_NAME"
|
||||
defaultCloudConfigMapNameEnvVar string = "KS_DEFAULT_CLOUD_CONFIGMAP_NAME"
|
||||
defaultConfigMapNamespaceEnvVar string = "KS_DEFAULT_CONFIGMAP_NAMESPACE"
|
||||
accountIdEnvVar string = "KS_ACCOUNT_ID"
|
||||
cloudApiUrlEnvVar string = "KS_CLOUD_API_URL"
|
||||
cloudReportUrlEnvVar string = "KS_CLOUD_REPORT_URL"
|
||||
)
|
||||
|
||||
func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName + ".json") }
|
||||
@@ -30,16 +42,10 @@ func ConfigFileFullPath() string { return getter.GetDefaultPath(configFileName +
|
||||
// ======================================================================================
|
||||
|
||||
type ConfigObj struct {
|
||||
AccountID string `json:"accountID,omitempty"`
|
||||
ClientID string `json:"clientID,omitempty"`
|
||||
SecretKey string `json:"secretKey,omitempty"`
|
||||
Token string `json:"invitationParam,omitempty"`
|
||||
CustomerAdminEMail string `json:"adminMail,omitempty"`
|
||||
ClusterName string `json:"clusterName,omitempty"`
|
||||
CloudReportURL string `json:"cloudReportURL,omitempty"`
|
||||
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
|
||||
CloudUIURL string `json:"cloudUIURL,omitempty"`
|
||||
CloudAuthURL string `json:"cloudAuthURL,omitempty"`
|
||||
AccountID string `json:"accountID,omitempty"`
|
||||
ClusterName string `json:"clusterName,omitempty"`
|
||||
CloudReportURL string `json:"cloudReportURL,omitempty"`
|
||||
CloudAPIURL string `json:"cloudAPIURL,omitempty"`
|
||||
}
|
||||
|
||||
// Config - convert ConfigObj to config file
|
||||
@@ -47,17 +53,11 @@ func (co *ConfigObj) Config() []byte {
|
||||
|
||||
// remove cluster name before saving to file
|
||||
clusterName := co.ClusterName
|
||||
customerAdminEMail := co.CustomerAdminEMail
|
||||
token := co.Token
|
||||
co.ClusterName = ""
|
||||
co.Token = ""
|
||||
co.CustomerAdminEMail = ""
|
||||
|
||||
b, err := json.MarshalIndent(co, "", " ")
|
||||
|
||||
co.ClusterName = clusterName
|
||||
co.CustomerAdminEMail = customerAdminEMail
|
||||
co.Token = token
|
||||
|
||||
if err == nil {
|
||||
return b
|
||||
@@ -73,24 +73,12 @@ func (co *ConfigObj) updateEmptyFields(inCO *ConfigObj) error {
|
||||
if inCO.CloudAPIURL != "" {
|
||||
co.CloudAPIURL = inCO.CloudAPIURL
|
||||
}
|
||||
if inCO.CloudAuthURL != "" {
|
||||
co.CloudAuthURL = inCO.CloudAuthURL
|
||||
}
|
||||
if inCO.CloudReportURL != "" {
|
||||
co.CloudReportURL = inCO.CloudReportURL
|
||||
}
|
||||
if inCO.CloudUIURL != "" {
|
||||
co.CloudUIURL = inCO.CloudUIURL
|
||||
}
|
||||
if inCO.ClusterName != "" {
|
||||
co.ClusterName = inCO.ClusterName
|
||||
}
|
||||
if inCO.CustomerAdminEMail != "" {
|
||||
co.CustomerAdminEMail = inCO.CustomerAdminEMail
|
||||
}
|
||||
if inCO.Token != "" {
|
||||
co.Token = inCO.Token
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -99,27 +87,17 @@ func (co *ConfigObj) updateEmptyFields(inCO *ConfigObj) error {
|
||||
// =============================== interface ============================================
|
||||
// ======================================================================================
|
||||
type ITenantConfig interface {
|
||||
// set
|
||||
SetTenant() error
|
||||
UpdateCachedConfig() error
|
||||
DeleteCachedConfig(ctx context.Context) error
|
||||
GenerateAccountID() (string, error)
|
||||
DeleteAccountID() error
|
||||
|
||||
// getters
|
||||
GetContextName() string
|
||||
GetAccountID() string
|
||||
GetTenantEmail() string
|
||||
GetToken() string
|
||||
GetClientID() string
|
||||
GetSecretKey() string
|
||||
GetConfigObj() *ConfigObj
|
||||
GetCloudReportURL() string
|
||||
GetCloudAPIURL() string
|
||||
GetCloudUIURL() string
|
||||
GetCloudAuthURL() string
|
||||
// GetBackendAPI() getter.IBackend
|
||||
// GenerateURL()
|
||||
|
||||
IsConfigFound() bool
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
@@ -130,23 +108,19 @@ type ITenantConfig interface {
|
||||
var _ ITenantConfig = &LocalConfig{}
|
||||
|
||||
type LocalConfig struct {
|
||||
backendAPI getter.IBackend
|
||||
configObj *ConfigObj
|
||||
configObj *ConfigObj
|
||||
}
|
||||
|
||||
func NewLocalConfig(
|
||||
backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *LocalConfig {
|
||||
|
||||
func NewLocalConfig(accountID, clusterName string, customClusterName string) *LocalConfig {
|
||||
lc := &LocalConfig{
|
||||
backendAPI: backendAPI,
|
||||
configObj: &ConfigObj{},
|
||||
configObj: &ConfigObj{},
|
||||
}
|
||||
// get from configMap
|
||||
if existsConfigFile() { // get from file
|
||||
loadConfigFromFile(lc.configObj)
|
||||
}
|
||||
|
||||
updateCredentials(lc.configObj, credentials)
|
||||
updateAccountID(lc.configObj, accountID)
|
||||
updateCloudURLs(lc.configObj)
|
||||
|
||||
// If a custom cluster name is provided then set that name, else use the cluster's original name
|
||||
@@ -156,59 +130,31 @@ func NewLocalConfig(
|
||||
lc.configObj.ClusterName = AdoptClusterName(clusterName) // override config clusterName
|
||||
}
|
||||
|
||||
lc.backendAPI.SetAccountID(lc.configObj.AccountID)
|
||||
lc.backendAPI.SetClientID(lc.configObj.ClientID)
|
||||
lc.backendAPI.SetSecretKey(lc.configObj.SecretKey)
|
||||
if lc.configObj.CloudAPIURL != "" {
|
||||
lc.backendAPI.SetCloudAPIURL(lc.configObj.CloudAPIURL)
|
||||
} else {
|
||||
lc.configObj.CloudAPIURL = lc.backendAPI.GetCloudAPIURL()
|
||||
}
|
||||
if lc.configObj.CloudAuthURL != "" {
|
||||
lc.backendAPI.SetCloudAuthURL(lc.configObj.CloudAuthURL)
|
||||
} else {
|
||||
lc.configObj.CloudAuthURL = lc.backendAPI.GetCloudAuthURL()
|
||||
}
|
||||
if lc.configObj.CloudReportURL != "" {
|
||||
lc.backendAPI.SetCloudReportURL(lc.configObj.CloudReportURL)
|
||||
} else {
|
||||
lc.configObj.CloudReportURL = lc.backendAPI.GetCloudReportURL()
|
||||
}
|
||||
if lc.configObj.CloudUIURL != "" {
|
||||
lc.backendAPI.SetCloudUIURL(lc.configObj.CloudUIURL)
|
||||
} else {
|
||||
lc.configObj.CloudUIURL = lc.backendAPI.GetCloudUIURL()
|
||||
}
|
||||
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", lc.backendAPI.GetCloudAPIURL()), helpers.String("auth", lc.backendAPI.GetCloudAuthURL()), helpers.String("report", lc.backendAPI.GetCloudReportURL()), helpers.String("UI", lc.backendAPI.GetCloudUIURL()))
|
||||
|
||||
initializeCloudAPI(lc)
|
||||
updatedKsCloud := initializeCloudAPI(lc)
|
||||
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", updatedKsCloud.GetCloudAPIURL()), helpers.String("report", updatedKsCloud.GetCloudReportURL()))
|
||||
|
||||
return lc
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) GetConfigObj() *ConfigObj { return lc.configObj }
|
||||
func (lc *LocalConfig) GetTenantEmail() string { return lc.configObj.CustomerAdminEMail }
|
||||
func (lc *LocalConfig) GetAccountID() string { return lc.configObj.AccountID }
|
||||
func (lc *LocalConfig) GetClientID() string { return lc.configObj.ClientID }
|
||||
func (lc *LocalConfig) GetSecretKey() string { return lc.configObj.SecretKey }
|
||||
func (lc *LocalConfig) GetContextName() string { return lc.configObj.ClusterName }
|
||||
func (lc *LocalConfig) GetToken() string { return lc.configObj.Token }
|
||||
func (lc *LocalConfig) GetCloudReportURL() string { return lc.configObj.CloudReportURL }
|
||||
func (lc *LocalConfig) GetCloudAPIURL() string { return lc.configObj.CloudAPIURL }
|
||||
func (lc *LocalConfig) GetCloudUIURL() string { return lc.configObj.CloudUIURL }
|
||||
func (lc *LocalConfig) GetCloudAuthURL() string { return lc.configObj.CloudAuthURL }
|
||||
func (lc *LocalConfig) IsConfigFound() bool { return existsConfigFile() }
|
||||
func (lc *LocalConfig) SetTenant() error {
|
||||
|
||||
// Kubescape Cloud tenant GUID
|
||||
if err := getTenantConfigFromBE(lc.backendAPI, lc.configObj); err != nil {
|
||||
return err
|
||||
}
|
||||
lc.UpdateCachedConfig()
|
||||
return nil
|
||||
|
||||
func (lc *LocalConfig) GenerateAccountID() (string, error) {
|
||||
lc.configObj.AccountID = uuid.NewString()
|
||||
err := lc.UpdateCachedConfig()
|
||||
return lc.configObj.AccountID, err
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) DeleteAccountID() error {
|
||||
lc.configObj.AccountID = ""
|
||||
return lc.UpdateCachedConfig()
|
||||
}
|
||||
|
||||
func (lc *LocalConfig) UpdateCachedConfig() error {
|
||||
logger.L().Debug("updating cached config", helpers.Interface("configObj", lc.configObj))
|
||||
return updateConfigFile(lc.configObj)
|
||||
}
|
||||
|
||||
@@ -219,26 +165,6 @@ func (lc *LocalConfig) DeleteCachedConfig(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTenantConfigFromBE(backendAPI getter.IBackend, configObj *ConfigObj) error {
|
||||
|
||||
// get from Kubescape Cloud API
|
||||
tenantResponse, err := backendAPI.GetTenant()
|
||||
if err == nil && tenantResponse != nil {
|
||||
if tenantResponse.AdminMail != "" { // registered tenant
|
||||
configObj.CustomerAdminEMail = tenantResponse.AdminMail
|
||||
} else { // new tenant
|
||||
configObj.Token = tenantResponse.Token
|
||||
configObj.AccountID = tenantResponse.TenantID
|
||||
}
|
||||
} else {
|
||||
if err != nil && !strings.Contains(err.Error(), "already exists") {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// ========================== Cluster Config ============================================
|
||||
// ======================================================================================
|
||||
@@ -251,8 +177,6 @@ KS_DEFAULT_CONFIGMAP_NAME // name of configmap, if not set default is 'kubescap
|
||||
KS_DEFAULT_CONFIGMAP_NAMESPACE // configmap namespace, if not set default is 'default'
|
||||
|
||||
KS_ACCOUNT_ID
|
||||
KS_CLIENT_ID
|
||||
KS_SECRET_KEY
|
||||
|
||||
TODO - support:
|
||||
KS_CACHE // path to cached files
|
||||
@@ -260,21 +184,20 @@ KS_CACHE // path to cached files
|
||||
var _ ITenantConfig = &ClusterConfig{}
|
||||
|
||||
type ClusterConfig struct {
|
||||
backendAPI getter.IBackend
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
configObj *ConfigObj
|
||||
configMapName string
|
||||
configMapNamespace string
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
configObj *ConfigObj
|
||||
configMapNamespace string
|
||||
ksConfigMapName string
|
||||
ksCloudConfigMapName string
|
||||
}
|
||||
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBackend, credentials *Credentials, clusterName string, customClusterName string) *ClusterConfig {
|
||||
// var configObj *ConfigObj
|
||||
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, accountID, clusterName string, customClusterName string) *ClusterConfig {
|
||||
c := &ClusterConfig{
|
||||
k8s: k8s,
|
||||
backendAPI: backendAPI,
|
||||
configObj: &ConfigObj{},
|
||||
configMapName: getConfigMapName(),
|
||||
configMapNamespace: GetConfigMapNamespace(),
|
||||
k8s: k8s,
|
||||
configObj: &ConfigObj{},
|
||||
ksConfigMapName: getKubescapeConfigMapName(),
|
||||
ksCloudConfigMapName: getKubescapeCloudConfigMapName(),
|
||||
configMapNamespace: GetConfigMapNamespace(),
|
||||
}
|
||||
|
||||
// first, load from file
|
||||
@@ -283,11 +206,16 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
|
||||
}
|
||||
|
||||
// second, load from configMap
|
||||
if c.existsConfigMap() {
|
||||
c.updateConfigEmptyFieldsFromConfigMap()
|
||||
if c.existsConfigMap(c.ksConfigMapName) {
|
||||
c.updateConfigEmptyFieldsFromKubescapeConfigMap()
|
||||
}
|
||||
|
||||
updateCredentials(c.configObj, credentials)
|
||||
// third, load urls from cloudConfigMap
|
||||
if c.existsConfigMap(c.ksCloudConfigMapName) {
|
||||
c.updateConfigEmptyFieldsFromKubescapeCloudConfigMap()
|
||||
}
|
||||
|
||||
updateAccountID(c.configObj, accountID)
|
||||
updateCloudURLs(c.configObj)
|
||||
|
||||
// If a custom cluster name is provided then set that name, else use the cluster's original name
|
||||
@@ -302,80 +230,23 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken
|
||||
} else { // override the cluster name if it has unwanted characters
|
||||
c.configObj.ClusterName = AdoptClusterName(c.configObj.ClusterName)
|
||||
}
|
||||
|
||||
c.backendAPI.SetAccountID(c.configObj.AccountID)
|
||||
c.backendAPI.SetClientID(c.configObj.ClientID)
|
||||
c.backendAPI.SetSecretKey(c.configObj.SecretKey)
|
||||
if c.configObj.CloudAPIURL != "" {
|
||||
c.backendAPI.SetCloudAPIURL(c.configObj.CloudAPIURL)
|
||||
} else {
|
||||
c.configObj.CloudAPIURL = c.backendAPI.GetCloudAPIURL()
|
||||
}
|
||||
if c.configObj.CloudAuthURL != "" {
|
||||
c.backendAPI.SetCloudAuthURL(c.configObj.CloudAuthURL)
|
||||
} else {
|
||||
c.configObj.CloudAuthURL = c.backendAPI.GetCloudAuthURL()
|
||||
}
|
||||
if c.configObj.CloudReportURL != "" {
|
||||
c.backendAPI.SetCloudReportURL(c.configObj.CloudReportURL)
|
||||
} else {
|
||||
c.configObj.CloudReportURL = c.backendAPI.GetCloudReportURL()
|
||||
}
|
||||
if c.configObj.CloudUIURL != "" {
|
||||
c.backendAPI.SetCloudUIURL(c.configObj.CloudUIURL)
|
||||
} else {
|
||||
c.configObj.CloudUIURL = c.backendAPI.GetCloudUIURL()
|
||||
}
|
||||
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", c.backendAPI.GetCloudAPIURL()), helpers.String("auth", c.backendAPI.GetCloudAuthURL()), helpers.String("report", c.backendAPI.GetCloudReportURL()), helpers.String("UI", c.backendAPI.GetCloudUIURL()))
|
||||
|
||||
initializeCloudAPI(c)
|
||||
|
||||
updatedKsCloud := initializeCloudAPI(c)
|
||||
logger.L().Debug("Kubescape Cloud URLs", helpers.String("api", updatedKsCloud.GetCloudAPIURL()), helpers.String("report", updatedKsCloud.GetCloudReportURL()))
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetConfigObj() *ConfigObj { return c.configObj }
|
||||
func (c *ClusterConfig) GetDefaultNS() string { return c.configMapNamespace }
|
||||
func (c *ClusterConfig) GetAccountID() string { return c.configObj.AccountID }
|
||||
func (c *ClusterConfig) GetClientID() string { return c.configObj.ClientID }
|
||||
func (c *ClusterConfig) GetSecretKey() string { return c.configObj.SecretKey }
|
||||
func (c *ClusterConfig) GetTenantEmail() string { return c.configObj.CustomerAdminEMail }
|
||||
func (c *ClusterConfig) GetToken() string { return c.configObj.Token }
|
||||
func (c *ClusterConfig) GetCloudReportURL() string { return c.configObj.CloudReportURL }
|
||||
func (c *ClusterConfig) GetCloudAPIURL() string { return c.configObj.CloudAPIURL }
|
||||
func (c *ClusterConfig) GetCloudUIURL() string { return c.configObj.CloudUIURL }
|
||||
func (c *ClusterConfig) GetCloudAuthURL() string { return c.configObj.CloudAuthURL }
|
||||
|
||||
func (c *ClusterConfig) IsConfigFound() bool { return existsConfigFile() || c.existsConfigMap() }
|
||||
|
||||
func (c *ClusterConfig) SetTenant() error {
|
||||
|
||||
// ARMO tenant GUID
|
||||
if err := getTenantConfigFromBE(c.backendAPI, c.configObj); err != nil {
|
||||
return err
|
||||
}
|
||||
c.UpdateCachedConfig()
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) UpdateCachedConfig() error {
|
||||
// update/create config
|
||||
if c.existsConfigMap() {
|
||||
if err := c.updateConfigMap(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := c.createConfigMap(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
logger.L().Debug("updating cached config", helpers.Interface("configObj", c.configObj))
|
||||
return updateConfigFile(c.configObj)
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) DeleteCachedConfig(ctx context.Context) error {
|
||||
if err := c.deleteConfigMap(); err != nil {
|
||||
logger.L().Ctx(ctx).Warning(err.Error())
|
||||
}
|
||||
if err := DeleteConfigFile(); err != nil {
|
||||
logger.L().Ctx(ctx).Warning(err.Error())
|
||||
}
|
||||
@@ -393,28 +264,44 @@ func (c *ClusterConfig) ToMapString() map[string]interface{} {
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigEmptyFieldsFromConfigMap() error {
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeConfigMap() error {
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.ksConfigMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tempCO := ConfigObj{}
|
||||
if jsonConf, ok := configMap.Data["config.json"]; ok {
|
||||
json.Unmarshal([]byte(jsonConf), &tempCO)
|
||||
if err = json.Unmarshal([]byte(jsonConf), &tempCO); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.configObj.updateEmptyFields(&tempCO)
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) loadConfigFromConfigMap() error {
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
func (c *ClusterConfig) updateConfigEmptyFieldsFromKubescapeCloudConfigMap() error {
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.ksCloudConfigMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return loadConfigFromData(c.configObj, configMap.Data)
|
||||
if jsonConf, ok := configMap.Data["services"]; ok {
|
||||
services, err := servicediscovery.GetServices(
|
||||
servicediscoveryv1.NewServiceDiscoveryStreamV1([]byte(jsonConf)),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if services.GetApiServerUrl() != "" {
|
||||
c.configObj.CloudAPIURL = services.GetApiServerUrl()
|
||||
}
|
||||
if services.GetReportReceiverHttpUrl() != "" {
|
||||
c.configObj.CloudReportURL = services.GetReportReceiverHttpUrl()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadConfigFromData(co *ConfigObj, data map[string]string) error {
|
||||
@@ -428,107 +315,36 @@ func loadConfigFromData(co *ConfigObj, data map[string]string) error {
|
||||
|
||||
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
|
||||
|
||||
func (c *ClusterConfig) existsConfigMap(name string) bool {
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), name, metav1.GetOptions{})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) GetValueByKeyFromConfigMap(key string) (string, error) {
|
||||
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if val, ok := configMap.Data[key]; ok {
|
||||
return val, nil
|
||||
} else {
|
||||
return "", fmt.Errorf("value does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func GetValueFromConfigJson(key string) (string, error) {
|
||||
data, err := os.ReadFile(ConfigFileFullPath())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var obj map[string]interface{}
|
||||
if err := json.Unmarshal(data, &obj); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if val, ok := obj[key]; ok {
|
||||
return fmt.Sprint(val), nil
|
||||
} else {
|
||||
return "", fmt.Errorf("value does not exist")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) SetKeyValueInConfigmap(key string, value string) error {
|
||||
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
configMap = &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: c.configMapName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if len(configMap.Data) == 0 {
|
||||
configMap.Data = make(map[string]string)
|
||||
}
|
||||
|
||||
configMap.Data[key] = value
|
||||
|
||||
if err != nil {
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
} else {
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func existsConfigFile() bool {
|
||||
_, err := os.ReadFile(ConfigFileFullPath())
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) createConfigMap() error {
|
||||
if c.k8s == nil {
|
||||
return nil
|
||||
}
|
||||
configMap := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: c.configMapName,
|
||||
},
|
||||
}
|
||||
c.updateConfigData(configMap)
|
||||
|
||||
_, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Create(context.Background(), configMap, metav1.CreateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigMap() error {
|
||||
if c.k8s == nil {
|
||||
return nil
|
||||
}
|
||||
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Get(context.Background(), c.configMapName, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
func updateConfigFile(configObj *ConfigObj) error {
|
||||
fullPath := ConfigFileFullPath()
|
||||
dir := filepath.Dir(fullPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.updateConfigData(configMap)
|
||||
|
||||
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.configMapNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
|
||||
return err
|
||||
return os.WriteFile(fullPath, configObj.Config(), 0664) //nolint:gosec
|
||||
}
|
||||
|
||||
func updateConfigFile(configObj *ConfigObj) error {
|
||||
return os.WriteFile(ConfigFileFullPath(), configObj.Config(), 0664) //nolint:gosec
|
||||
func (c *ClusterConfig) GenerateAccountID() (string, error) {
|
||||
c.configObj.AccountID = uuid.NewString()
|
||||
err := c.UpdateCachedConfig()
|
||||
return c.configObj.AccountID, err
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) DeleteAccountID() error {
|
||||
c.configObj.AccountID = ""
|
||||
return c.UpdateCachedConfig()
|
||||
}
|
||||
|
||||
func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
|
||||
@@ -561,28 +377,6 @@ func readConfig(dat []byte, configObj *ConfigObj) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the customer is submitted
|
||||
func (clusterConfig *ClusterConfig) IsSubmitted() bool {
|
||||
return clusterConfig.existsConfigMap() || existsConfigFile()
|
||||
}
|
||||
|
||||
// Check if the customer is registered
|
||||
func (clusterConfig *ClusterConfig) IsRegistered() bool {
|
||||
|
||||
// get from armoBE
|
||||
tenantResponse, err := clusterConfig.backendAPI.GetTenant()
|
||||
if err == nil && tenantResponse != nil {
|
||||
if tenantResponse.AdminMail != "" { // this customer already belongs to some user
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (clusterConfig *ClusterConfig) deleteConfigMap() error {
|
||||
return clusterConfig.k8s.KubernetesClient.CoreV1().ConfigMaps(clusterConfig.configMapNamespace).Delete(context.Background(), clusterConfig.configMapName, metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
func DeleteConfigFile() error {
|
||||
return os.Remove(ConfigFileFullPath())
|
||||
}
|
||||
@@ -595,67 +389,49 @@ func AdoptClusterName(clusterName string) string {
|
||||
return re.ReplaceAllString(clusterName, "-")
|
||||
}
|
||||
|
||||
func getConfigMapName() string {
|
||||
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAME"); n != "" {
|
||||
func getKubescapeConfigMapName() string {
|
||||
if n := os.Getenv(defaultConfigMapNameEnvVar); n != "" {
|
||||
return n
|
||||
}
|
||||
return kubescapeConfigMapName
|
||||
}
|
||||
|
||||
func getKubescapeCloudConfigMapName() string {
|
||||
if n := os.Getenv(defaultCloudConfigMapNameEnvVar); n != "" {
|
||||
return n
|
||||
}
|
||||
|
||||
return kubescapeCloudConfigMapName
|
||||
}
|
||||
|
||||
// GetConfigMapNamespace returns the namespace of the cluster config, which is the same for all in-cluster components
|
||||
func GetConfigMapNamespace() string {
|
||||
if n := os.Getenv("KS_DEFAULT_CONFIGMAP_NAMESPACE"); n != "" {
|
||||
if n := os.Getenv(defaultConfigMapNamespaceEnvVar); n != "" {
|
||||
return n
|
||||
}
|
||||
return kubescapeNamespace
|
||||
}
|
||||
|
||||
func getAccountFromEnv(credentials *Credentials) {
|
||||
// load from env
|
||||
if accountID := os.Getenv("KS_ACCOUNT_ID"); credentials.Account == "" && accountID != "" {
|
||||
credentials.Account = accountID
|
||||
}
|
||||
if clientID := os.Getenv("KS_CLIENT_ID"); credentials.ClientID == "" && clientID != "" {
|
||||
credentials.ClientID = clientID
|
||||
}
|
||||
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
|
||||
func updateAccountID(configObj *ConfigObj, accountID string) {
|
||||
if accountID != "" {
|
||||
configObj.AccountID = accountID
|
||||
}
|
||||
|
||||
if envAccountID := os.Getenv(accountIdEnvVar); envAccountID != "" {
|
||||
configObj.AccountID = envAccountID
|
||||
}
|
||||
}
|
||||
|
||||
func getCloudURLsFromEnv(cloudURLs *CloudURLs) {
|
||||
// load from env
|
||||
if cloudAPIURL := os.Getenv("KS_CLOUD_API_URL"); cloudAPIURL != "" {
|
||||
if cloudAPIURL := os.Getenv(cloudApiUrlEnvVar); cloudAPIURL != "" {
|
||||
logger.L().Debug("cloud API URL updated from env var", helpers.Interface(cloudApiUrlEnvVar, cloudAPIURL))
|
||||
cloudURLs.CloudAPIURL = cloudAPIURL
|
||||
}
|
||||
if cloudAuthURL := os.Getenv("KS_CLOUD_AUTH_URL"); cloudAuthURL != "" {
|
||||
cloudURLs.CloudAuthURL = cloudAuthURL
|
||||
}
|
||||
if cloudReportURL := os.Getenv("KS_CLOUD_REPORT_URL"); cloudReportURL != "" {
|
||||
if cloudReportURL := os.Getenv(cloudReportUrlEnvVar); cloudReportURL != "" {
|
||||
logger.L().Debug("cloud Report URL updated from env var", helpers.Interface(cloudReportUrlEnvVar, cloudReportURL))
|
||||
cloudURLs.CloudReportURL = cloudReportURL
|
||||
}
|
||||
if cloudUIURL := os.Getenv("KS_CLOUD_UI_URL"); cloudUIURL != "" {
|
||||
cloudURLs.CloudUIURL = cloudUIURL
|
||||
}
|
||||
}
|
||||
|
||||
func updateCloudURLs(configObj *ConfigObj) {
|
||||
@@ -666,26 +442,25 @@ func updateCloudURLs(configObj *ConfigObj) {
|
||||
if cloudURLs.CloudAPIURL != "" {
|
||||
configObj.CloudAPIURL = cloudURLs.CloudAPIURL // override config CloudAPIURL
|
||||
}
|
||||
if cloudURLs.CloudAuthURL != "" {
|
||||
configObj.CloudAuthURL = cloudURLs.CloudAuthURL // override config CloudAuthURL
|
||||
}
|
||||
if cloudURLs.CloudReportURL != "" {
|
||||
configObj.CloudReportURL = cloudURLs.CloudReportURL // override config CloudReportURL
|
||||
}
|
||||
if cloudURLs.CloudUIURL != "" {
|
||||
configObj.CloudUIURL = cloudURLs.CloudUIURL // override config CloudUIURL
|
||||
|
||||
}
|
||||
|
||||
func initializeCloudAPI(c ITenantConfig) *v1.KSCloudAPI {
|
||||
logger.L().Debug("initializing KS Cloud API from config", helpers.String("accountID", c.GetAccountID()), helpers.String("cloudAPIURL", c.GetCloudAPIURL()), helpers.String("cloudReportURL", c.GetCloudReportURL()))
|
||||
cloud, err := v1.NewKSCloudAPI(c.GetCloudAPIURL(), c.GetCloudReportURL(), c.GetAccountID())
|
||||
if err != nil {
|
||||
logger.L().Fatal("failed to create KS Cloud client", helpers.Error(err))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func initializeCloudAPI(c ITenantConfig) {
|
||||
cloud := getter.GetKSCloudAPIConnector()
|
||||
cloud.SetAccountID(c.GetAccountID())
|
||||
cloud.SetClientID(c.GetClientID())
|
||||
cloud.SetSecretKey(c.GetSecretKey())
|
||||
cloud.SetCloudAuthURL(c.GetCloudAuthURL())
|
||||
cloud.SetCloudReportURL(c.GetCloudReportURL())
|
||||
cloud.SetCloudUIURL(c.GetCloudUIURL())
|
||||
cloud.SetCloudAPIURL(c.GetCloudAPIURL())
|
||||
getter.SetKSCloudAPIConnector(cloud)
|
||||
return getter.GetKSCloudAPIConnector()
|
||||
}
|
||||
|
||||
func GetTenantConfig(accountID, clusterName, customClusterName string, k8s *k8sinterface.KubernetesApi) ITenantConfig {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return NewLocalConfig(accountID, clusterName, customClusterName)
|
||||
}
|
||||
return NewClusterConfig(k8s, accountID, clusterName, customClusterName)
|
||||
}
|
||||
|
||||
@@ -12,29 +12,21 @@ import (
|
||||
|
||||
func mockConfigObj() *ConfigObj {
|
||||
return &ConfigObj{
|
||||
AccountID: "aaa",
|
||||
ClientID: "bbb",
|
||||
SecretKey: "ccc",
|
||||
ClusterName: "ddd",
|
||||
CustomerAdminEMail: "ab@cd",
|
||||
Token: "eee",
|
||||
CloudReportURL: "report.armo.cloud",
|
||||
CloudAPIURL: "api.armosec.io",
|
||||
CloudUIURL: "cloud.armosec.io",
|
||||
CloudAuthURL: "auth.armosec.io",
|
||||
AccountID: "aaa",
|
||||
ClusterName: "ddd",
|
||||
CloudReportURL: "report.domain.com",
|
||||
CloudAPIURL: "api.domain.com",
|
||||
}
|
||||
}
|
||||
func mockLocalConfig() *LocalConfig {
|
||||
return &LocalConfig{
|
||||
backendAPI: nil,
|
||||
configObj: mockConfigObj(),
|
||||
configObj: mockConfigObj(),
|
||||
}
|
||||
}
|
||||
|
||||
func mockClusterConfig() *ClusterConfig {
|
||||
return &ClusterConfig{
|
||||
backendAPI: nil,
|
||||
configObj: mockConfigObj(),
|
||||
configObj: mockConfigObj(),
|
||||
}
|
||||
}
|
||||
func TestConfig(t *testing.T) {
|
||||
@@ -43,15 +35,9 @@ func TestConfig(t *testing.T) {
|
||||
|
||||
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, co.CloudReportURL, cop.CloudReportURL)
|
||||
assert.Equal(t, co.CloudAPIURL, cop.CloudAPIURL)
|
||||
assert.Equal(t, co.CloudUIURL, cop.CloudUIURL)
|
||||
assert.Equal(t, co.CloudAuthURL, cop.CloudAuthURL)
|
||||
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
|
||||
assert.Equal(t, "", cop.CustomerAdminEMail) // Not copied to bytes
|
||||
assert.Equal(t, "", cop.Token) // Not copied to bytes
|
||||
assert.Equal(t, "", cop.ClusterName) // Not copied to bytes
|
||||
|
||||
}
|
||||
|
||||
@@ -65,27 +51,15 @@ func TestITenantConfig(t *testing.T) {
|
||||
|
||||
// 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())
|
||||
assert.Equal(t, co.CloudReportURL, lc.GetCloudReportURL())
|
||||
assert.Equal(t, co.CloudAPIURL, lc.GetCloudAPIURL())
|
||||
assert.Equal(t, co.CloudUIURL, lc.GetCloudUIURL())
|
||||
assert.Equal(t, co.CloudAuthURL, lc.GetCloudAuthURL())
|
||||
|
||||
// test ClusterConfig methods
|
||||
assert.Equal(t, co.AccountID, c.GetAccountID())
|
||||
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())
|
||||
assert.Equal(t, co.CloudReportURL, c.GetCloudReportURL())
|
||||
assert.Equal(t, co.CloudAPIURL, c.GetCloudAPIURL())
|
||||
assert.Equal(t, co.CloudUIURL, c.GetCloudUIURL())
|
||||
assert.Equal(t, co.CloudAuthURL, c.GetCloudAuthURL())
|
||||
}
|
||||
|
||||
func TestUpdateConfigData(t *testing.T) {
|
||||
@@ -96,12 +70,8 @@ func TestUpdateConfigData(t *testing.T) {
|
||||
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"])
|
||||
assert.Equal(t, c.GetCloudReportURL(), configMap.Data["cloudReportURL"])
|
||||
assert.Equal(t, c.GetCloudAPIURL(), configMap.Data["cloudAPIURL"])
|
||||
assert.Equal(t, c.GetCloudUIURL(), configMap.Data["cloudUIURL"])
|
||||
assert.Equal(t, c.GetCloudAuthURL(), configMap.Data["cloudAuthURL"])
|
||||
}
|
||||
|
||||
func TestReadConfig(t *testing.T) {
|
||||
@@ -114,15 +84,9 @@ func TestReadConfig(t *testing.T) {
|
||||
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)
|
||||
assert.Equal(t, com.CloudReportURL, co.CloudReportURL)
|
||||
assert.Equal(t, com.CloudAPIURL, co.CloudAPIURL)
|
||||
assert.Equal(t, com.CloudUIURL, co.CloudUIURL)
|
||||
assert.Equal(t, com.CloudAuthURL, co.CloudAuthURL)
|
||||
}
|
||||
|
||||
func TestLoadConfigFromData(t *testing.T) {
|
||||
@@ -141,15 +105,9 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
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)
|
||||
assert.Equal(t, c.GetCloudReportURL(), co.CloudReportURL)
|
||||
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
|
||||
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
|
||||
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
|
||||
}
|
||||
|
||||
// use case: all data is in config.json
|
||||
@@ -167,12 +125,8 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
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.GetCloudReportURL(), co.CloudReportURL)
|
||||
assert.Equal(t, c.GetCloudAPIURL(), co.CloudAPIURL)
|
||||
assert.Equal(t, c.GetCloudUIURL(), co.CloudUIURL)
|
||||
assert.Equal(t, c.GetCloudAuthURL(), co.CloudAuthURL)
|
||||
}
|
||||
|
||||
// use case: some data is in config.json
|
||||
@@ -183,21 +137,15 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
}
|
||||
|
||||
// add to map
|
||||
configMap.Data["clientID"] = c.configObj.ClientID
|
||||
configMap.Data["secretKey"] = c.configObj.SecretKey
|
||||
configMap.Data["cloudReportURL"] = c.configObj.CloudReportURL
|
||||
|
||||
// delete the content
|
||||
c.configObj.ClientID = ""
|
||||
c.configObj.SecretKey = ""
|
||||
c.configObj.CloudReportURL = ""
|
||||
|
||||
configMap.Data["config.json"] = string(c.GetConfigObj().Config())
|
||||
loadConfigFromData(c.configObj, configMap.Data)
|
||||
|
||||
assert.NotEmpty(t, c.GetAccountID())
|
||||
assert.NotEmpty(t, c.GetClientID())
|
||||
assert.NotEmpty(t, c.GetSecretKey())
|
||||
assert.NotEmpty(t, c.GetCloudReportURL())
|
||||
}
|
||||
|
||||
@@ -212,19 +160,11 @@ func TestLoadConfigFromData(t *testing.T) {
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -289,13 +229,9 @@ func Test_initializeCloudAPI(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
initializeCloudAPI(tt.args.c)
|
||||
cloud := getter.GetKSCloudAPIConnector()
|
||||
assert.Equal(t, tt.args.c.GetCloudAPIURL(), cloud.GetCloudAPIURL())
|
||||
assert.Equal(t, tt.args.c.GetCloudAuthURL(), cloud.GetCloudAuthURL())
|
||||
assert.Equal(t, tt.args.c.GetCloudUIURL(), cloud.GetCloudUIURL())
|
||||
assert.Equal(t, tt.args.c.GetCloudReportURL(), cloud.GetCloudReportURL())
|
||||
assert.Equal(t, "https://api.domain.com", cloud.GetCloudAPIURL())
|
||||
assert.Equal(t, "https://report.domain.com", cloud.GetCloudReportURL())
|
||||
assert.Equal(t, tt.args.c.GetAccountID(), cloud.GetAccountID())
|
||||
assert.Equal(t, tt.args.c.GetClientID(), cloud.GetClientID())
|
||||
assert.Equal(t, tt.args.c.GetSecretKey(), cloud.GetSecretKey())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -354,90 +290,58 @@ func TestUpdateEmptyFields(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
outCo: &ConfigObj{
|
||||
AccountID: "",
|
||||
Token: "",
|
||||
CustomerAdminEMail: "",
|
||||
ClusterName: "",
|
||||
CloudReportURL: "",
|
||||
CloudAPIURL: "",
|
||||
CloudUIURL: "",
|
||||
CloudAuthURL: "",
|
||||
AccountID: "",
|
||||
ClusterName: "",
|
||||
CloudReportURL: "",
|
||||
CloudAPIURL: "",
|
||||
},
|
||||
inCo: &ConfigObj{
|
||||
AccountID: shouldUpdate,
|
||||
Token: shouldUpdate,
|
||||
CustomerAdminEMail: shouldUpdate,
|
||||
ClusterName: shouldUpdate,
|
||||
CloudReportURL: shouldUpdate,
|
||||
CloudAPIURL: shouldUpdate,
|
||||
CloudUIURL: shouldUpdate,
|
||||
CloudAuthURL: shouldUpdate,
|
||||
AccountID: shouldUpdate,
|
||||
ClusterName: shouldUpdate,
|
||||
CloudReportURL: shouldUpdate,
|
||||
CloudAPIURL: shouldUpdate,
|
||||
},
|
||||
},
|
||||
{
|
||||
outCo: &ConfigObj{
|
||||
AccountID: anyString,
|
||||
Token: anyString,
|
||||
CustomerAdminEMail: "",
|
||||
ClusterName: "",
|
||||
CloudReportURL: "",
|
||||
CloudAPIURL: "",
|
||||
CloudUIURL: "",
|
||||
CloudAuthURL: "",
|
||||
AccountID: anyString,
|
||||
ClusterName: "",
|
||||
CloudReportURL: "",
|
||||
CloudAPIURL: "",
|
||||
},
|
||||
inCo: &ConfigObj{
|
||||
AccountID: shouldNotUpdate,
|
||||
Token: shouldNotUpdate,
|
||||
CustomerAdminEMail: shouldUpdate,
|
||||
ClusterName: shouldUpdate,
|
||||
CloudReportURL: shouldUpdate,
|
||||
CloudAPIURL: shouldUpdate,
|
||||
CloudUIURL: shouldUpdate,
|
||||
CloudAuthURL: shouldUpdate,
|
||||
AccountID: shouldNotUpdate,
|
||||
ClusterName: shouldUpdate,
|
||||
CloudReportURL: shouldUpdate,
|
||||
CloudAPIURL: shouldUpdate,
|
||||
},
|
||||
},
|
||||
{
|
||||
outCo: &ConfigObj{
|
||||
AccountID: "",
|
||||
Token: "",
|
||||
CustomerAdminEMail: anyString,
|
||||
ClusterName: anyString,
|
||||
CloudReportURL: anyString,
|
||||
CloudAPIURL: anyString,
|
||||
CloudUIURL: anyString,
|
||||
CloudAuthURL: anyString,
|
||||
AccountID: "",
|
||||
ClusterName: anyString,
|
||||
CloudReportURL: anyString,
|
||||
CloudAPIURL: anyString,
|
||||
},
|
||||
inCo: &ConfigObj{
|
||||
AccountID: shouldUpdate,
|
||||
Token: shouldUpdate,
|
||||
CustomerAdminEMail: shouldNotUpdate,
|
||||
ClusterName: shouldNotUpdate,
|
||||
CloudReportURL: shouldNotUpdate,
|
||||
CloudAPIURL: shouldNotUpdate,
|
||||
CloudUIURL: shouldNotUpdate,
|
||||
CloudAuthURL: shouldNotUpdate,
|
||||
AccountID: shouldUpdate,
|
||||
ClusterName: shouldNotUpdate,
|
||||
CloudReportURL: shouldNotUpdate,
|
||||
CloudAPIURL: shouldNotUpdate,
|
||||
},
|
||||
},
|
||||
{
|
||||
outCo: &ConfigObj{
|
||||
AccountID: anyString,
|
||||
Token: anyString,
|
||||
CustomerAdminEMail: "",
|
||||
ClusterName: anyString,
|
||||
CloudReportURL: "",
|
||||
CloudAPIURL: anyString,
|
||||
CloudUIURL: "",
|
||||
CloudAuthURL: anyString,
|
||||
AccountID: anyString,
|
||||
ClusterName: anyString,
|
||||
CloudReportURL: "",
|
||||
CloudAPIURL: anyString,
|
||||
},
|
||||
inCo: &ConfigObj{
|
||||
AccountID: shouldNotUpdate,
|
||||
Token: shouldNotUpdate,
|
||||
CustomerAdminEMail: shouldUpdate,
|
||||
ClusterName: shouldNotUpdate,
|
||||
CloudReportURL: shouldUpdate,
|
||||
CloudAPIURL: shouldNotUpdate,
|
||||
CloudUIURL: shouldUpdate,
|
||||
CloudAuthURL: shouldNotUpdate,
|
||||
AccountID: shouldNotUpdate,
|
||||
ClusterName: shouldNotUpdate,
|
||||
CloudReportURL: shouldUpdate,
|
||||
CloudAPIURL: shouldNotUpdate,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -447,11 +351,7 @@ func TestUpdateEmptyFields(t *testing.T) {
|
||||
tests[i].outCo.updateEmptyFields(tests[i].inCo)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.AccountID, tests[i].outCo.AccountID)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudAPIURL, tests[i].outCo.CloudAPIURL)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudAuthURL, tests[i].outCo.CloudAuthURL)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudReportURL, tests[i].outCo.CloudReportURL)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.CloudUIURL, tests[i].outCo.CloudUIURL)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.ClusterName, tests[i].outCo.ClusterName)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.CustomerAdminEMail, tests[i].outCo.CustomerAdminEMail)
|
||||
checkIsUpdateCorrectly(t, beforeChangesOutCO.Token, tests[i].outCo.Token)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ const (
|
||||
ScanTypeImage ScanTypes = "image"
|
||||
ScanTypeWorkload ScanTypes = "workload"
|
||||
ScanTypeFramework ScanTypes = "framework"
|
||||
ScanTypeControl ScanTypes = "control"
|
||||
)
|
||||
|
||||
type OPASessionObj struct {
|
||||
@@ -56,6 +57,7 @@ type OPASessionObj struct {
|
||||
Exceptions []armotypes.PostureExceptionPolicy // list of exceptions to apply on scan results
|
||||
OmitRawResources bool // omit raw resources from output
|
||||
SingleResourceScan workloadinterface.IWorkload // single resource scan
|
||||
TopWorkloadsByScore []reporthandling.IResource
|
||||
}
|
||||
|
||||
func NewOPASessionObj(ctx context.Context, frameworks []reporthandling.Framework, k8sResources K8SResources, scanInfo *ScanInfo) *OPASessionObj {
|
||||
@@ -109,7 +111,7 @@ func (sessionObj *OPASessionObj) SetTopWorkloads() {
|
||||
Source: &source,
|
||||
}
|
||||
|
||||
sessionObj.Report.SummaryDetails.TopWorkloadsByScore = append(sessionObj.Report.SummaryDetails.TopWorkloadsByScore, wlObj)
|
||||
sessionObj.TopWorkloadsByScore = append(sessionObj.TopWorkloadsByScore, wlObj)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/armosec/utils-go/boolutils"
|
||||
cloudsupport "github.com/kubescape/k8s-interface/cloudsupport/v1"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
)
|
||||
@@ -89,58 +88,50 @@ func isRuleKubescapeVersionCompatible(attributes map[string]interface{}, version
|
||||
return true
|
||||
}
|
||||
|
||||
func getCloudType(scanInfo *ScanInfo) (bool, reporthandling.ScanningScopeType) {
|
||||
func getCloudProvider(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
|
||||
if cloudsupport.IsAKS() {
|
||||
return true, reporthandling.ScopeCloudAKS
|
||||
return reporthandling.ScopeCloudAKS
|
||||
}
|
||||
if cloudsupport.IsEKS(k8sinterface.GetConfig()) {
|
||||
return true, reporthandling.ScopeCloudEKS
|
||||
if cloudsupport.IsEKS() {
|
||||
return reporthandling.ScopeCloudEKS
|
||||
}
|
||||
if cloudsupport.IsGKE(k8sinterface.GetConfig()) {
|
||||
return true, reporthandling.ScopeCloudGKE
|
||||
if cloudsupport.IsGKE() {
|
||||
return reporthandling.ScopeCloudGKE
|
||||
}
|
||||
return false, ""
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetScanningScope(scanInfo *ScanInfo) reporthandling.ScanningScopeType {
|
||||
var result reporthandling.ScanningScopeType
|
||||
|
||||
switch scanInfo.GetScanningContext() {
|
||||
case ContextCluster:
|
||||
isCloud, cloudType := getCloudType(scanInfo)
|
||||
if isCloud {
|
||||
result = cloudType
|
||||
} else {
|
||||
result = reporthandling.ScopeCluster
|
||||
if cloudProvider := getCloudProvider(scanInfo); cloudProvider != "" {
|
||||
return cloudProvider
|
||||
}
|
||||
return reporthandling.ScopeCluster
|
||||
default:
|
||||
result = reporthandling.ScopeFile
|
||||
return reporthandling.ScopeFile
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func isScanningScopeMatchToControlScope(scanScope reporthandling.ScanningScopeType, controlScope reporthandling.ScanningScopeType) bool {
|
||||
result := false
|
||||
|
||||
switch controlScope {
|
||||
case reporthandling.ScopeFile:
|
||||
result = (reporthandling.ScopeFile == scanScope)
|
||||
return reporthandling.ScopeFile == scanScope
|
||||
case reporthandling.ScopeCluster:
|
||||
result = (reporthandling.ScopeCluster == scanScope) || (reporthandling.ScopeCloud == scanScope) || (reporthandling.ScopeCloudAKS == scanScope) || (reporthandling.ScopeCloudEKS == scanScope) || (reporthandling.ScopeCloudGKE == scanScope)
|
||||
return reporthandling.ScopeCluster == scanScope || reporthandling.ScopeCloud == scanScope || reporthandling.ScopeCloudAKS == scanScope || reporthandling.ScopeCloudEKS == scanScope || reporthandling.ScopeCloudGKE == scanScope
|
||||
case reporthandling.ScopeCloud:
|
||||
result = (reporthandling.ScopeCloud == scanScope) || (reporthandling.ScopeCloudAKS == scanScope) || (reporthandling.ScopeCloudEKS == scanScope) || (reporthandling.ScopeCloudGKE == scanScope)
|
||||
return reporthandling.ScopeCloud == scanScope || reporthandling.ScopeCloudAKS == scanScope || reporthandling.ScopeCloudEKS == scanScope || reporthandling.ScopeCloudGKE == scanScope
|
||||
case reporthandling.ScopeCloudAKS:
|
||||
result = (reporthandling.ScopeCloudAKS == scanScope)
|
||||
return reporthandling.ScopeCloudAKS == scanScope
|
||||
case reporthandling.ScopeCloudEKS:
|
||||
result = (reporthandling.ScopeCloudEKS == scanScope)
|
||||
return reporthandling.ScopeCloudEKS == scanScope
|
||||
case reporthandling.ScopeCloudGKE:
|
||||
result = (reporthandling.ScopeCloudGKE == scanScope)
|
||||
return reporthandling.ScopeCloudGKE == scanScope
|
||||
default:
|
||||
result = true
|
||||
return true
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func isControlFitToScanScope(control reporthandling.Control, scanScopeMatches reporthandling.ScanningScopeType) bool {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -79,7 +79,6 @@ func TestIsControlFitToScanScope(t *testing.T) {
|
||||
scanInfo: &ScanInfo{},
|
||||
Control: reporthandling.Control{
|
||||
ScanningScope: &reporthandling.ScanningScope{
|
||||
|
||||
Matches: []reporthandling.ScanningScopeType{
|
||||
reporthandling.ScopeCloudEKS,
|
||||
},
|
||||
@@ -99,6 +98,237 @@ func TestIsControlFitToScanScope(t *testing.T) {
|
||||
expected_res: false,
|
||||
}}
|
||||
for i := range tests {
|
||||
assert.Equal(t, isControlFitToScanScope(tests[i].Control, GetScanningScope(tests[i].scanInfo)), tests[i].expected_res, fmt.Sprintf("tests_true index %d", i))
|
||||
assert.Equal(t, tests[i].expected_res, isControlFitToScanScope(tests[i].Control, GetScanningScope(tests[i].scanInfo)), fmt.Sprintf("tests_true index %d", i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsScanningScopeMatchToControlScope(t *testing.T) {
|
||||
tests := []struct {
|
||||
scanScope reporthandling.ScanningScopeType
|
||||
controlScope reporthandling.ScanningScopeType
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
scanScope: reporthandling.ScopeFile,
|
||||
controlScope: reporthandling.ScopeFile,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: ScopeCluster,
|
||||
controlScope: ScopeCluster,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloud,
|
||||
controlScope: reporthandling.ScopeCloud,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudAKS,
|
||||
controlScope: reporthandling.ScopeCloudAKS,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudEKS,
|
||||
controlScope: reporthandling.ScopeCloudEKS,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudGKE,
|
||||
controlScope: reporthandling.ScopeCloudGKE,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: ScopeCluster,
|
||||
controlScope: reporthandling.ScopeCloud,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloud,
|
||||
controlScope: ScopeCluster,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudAKS,
|
||||
controlScope: ScopeCluster,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudEKS,
|
||||
controlScope: ScopeCluster,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudGKE,
|
||||
controlScope: ScopeCluster,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloud,
|
||||
controlScope: reporthandling.ScopeCloudAKS,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudAKS,
|
||||
controlScope: reporthandling.ScopeCloud,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudEKS,
|
||||
controlScope: reporthandling.ScopeCloud,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudGKE,
|
||||
controlScope: reporthandling.ScopeCloud,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
scanScope: ScopeCluster,
|
||||
controlScope: reporthandling.ScopeCloudAKS,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: ScopeCluster,
|
||||
controlScope: reporthandling.ScopeCloudEKS,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: ScopeCluster,
|
||||
controlScope: reporthandling.ScopeCloudGKE,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeFile,
|
||||
controlScope: ScopeCluster,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeFile,
|
||||
controlScope: reporthandling.ScopeCloud,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeFile,
|
||||
controlScope: reporthandling.ScopeCloudAKS,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeFile,
|
||||
controlScope: reporthandling.ScopeCloudEKS,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeFile,
|
||||
controlScope: reporthandling.ScopeCloudGKE,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloud,
|
||||
controlScope: reporthandling.ScopeCloudEKS,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloud,
|
||||
controlScope: reporthandling.ScopeCloudGKE,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudAKS,
|
||||
controlScope: reporthandling.ScopeCloudEKS,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudAKS,
|
||||
controlScope: reporthandling.ScopeCloudGKE,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudEKS,
|
||||
controlScope: reporthandling.ScopeCloudAKS,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudEKS,
|
||||
controlScope: reporthandling.ScopeCloudGKE,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudGKE,
|
||||
controlScope: reporthandling.ScopeCloudAKS,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
scanScope: reporthandling.ScopeCloudGKE,
|
||||
controlScope: reporthandling.ScopeCloudEKS,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := isScanningScopeMatchToControlScope(test.scanScope, test.controlScope)
|
||||
assert.Equal(t, test.expected, result, fmt.Sprintf("scanScope: %v, controlScope: %v", test.scanScope, test.controlScope))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFrameworkFitToScanScope(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
framework reporthandling.Framework
|
||||
scanScopeMatch reporthandling.ScanningScopeType
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Framework with nil ScanningScope should return true",
|
||||
framework: reporthandling.Framework{
|
||||
PortalBase: armotypes.PortalBase{
|
||||
Name: "test-framework",
|
||||
},
|
||||
},
|
||||
scanScopeMatch: reporthandling.ScopeFile,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Framework with empty ScanningScope.Matches should return true",
|
||||
framework: reporthandling.Framework{
|
||||
PortalBase: armotypes.PortalBase{
|
||||
Name: "test-framework",
|
||||
}, ScanningScope: &reporthandling.ScanningScope{},
|
||||
},
|
||||
scanScopeMatch: reporthandling.ScopeFile,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Framework with matching ScanningScope.Matches should return true",
|
||||
framework: reporthandling.Framework{
|
||||
PortalBase: armotypes.PortalBase{
|
||||
Name: "test-framework",
|
||||
}, ScanningScope: &reporthandling.ScanningScope{
|
||||
Matches: []reporthandling.ScanningScopeType{reporthandling.ScopeFile},
|
||||
},
|
||||
},
|
||||
scanScopeMatch: reporthandling.ScopeFile,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Framework with non-matching ScanningScope.Matches should return false",
|
||||
framework: reporthandling.Framework{
|
||||
PortalBase: armotypes.PortalBase{
|
||||
Name: "test-framework",
|
||||
}, ScanningScope: &reporthandling.ScanningScope{
|
||||
Matches: []reporthandling.ScanningScopeType{reporthandling.ScopeCluster},
|
||||
},
|
||||
},
|
||||
scanScopeMatch: reporthandling.ScopeFile,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isFrameworkFitToScanScope(tt.framework, tt.scanScopeMatch); got != tt.want {
|
||||
t.Errorf("isFrameworkFitToScanScope() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package cautils
|
||||
|
||||
// Kubescape Cloud environment vars
|
||||
var (
|
||||
CustomerGUID = ""
|
||||
ClusterName = ""
|
||||
)
|
||||
@@ -1,61 +1,4 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
)
|
||||
|
||||
// NativeFrameworks identifies all pre-built, native frameworks.
|
||||
var NativeFrameworks = []string{"allcontrols", "nsa", "mitre"}
|
||||
|
||||
type (
|
||||
// TenantResponse holds the credentials for a tenant.
|
||||
TenantResponse struct {
|
||||
TenantID string `json:"tenantId"`
|
||||
Token string `json:"token"`
|
||||
Expires string `json:"expires"`
|
||||
AdminMail string `json:"adminMail,omitempty"`
|
||||
}
|
||||
|
||||
// AttackTrack is an alias to the API type definition for attack tracks.
|
||||
AttackTrack = v1alpha1.AttackTrack
|
||||
|
||||
// Framework is an alias to the API type definition for a framework.
|
||||
Framework = reporthandling.Framework
|
||||
|
||||
// Control is an alias to the API type definition for a control.
|
||||
Control = reporthandling.Control
|
||||
|
||||
// PostureExceptionPolicy is an alias to the API type definition for posture exception policy.
|
||||
PostureExceptionPolicy = armotypes.PostureExceptionPolicy
|
||||
|
||||
// CustomerConfig is an alias to the API type definition for a customer configuration.
|
||||
CustomerConfig = armotypes.CustomerConfig
|
||||
|
||||
// PostureReport is an alias to the API type definition for a posture report.
|
||||
PostureReport = reporthandlingv2.PostureReport
|
||||
)
|
||||
|
||||
type (
|
||||
// internal data descriptors
|
||||
|
||||
// feLoginData describes the input to a login challenge.
|
||||
feLoginData struct {
|
||||
Secret string `json:"secret"`
|
||||
ClientId string `json:"clientId"`
|
||||
}
|
||||
|
||||
// feLoginResponse describes the response to a login challenge.
|
||||
feLoginResponse struct {
|
||||
Token string `json:"accessToken"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
Expires string `json:"expires"`
|
||||
ExpiresIn int32 `json:"expiresIn"`
|
||||
}
|
||||
|
||||
ksCloudSelectCustomer struct {
|
||||
SelectedCustomerGuid string `json:"selectedCustomer"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
|
||||
|
||||
@@ -9,11 +9,19 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/internal/testutils"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/kubescape/kubescape/v2/internal/testutils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func min(a, b int64) int64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func TestReleasedPolicy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
containeranalysis "cloud.google.com/go/containeranalysis/apiv1"
|
||||
)
|
||||
|
||||
type GCPCloudAPI struct {
|
||||
credentialsPath string
|
||||
context context.Context
|
||||
client *containeranalysis.Client
|
||||
projectID string
|
||||
credentialsCheck bool
|
||||
}
|
||||
|
||||
func GetGlobalGCPCloudAPIConnector() *GCPCloudAPI {
|
||||
|
||||
if os.Getenv("KS_GCP_CREDENTIALS_PATH") == "" || os.Getenv("KS_GCP_PROJECT_ID") == "" {
|
||||
return &GCPCloudAPI{
|
||||
credentialsCheck: false,
|
||||
}
|
||||
} else {
|
||||
return &GCPCloudAPI{
|
||||
context: context.Background(),
|
||||
credentialsPath: os.Getenv("KS_GCP_CREDENTIALS_PATH"),
|
||||
projectID: os.Getenv("KS_GCP_PROJECT_ID"),
|
||||
credentialsCheck: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *GCPCloudAPI) SetClient(client *containeranalysis.Client) {
|
||||
api.client = client
|
||||
}
|
||||
|
||||
func (api *GCPCloudAPI) GetCredentialsPath() string { return api.credentialsPath }
|
||||
func (api *GCPCloudAPI) GetClient() *containeranalysis.Client { return api.client }
|
||||
func (api *GCPCloudAPI) GetProjectID() string { return api.projectID }
|
||||
func (api *GCPCloudAPI) GetCredentialsCheck() bool { return api.credentialsCheck }
|
||||
func (api *GCPCloudAPI) GetContext() context.Context { return api.context }
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
beClient "github.com/kubescape/backend/pkg/client/v1"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -72,7 +73,7 @@ func TestHttpMethods(t *testing.T) {
|
||||
client := http.DefaultClient
|
||||
hdrs := map[string]string{"key": "value"}
|
||||
|
||||
srv := mockAPIServer(t)
|
||||
srv := beClient.MockAPIServer(t)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
t.Run("HttpGetter should GET", func(t *testing.T) {
|
||||
|
||||
@@ -31,25 +31,4 @@ type (
|
||||
IAttackTracksGetter interface {
|
||||
GetAttackTracks() ([]v1alpha1.AttackTrack, error)
|
||||
}
|
||||
|
||||
// IBackend knows how to configure a KS Cloud client
|
||||
IBackend interface {
|
||||
GetAccountID() string
|
||||
GetClientID() string
|
||||
GetSecretKey() string
|
||||
GetCloudReportURL() string
|
||||
GetCloudAPIURL() string
|
||||
GetCloudUIURL() string
|
||||
GetCloudAuthURL() string
|
||||
|
||||
SetAccountID(accountID string)
|
||||
SetClientID(clientID string)
|
||||
SetSecretKey(secretKey string)
|
||||
SetCloudReportURL(cloudReportURL string)
|
||||
SetCloudAPIURL(cloudAPIURL string)
|
||||
SetCloudUIURL(cloudUIURL string)
|
||||
SetCloudAuthURL(cloudAuthURL string)
|
||||
|
||||
GetTenant() (*TenantResponse, error)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,833 +1,49 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// Kubescape API endpoints
|
||||
|
||||
// production
|
||||
ksCloudERURL = "report.armo.cloud" // API reports URL
|
||||
ksCloudBEURL = "api.armosec.io" // API backend URL
|
||||
ksCloudFEURL = "cloud.armosec.io" // API frontend (UI) URIL
|
||||
ksCloudAUTHURL = "auth.armosec.io" // API login URL
|
||||
|
||||
// staging
|
||||
ksCloudStageERURL = "report-ks.eustage2.cyberarmorsoft.com"
|
||||
ksCloudStageBEURL = "api-stage.armosec.io"
|
||||
ksCloudStageFEURL = "armoui-stage.armosec.io"
|
||||
ksCloudStageAUTHURL = "eggauth-stage.armosec.io"
|
||||
|
||||
// dev
|
||||
ksCloudDevERURL = "report.eudev3.cyberarmorsoft.com"
|
||||
ksCloudDevBEURL = "api-dev.armosec.io"
|
||||
ksCloudDevFEURL = "cloud-dev.armosec.io"
|
||||
ksCloudDevAUTHURL = "eggauth-dev.armosec.io"
|
||||
|
||||
// Kubescape API routes
|
||||
pathAttackTracks = "/api/v1/attackTracks"
|
||||
pathFrameworks = "/api/v1/armoFrameworks"
|
||||
pathExceptions = "/api/v1/armoPostureExceptions"
|
||||
pathTenant = "/api/v1/tenants/createTenant"
|
||||
pathExceptionPolicy = "/api/v1/postureExceptionPolicy"
|
||||
pathCustomerConfig = "/api/v1/armoCustomerConfiguration"
|
||||
pathLogin = "/identity/resources/auth/v1/api-token"
|
||||
pathToken = "/api/v1/openid_customers" //nolint:gosec
|
||||
|
||||
// reports upload route
|
||||
pathReport = "/k8s/v2/postureReport"
|
||||
|
||||
// Kubescape UI routes
|
||||
pathUIScan = "/compliance/%s"
|
||||
pathUIRBAC = "/rbac-visualizer"
|
||||
pathUIRepository = "/repository-scanning/%s"
|
||||
pathUIDashboard = "/dashboard/"
|
||||
pathUISign = "/account/sign-up"
|
||||
)
|
||||
|
||||
const (
|
||||
// default dummy GUID when not defined
|
||||
fallbackGUID = "11111111-1111-1111-1111-111111111111"
|
||||
|
||||
// URL query parameters
|
||||
queryParamGUID = "customerGUID"
|
||||
queryParamScope = "scope"
|
||||
queryParamFrameworkName = "frameworkName"
|
||||
queryParamPolicyName = "policyName"
|
||||
queryParamClusterName = "clusterName"
|
||||
queryParamContextName = "contextName"
|
||||
|
||||
queryParamUTMSource = "utm_source"
|
||||
queryParamUTMMedium = "utm_medium"
|
||||
// queryParamUTMCampaign = "utm_campaign"
|
||||
queryParamReport = "reportGUID"
|
||||
queryParamInvitationToken = "invitationToken"
|
||||
|
||||
authenticationCookie = "auth"
|
||||
)
|
||||
|
||||
var (
|
||||
// Errors returned by the API
|
||||
|
||||
ErrLoginMissingAccountID = errors.New("failed to login, missing accountID")
|
||||
ErrLoginMissingClientID = errors.New("failed to login, missing clientID")
|
||||
ErrLoginMissingSecretKey = errors.New("failed to login, missing secretKey")
|
||||
ErrAPINotPublic = errors.New("control api is not public")
|
||||
v1 "github.com/kubescape/backend/pkg/client/v1"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
)
|
||||
|
||||
var (
|
||||
// globalKSCloudAPIConnector is a static global instance of the KS Cloud client,
|
||||
// to be initialized with SetKSCloudAPIConnector.
|
||||
globalKSCloudAPIConnector *KSCloudAPI
|
||||
globalKSCloudAPIConnector *v1.KSCloudAPI
|
||||
|
||||
_ IPolicyGetter = &KSCloudAPI{}
|
||||
_ IExceptionsGetter = &KSCloudAPI{}
|
||||
_ IAttackTracksGetter = &KSCloudAPI{}
|
||||
_ IControlsInputsGetter = &KSCloudAPI{}
|
||||
_ IPolicyGetter = &v1.KSCloudAPI{}
|
||||
_ IExceptionsGetter = &v1.KSCloudAPI{}
|
||||
_ IAttackTracksGetter = &v1.KSCloudAPI{}
|
||||
_ IControlsInputsGetter = &v1.KSCloudAPI{}
|
||||
)
|
||||
|
||||
// KSCloudAPI allows to access the API of the Kubescape Cloud offering.
|
||||
type KSCloudAPI struct {
|
||||
authCookie *http.Cookie
|
||||
*ksCloudOptions
|
||||
authhost string
|
||||
cloudAPIURL string
|
||||
secretKey string
|
||||
accountID string
|
||||
cloudAuthURL string
|
||||
invitationToken string
|
||||
reporthost string
|
||||
scheme string
|
||||
host string
|
||||
authscheme string
|
||||
clientID string
|
||||
uischeme string
|
||||
uihost string
|
||||
reportscheme string
|
||||
feToken feLoginResponse
|
||||
loggedIn bool
|
||||
}
|
||||
|
||||
// SetKSCloudAPIConnector registers a global instance of the KS Cloud client.
|
||||
//
|
||||
// NOTE: cannot be used concurrently.
|
||||
func SetKSCloudAPIConnector(ksCloudAPI *KSCloudAPI) {
|
||||
func SetKSCloudAPIConnector(ksCloudAPI *v1.KSCloudAPI) {
|
||||
if ksCloudAPI != nil {
|
||||
logger.L().Debug("setting global KS Cloud API connector",
|
||||
helpers.String("accountID", ksCloudAPI.GetAccountID()),
|
||||
helpers.String("cloudAPIURL", ksCloudAPI.GetCloudAPIURL()),
|
||||
helpers.String("cloudReportURL", ksCloudAPI.GetCloudReportURL()))
|
||||
} else {
|
||||
logger.L().Debug("setting global KS Cloud API connector (nil)")
|
||||
}
|
||||
globalKSCloudAPIConnector = ksCloudAPI
|
||||
}
|
||||
|
||||
// GetKSCloudAPIConnector returns a shallow clone of the KS Cloud client registered for this package.
|
||||
//
|
||||
// NOTE: cannot be used concurrently with SetKSCloudAPIConnector.
|
||||
func GetKSCloudAPIConnector() *KSCloudAPI {
|
||||
func GetKSCloudAPIConnector() *v1.KSCloudAPI {
|
||||
if globalKSCloudAPIConnector == nil {
|
||||
SetKSCloudAPIConnector(NewKSCloudAPIProd())
|
||||
SetKSCloudAPIConnector(v1.NewEmptyKSCloudAPI())
|
||||
}
|
||||
|
||||
// we return a shallow clone that may be freely modified by the caller.
|
||||
client := *globalKSCloudAPIConnector
|
||||
options := *globalKSCloudAPIConnector.ksCloudOptions
|
||||
client.ksCloudOptions = &options
|
||||
options := *globalKSCloudAPIConnector.KsCloudOptions
|
||||
client.KsCloudOptions = &options
|
||||
|
||||
return &client
|
||||
}
|
||||
|
||||
// NewKSCloudAPIDev returns a KS Cloud client pointing to a development environment.
|
||||
func NewKSCloudAPIDev(opts ...KSCloudOption) *KSCloudAPI {
|
||||
devOpts := []KSCloudOption{
|
||||
WithFrontendURL(ksCloudDevFEURL),
|
||||
WithReportURL(ksCloudDevERURL),
|
||||
}
|
||||
devOpts = append(devOpts, opts...)
|
||||
|
||||
apiObj := newKSCloudAPI(
|
||||
ksCloudDevBEURL,
|
||||
ksCloudDevAUTHURL,
|
||||
devOpts...,
|
||||
)
|
||||
|
||||
return apiObj
|
||||
}
|
||||
|
||||
// NewKSCloudAPIDProd returns a KS Cloud client pointing to a production environment.
|
||||
func NewKSCloudAPIProd(opts ...KSCloudOption) *KSCloudAPI {
|
||||
prodOpts := []KSCloudOption{
|
||||
WithFrontendURL(ksCloudFEURL),
|
||||
WithReportURL(ksCloudERURL),
|
||||
}
|
||||
prodOpts = append(prodOpts, opts...)
|
||||
|
||||
return newKSCloudAPI(
|
||||
ksCloudBEURL,
|
||||
ksCloudAUTHURL,
|
||||
prodOpts...,
|
||||
)
|
||||
}
|
||||
|
||||
// NewKSCloudAPIStaging returns a KS Cloud client pointing to a testing environment.
|
||||
func NewKSCloudAPIStaging(opts ...KSCloudOption) *KSCloudAPI {
|
||||
stagingOpts := []KSCloudOption{
|
||||
WithFrontendURL(ksCloudStageFEURL),
|
||||
WithReportURL(ksCloudStageERURL),
|
||||
}
|
||||
stagingOpts = append(stagingOpts, opts...)
|
||||
|
||||
return newKSCloudAPI(
|
||||
ksCloudStageBEURL,
|
||||
ksCloudStageAUTHURL,
|
||||
stagingOpts...,
|
||||
)
|
||||
}
|
||||
|
||||
// NewKSCloudAPICustomed returns a KS Cloud client with configurable API and authentication endpoints.
|
||||
func NewKSCloudAPICustomized(ksCloudAPIURL, ksCloudAuthURL string, opts ...KSCloudOption) *KSCloudAPI {
|
||||
return newKSCloudAPI(
|
||||
ksCloudAPIURL,
|
||||
ksCloudAuthURL,
|
||||
opts...,
|
||||
)
|
||||
}
|
||||
|
||||
func newKSCloudAPI(apiURL, authURL string, opts ...KSCloudOption) *KSCloudAPI {
|
||||
api := &KSCloudAPI{
|
||||
cloudAPIURL: apiURL,
|
||||
cloudAuthURL: authURL,
|
||||
ksCloudOptions: ksCloudOptionsWithDefaults(opts),
|
||||
}
|
||||
|
||||
api.SetCloudAPIURL(apiURL)
|
||||
api.SetCloudAuthURL(authURL)
|
||||
api.SetCloudUIURL(api.cloudUIURL)
|
||||
api.SetCloudReportURL(api.cloudReportURL)
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
// Get retrieves an API resource.
|
||||
//
|
||||
// The response is serialized as a string.
|
||||
//
|
||||
// The caller may specify extra headers.
|
||||
//
|
||||
// By default, all authentication headers are added.
|
||||
func (api *KSCloudAPI) Get(fullURL string, headers map[string]string) (string, error) {
|
||||
rdr, size, err := api.get(fullURL, withExtraHeaders(headers))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
return readString(rdr, size)
|
||||
}
|
||||
|
||||
// Post creates an API resource.
|
||||
//
|
||||
// The response is serialized as a string.
|
||||
//
|
||||
// The caller may specify extra headers.
|
||||
//
|
||||
// By default, the body content type is set to JSON and all authentication headers are added.
|
||||
func (api *KSCloudAPI) Post(fullURL string, headers map[string]string, body []byte) (string, error) {
|
||||
rdr, size, err := api.post(fullURL, body, withContentJSON(true), withExtraHeaders(headers))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
return readString(rdr, size)
|
||||
}
|
||||
|
||||
// Delete an API resource.
|
||||
//
|
||||
// The response is serialized as a string.
|
||||
//
|
||||
// The caller may specify extra headers.
|
||||
//
|
||||
// By default, all authentication headers are added.
|
||||
func (api *KSCloudAPI) Delete(fullURL string, headers map[string]string) (string, error) {
|
||||
rdr, size, err := api.delete(fullURL, withExtraHeaders(headers))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
return readString(rdr, size)
|
||||
}
|
||||
|
||||
// GetAccountID returns the customer account's GUID.
|
||||
func (api *KSCloudAPI) GetAccountID() string { return api.accountID }
|
||||
|
||||
// IsLoggedIn indicates if the client has sucessfully authenticated.
|
||||
func (api *KSCloudAPI) IsLoggedIn() bool { return api.loggedIn }
|
||||
|
||||
func (api *KSCloudAPI) GetClientID() string { return api.clientID }
|
||||
func (api *KSCloudAPI) GetSecretKey() string { return api.secretKey }
|
||||
func (api *KSCloudAPI) GetCloudReportURL() string { return api.cloudReportURL }
|
||||
func (api *KSCloudAPI) GetCloudAPIURL() string { return api.cloudAPIURL }
|
||||
func (api *KSCloudAPI) GetCloudUIURL() string { return api.cloudUIURL }
|
||||
func (api *KSCloudAPI) GetCloudAuthURL() string { return api.cloudAuthURL }
|
||||
func (api *KSCloudAPI) GetInvitationToken() string { return api.invitationToken }
|
||||
|
||||
func (api *KSCloudAPI) SetAccountID(accountID string) { api.accountID = accountID }
|
||||
func (api *KSCloudAPI) SetClientID(clientID string) { api.clientID = clientID }
|
||||
func (api *KSCloudAPI) SetSecretKey(secretKey string) { api.secretKey = secretKey }
|
||||
func (api *KSCloudAPI) SetInvitationToken(token string) { api.invitationToken = token }
|
||||
|
||||
func (api *KSCloudAPI) SetCloudAPIURL(cloudAPIURL string) {
|
||||
api.cloudAPIURL = cloudAPIURL
|
||||
api.scheme, api.host = parseHost(cloudAPIURL)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) SetCloudUIURL(cloudUIURL string) {
|
||||
api.cloudUIURL = cloudUIURL
|
||||
api.uischeme, api.uihost = parseHost(cloudUIURL)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) SetCloudAuthURL(cloudAuthURL string) {
|
||||
api.cloudAuthURL = cloudAuthURL
|
||||
api.authscheme, api.authhost = parseHost(cloudAuthURL)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) SetCloudReportURL(cloudReportURL string) {
|
||||
api.cloudReportURL = cloudReportURL
|
||||
api.reportscheme, api.reporthost = parseHost(cloudReportURL)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) GetAttackTracks() ([]AttackTrack, error) {
|
||||
rdr, _, err := api.get(api.getAttackTracksURL())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
attackTracks, err := decode[[]AttackTrack](rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attackTracks, nil
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getAttackTracksURL() string {
|
||||
return api.buildAPIURL(
|
||||
pathAttackTracks,
|
||||
api.paramsWithGUID()...,
|
||||
)
|
||||
}
|
||||
|
||||
// GetFramework retrieves a framework by name.
|
||||
func (api *KSCloudAPI) GetFramework(frameworkName string) (*Framework, error) {
|
||||
rdr, _, err := api.get(api.getFrameworkURL(frameworkName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
framework, err := decode[Framework](rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &framework, err
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string {
|
||||
if isNativeFramework(frameworkName) {
|
||||
// Native framework name is normalized as upper case, but for a custom framework the name remains unaltered
|
||||
frameworkName = strings.ToUpper(frameworkName)
|
||||
}
|
||||
|
||||
return api.buildAPIURL(
|
||||
pathFrameworks,
|
||||
append(
|
||||
api.paramsWithGUID(),
|
||||
queryParamFrameworkName, frameworkName,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
// GetFrameworks returns all registered frameworks.
|
||||
func (api *KSCloudAPI) GetFrameworks() ([]Framework, error) {
|
||||
rdr, _, err := api.get(api.getListFrameworkURL())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
frameworks, err := decode[[]Framework](rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return frameworks, err
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getListFrameworkURL() string {
|
||||
return api.buildAPIURL(
|
||||
pathFrameworks,
|
||||
api.paramsWithGUID()...,
|
||||
)
|
||||
}
|
||||
|
||||
// ListCustomFrameworks lists the names of all non-native frameworks that have been registered for this account.
|
||||
func (api *KSCloudAPI) ListCustomFrameworks() ([]string, error) {
|
||||
frameworks, err := api.GetFrameworks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
frameworkList := make([]string, 0, len(frameworks))
|
||||
for _, framework := range frameworks {
|
||||
if isNativeFramework(framework.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
frameworkList = append(frameworkList, framework.Name)
|
||||
}
|
||||
|
||||
return frameworkList, nil
|
||||
}
|
||||
|
||||
// ListFrameworks list the names of all registered frameworks.
|
||||
func (api *KSCloudAPI) ListFrameworks() ([]string, error) {
|
||||
frameworks, err := api.GetFrameworks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
frameworkList := make([]string, 0, len(frameworks))
|
||||
for _, framework := range frameworks {
|
||||
name := framework.Name
|
||||
if isNativeFramework(framework.Name) {
|
||||
name = strings.ToLower(framework.Name)
|
||||
}
|
||||
|
||||
frameworkList = append(frameworkList, name)
|
||||
}
|
||||
|
||||
return frameworkList, nil
|
||||
}
|
||||
|
||||
// GetExceptions returns exception policies.
|
||||
func (api *KSCloudAPI) GetExceptions(clusterName string) ([]PostureExceptionPolicy, error) {
|
||||
rdr, _, err := api.get(api.getExceptionsURL(clusterName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
exceptions, err := decode[[]PostureExceptionPolicy](rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exceptions, nil
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getExceptionsURL(clusterName string) string {
|
||||
return api.buildAPIURL(
|
||||
pathExceptions,
|
||||
api.paramsWithGUID()...,
|
||||
)
|
||||
// queryParamClusterName, clusterName, // TODO - fix customer name support in Armo BE
|
||||
}
|
||||
|
||||
// GetTenant retrieves the credentials for the calling tenant.
|
||||
//
|
||||
// The tenant ID overides any already provided account ID.
|
||||
func (api *KSCloudAPI) GetTenant() (*TenantResponse, error) {
|
||||
rdr, _, err := api.get(api.getTenantURL())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
tenant, err := decode[TenantResponse](rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tenant.TenantID != "" {
|
||||
api.accountID = tenant.TenantID
|
||||
}
|
||||
|
||||
return &tenant, nil
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getTenantURL() string {
|
||||
var params []string
|
||||
if api.accountID != "" {
|
||||
params = []string{
|
||||
queryParamGUID, api.accountID, // NOTE: no fallback in this case
|
||||
}
|
||||
}
|
||||
|
||||
return api.buildAPIURL(
|
||||
pathTenant,
|
||||
params...,
|
||||
)
|
||||
}
|
||||
|
||||
// GetAccountConfig yields the account configuration.
|
||||
func (api *KSCloudAPI) GetAccountConfig(clusterName string) (*CustomerConfig, error) {
|
||||
if api.accountID == "" {
|
||||
return &CustomerConfig{}, nil
|
||||
}
|
||||
|
||||
rdr, _, err := api.get(api.getAccountConfig(clusterName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
accountConfig, err := decode[CustomerConfig](rdr)
|
||||
if err != nil {
|
||||
// retry with default scope
|
||||
rdr, _, err = api.get(api.getAccountConfigDefault(clusterName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
accountConfig, err = decode[CustomerConfig](rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &accountConfig, nil
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getAccountConfig(clusterName string) string {
|
||||
params := api.paramsWithGUID()
|
||||
|
||||
if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
params = append(params, queryParamClusterName, clusterName)
|
||||
}
|
||||
|
||||
return api.buildAPIURL(
|
||||
pathCustomerConfig,
|
||||
params...,
|
||||
)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getAccountConfigDefault(clusterName string) string {
|
||||
params := append(
|
||||
api.paramsWithGUID(),
|
||||
queryParamScope, "customer",
|
||||
)
|
||||
|
||||
if clusterName != "" { // TODO - fix customer name support in Armo BE
|
||||
params = append(params, queryParamClusterName, clusterName)
|
||||
}
|
||||
|
||||
return api.buildAPIURL(
|
||||
pathCustomerConfig,
|
||||
params...,
|
||||
)
|
||||
}
|
||||
|
||||
// GetControlsInputs returns the controls inputs configured in the account configuration.
|
||||
func (api *KSCloudAPI) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
||||
accountConfig, err := api.GetAccountConfig(clusterName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return accountConfig.Settings.PostureControlInputs, nil
|
||||
}
|
||||
|
||||
// GetControl is currently not exposed as a public API endpoint.
|
||||
func (api *KSCloudAPI) GetControl(ID string) (*Control, error) {
|
||||
return nil, ErrAPINotPublic
|
||||
}
|
||||
|
||||
// ListControls is currently not exposed as a public API endpoint.
|
||||
func (api *KSCloudAPI) ListControls() ([]string, error) {
|
||||
return nil, ErrAPINotPublic
|
||||
}
|
||||
|
||||
// PostExceptions registers a list of exceptions.
|
||||
func (api *KSCloudAPI) PostExceptions(exceptions []PostureExceptionPolicy) error {
|
||||
target := api.exceptionsURL("")
|
||||
|
||||
for i := range exceptions {
|
||||
jazon, err := json.Marshal(exceptions[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = api.post(target, jazon, withContentJSON(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete exception removes a registered exception rule.
|
||||
func (api *KSCloudAPI) DeleteException(exceptionName string) error {
|
||||
_, _, err := api.delete(api.exceptionsURL(exceptionName))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) exceptionsURL(exceptionsPolicyName string) string {
|
||||
params := api.paramsWithGUID()
|
||||
if exceptionsPolicyName != "" { // for delete
|
||||
params = append(params, queryParamPolicyName, exceptionsPolicyName)
|
||||
}
|
||||
|
||||
return api.buildAPIURL(
|
||||
pathExceptionPolicy,
|
||||
params...,
|
||||
)
|
||||
}
|
||||
|
||||
// SubmitReport uploads a posture report.
|
||||
func (api *KSCloudAPI) SubmitReport(report *PostureReport) error {
|
||||
jazon, err := json.Marshal(report)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = api.post(api.postReportURL(report.ClusterName, report.ReportID), jazon, withContentJSON(true), withToken(api.invitationToken))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) postReportURL(cluster, reportID string) string {
|
||||
return api.buildReportURL(pathReport,
|
||||
append(
|
||||
api.paramsWithGUID(),
|
||||
queryParamContextName, cluster,
|
||||
queryParamClusterName, cluster, // deprecated
|
||||
queryParamReport, reportID,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
// ViewReportURL yields the frontend URL to view a posture report (e.g. from a repository scan).
|
||||
func (api *KSCloudAPI) ViewReportURL(reportID string) string {
|
||||
return api.buildUIURL(
|
||||
fmt.Sprintf(pathUIRepository, reportID),
|
||||
)
|
||||
}
|
||||
|
||||
// ViewDashboardURL yields the frontend URL for the dashboard.
|
||||
func (api *KSCloudAPI) ViewDashboardURL() string {
|
||||
return api.buildUIURL(
|
||||
pathUIDashboard,
|
||||
)
|
||||
}
|
||||
|
||||
// ViewRBACURL yields the frontend URL to visualize RBAC.
|
||||
func (api *KSCloudAPI) ViewRBACURL() string {
|
||||
return api.buildUIURL(
|
||||
pathUIRBAC,
|
||||
)
|
||||
}
|
||||
|
||||
// ViewRBACURL yields the frontend URL to check the compliance of a scanned cluster.
|
||||
func (api *KSCloudAPI) ViewScanURL(cluster string) string {
|
||||
return api.buildUIURL(
|
||||
fmt.Sprintf(pathUIScan, cluster),
|
||||
)
|
||||
}
|
||||
|
||||
// ViewSignURL yields the frontend login page.
|
||||
func (api *KSCloudAPI) ViewSignURL() string {
|
||||
params := api.paramsWithGUID()
|
||||
params = append(params, api.paramsWithUTM()...)
|
||||
params = append(params, queryParamInvitationToken, api.invitationToken)
|
||||
|
||||
return api.buildUIURL(
|
||||
pathUISign,
|
||||
params...,
|
||||
)
|
||||
}
|
||||
|
||||
// Login to the KS Cloud using the caller's accountID, clientID and secret key.
|
||||
func (api *KSCloudAPI) Login() error {
|
||||
if err := api.loginRequirements(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. acquire auth token
|
||||
body, err := json.Marshal(feLoginData{ClientId: api.clientID, Secret: api.secretKey})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rdr, _, err := api.post(api.authTokenURL(), body, withContentJSON(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rdr.Close()
|
||||
|
||||
resp, err := decode[feLoginResponse](rdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
api.feToken = resp
|
||||
|
||||
// 2. acquire auth cookie
|
||||
// Now that we have the JWT token, acquire a cookie from the API
|
||||
api.authCookie, err = api.getAuthCookie()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
api.loggedIn = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) authTokenURL() string {
|
||||
return api.buildAuthURL(pathLogin)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getOpenidURL() string {
|
||||
return api.buildAPIURL(pathToken)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getAuthCookie() (*http.Cookie, error) {
|
||||
selectCustomer := ksCloudSelectCustomer{SelectedCustomerGuid: api.accountID}
|
||||
body, err := json.Marshal(selectCustomer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
target := api.getOpenidURL()
|
||||
o := api.defaultRequestOptions([]requestOption{withContentJSON(true), withCookie(nil)})
|
||||
req, err := http.NewRequestWithContext(o.reqContext, http.MethodPost, target, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.setHeaders(req)
|
||||
o.traceReq(req)
|
||||
resp, err := api.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
o.traceResp(resp)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to get cookie from %s: status %d", target, resp.StatusCode)
|
||||
}
|
||||
|
||||
for _, cookie := range resp.Cookies() {
|
||||
if cookie.Name == authenticationCookie {
|
||||
return cookie, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no auth cookie in response from %s", target)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) loginRequirements() error {
|
||||
if api.accountID == "" {
|
||||
return ErrLoginMissingAccountID
|
||||
}
|
||||
|
||||
if api.clientID == "" {
|
||||
return ErrLoginMissingClientID
|
||||
}
|
||||
|
||||
if api.secretKey == "" {
|
||||
return ErrLoginMissingSecretKey
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// defaultRequestOptions adds standard authentication headers to all requests
|
||||
func (api *KSCloudAPI) defaultRequestOptions(opts []requestOption) *requestOptions {
|
||||
optionsWithDefaults := append(make([]requestOption, 0, 4),
|
||||
withToken(api.feToken.Token),
|
||||
withCookie(api.authCookie),
|
||||
withTrace(api.withTrace),
|
||||
)
|
||||
optionsWithDefaults = append(optionsWithDefaults, opts...)
|
||||
|
||||
return requestOptionsWithDefaults(optionsWithDefaults)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) get(fullURL string, opts ...requestOption) (io.ReadCloser, int64, error) {
|
||||
o := api.defaultRequestOptions(opts)
|
||||
req, err := http.NewRequestWithContext(o.reqContext, http.MethodGet, fullURL, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return api.do(req, o)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) post(fullURL string, body []byte, opts ...requestOption) (io.ReadCloser, int64, error) {
|
||||
o := api.defaultRequestOptions(opts)
|
||||
req, err := http.NewRequestWithContext(o.reqContext, http.MethodPost, fullURL, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return api.do(req, o)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) delete(fullURL string, opts ...requestOption) (io.ReadCloser, int64, error) {
|
||||
o := api.defaultRequestOptions(opts)
|
||||
req, err := http.NewRequestWithContext(o.reqContext, http.MethodDelete, fullURL, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return api.do(req, o)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) do(req *http.Request, o *requestOptions) (io.ReadCloser, int64, error) {
|
||||
o.setHeaders(req)
|
||||
o.traceReq(req)
|
||||
|
||||
resp, err := api.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
o.traceResp(resp)
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
if req.URL.Path == pathLogin {
|
||||
return nil, 0, errAuth(resp)
|
||||
|
||||
}
|
||||
return nil, 0, errAPI(resp)
|
||||
}
|
||||
|
||||
return resp.Body, resp.ContentLength, err
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) paramsWithGUID() []string {
|
||||
return append(make([]string, 0, 6),
|
||||
queryParamGUID, api.getCustomerGUIDFallBack(),
|
||||
)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) paramsWithUTM() []string {
|
||||
return append(make([]string, 0, 6),
|
||||
queryParamUTMSource, "ARMOgithub",
|
||||
queryParamUTMMedium, "createaccount",
|
||||
)
|
||||
}
|
||||
|
||||
func (api *KSCloudAPI) getCustomerGUIDFallBack() string {
|
||||
if api.accountID != "" {
|
||||
return api.accountID
|
||||
}
|
||||
return fallbackGUID
|
||||
}
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/armosec/armoapi-go/armotypes"
|
||||
"github.com/armosec/armoapi-go/identifiers"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/kubescape/kubescape/v2/internal/testutils"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func mockAttackTracks() []v1alpha1.AttackTrack {
|
||||
return []v1alpha1.AttackTrack{
|
||||
{
|
||||
ApiVersion: "v1",
|
||||
Kind: "track",
|
||||
Metadata: map[string]interface{}{"label": "name"},
|
||||
Spec: v1alpha1.AttackTrackSpecification{
|
||||
Version: "v2",
|
||||
Description: "a mock",
|
||||
Data: v1alpha1.AttackTrackStep{
|
||||
Name: "track1",
|
||||
Description: "mock-step",
|
||||
SubSteps: []v1alpha1.AttackTrackStep{
|
||||
{
|
||||
Name: "track1",
|
||||
Description: "mock-step",
|
||||
Controls: []v1alpha1.IAttackTrackControl{
|
||||
mockControlPtr("control-1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Controls: []v1alpha1.IAttackTrackControl{
|
||||
mockControlPtr("control-2"),
|
||||
mockControlPtr("control-3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ApiVersion: "v1",
|
||||
Kind: "track",
|
||||
Metadata: map[string]interface{}{"label": "stuff"},
|
||||
Spec: v1alpha1.AttackTrackSpecification{
|
||||
Version: "v1",
|
||||
Description: "another mock",
|
||||
Data: v1alpha1.AttackTrackStep{
|
||||
Name: "track2",
|
||||
Description: "mock-step2",
|
||||
SubSteps: []v1alpha1.AttackTrackStep{
|
||||
{
|
||||
Name: "track3",
|
||||
Description: "mock-step",
|
||||
Controls: []v1alpha1.IAttackTrackControl{
|
||||
mockControlPtr("control-4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Controls: []v1alpha1.IAttackTrackControl{
|
||||
mockControlPtr("control-5"),
|
||||
mockControlPtr("control-6"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mockFrameworks() []reporthandling.Framework {
|
||||
id1s := []string{"control-1", "control-2"}
|
||||
id2s := []string{"control-3", "control-4"}
|
||||
id3s := []string{"control-5", "control-6"}
|
||||
|
||||
return []reporthandling.Framework{
|
||||
{
|
||||
PortalBase: armotypes.PortalBase{
|
||||
Name: "mock-1",
|
||||
},
|
||||
CreationTime: "now",
|
||||
Description: "mock-1",
|
||||
Controls: []reporthandling.Control{
|
||||
mockControl("control-1"),
|
||||
mockControl("control-2"),
|
||||
},
|
||||
ControlsIDs: &id1s,
|
||||
SubSections: map[string]*reporthandling.FrameworkSubSection{
|
||||
"section1": {
|
||||
ID: "section-id",
|
||||
ControlIDs: id1s,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
PortalBase: armotypes.PortalBase{
|
||||
Name: "mock-2",
|
||||
},
|
||||
CreationTime: "then",
|
||||
Description: "mock-2",
|
||||
Controls: []reporthandling.Control{
|
||||
mockControl("control-3"),
|
||||
mockControl("control-4"),
|
||||
},
|
||||
ControlsIDs: &id2s,
|
||||
SubSections: map[string]*reporthandling.FrameworkSubSection{
|
||||
"section2": {
|
||||
ID: "section-id",
|
||||
ControlIDs: id2s,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
PortalBase: armotypes.PortalBase{
|
||||
Name: "nsa",
|
||||
},
|
||||
CreationTime: "tomorrow",
|
||||
Description: "nsa mock",
|
||||
Controls: []reporthandling.Control{
|
||||
mockControl("control-5"),
|
||||
mockControl("control-6"),
|
||||
},
|
||||
ControlsIDs: &id3s,
|
||||
SubSections: map[string]*reporthandling.FrameworkSubSection{
|
||||
"section2": {
|
||||
ID: "section-id",
|
||||
ControlIDs: id3s,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mockControl(controlID string) reporthandling.Control {
|
||||
return reporthandling.Control{
|
||||
ControlID: controlID,
|
||||
}
|
||||
}
|
||||
func mockControlPtr(controlID string) *reporthandling.Control {
|
||||
val := mockControl(controlID)
|
||||
|
||||
return &val
|
||||
}
|
||||
|
||||
func mockExceptions() []armotypes.PostureExceptionPolicy {
|
||||
return []armotypes.PostureExceptionPolicy{
|
||||
{
|
||||
PolicyType: "postureExceptionPolicy",
|
||||
CreationTime: "now",
|
||||
Actions: []armotypes.PostureExceptionPolicyActions{
|
||||
"alertOnly",
|
||||
},
|
||||
Resources: []identifiers.PortalDesignator{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: map[string]string{
|
||||
"kind": "Pod",
|
||||
"name": "coredns-[A-Za-z0-9]+-[A-Za-z0-9]+",
|
||||
"namespace": "kube-system",
|
||||
},
|
||||
},
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: map[string]string{
|
||||
"kind": "Pod",
|
||||
"name": "etcd-.*",
|
||||
"namespace": "kube-system",
|
||||
},
|
||||
},
|
||||
},
|
||||
PosturePolicies: []armotypes.PosturePolicy{
|
||||
{
|
||||
FrameworkName: "MITRE",
|
||||
ControlID: "C-.*",
|
||||
},
|
||||
{
|
||||
FrameworkName: "another-framework",
|
||||
ControlID: "a regexp",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
PolicyType: "postureExceptionPolicy",
|
||||
CreationTime: "then",
|
||||
Actions: []armotypes.PostureExceptionPolicyActions{
|
||||
"alertOnly",
|
||||
},
|
||||
Resources: []identifiers.PortalDesignator{
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: map[string]string{
|
||||
"kind": "Deployment",
|
||||
"name": "my-regexp",
|
||||
},
|
||||
},
|
||||
{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: map[string]string{
|
||||
"kind": "Secret",
|
||||
"name": "another-regexp",
|
||||
},
|
||||
},
|
||||
},
|
||||
PosturePolicies: []armotypes.PosturePolicy{
|
||||
{
|
||||
FrameworkName: "yet-another-framework",
|
||||
ControlID: "a regexp",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mockTenantResponse() *TenantResponse {
|
||||
return &TenantResponse{
|
||||
TenantID: "id",
|
||||
Token: "token",
|
||||
Expires: "expiry-time",
|
||||
AdminMail: "admin@example.com",
|
||||
}
|
||||
}
|
||||
|
||||
func mockCustomerConfig(cluster, scope string) func() *armotypes.CustomerConfig {
|
||||
if cluster == "" {
|
||||
cluster = "my-cluster"
|
||||
}
|
||||
|
||||
if scope == "" {
|
||||
scope = "default"
|
||||
}
|
||||
|
||||
return func() *armotypes.CustomerConfig {
|
||||
return &armotypes.CustomerConfig{
|
||||
Name: "user",
|
||||
Attributes: map[string]interface{}{
|
||||
"label": "value",
|
||||
},
|
||||
Scope: identifiers.PortalDesignator{
|
||||
DesignatorType: "Attributes",
|
||||
Attributes: map[string]string{
|
||||
"kind": "Cluster",
|
||||
"name": cluster,
|
||||
"scope": scope,
|
||||
},
|
||||
},
|
||||
Settings: armotypes.Settings{
|
||||
PostureControlInputs: map[string][]string{
|
||||
"inputs-1": {"x1", "y2"},
|
||||
"inputs-2": {"x2", "y2"},
|
||||
},
|
||||
PostureScanConfig: armotypes.PostureScanConfig{
|
||||
ScanFrequency: armotypes.ScanFrequency("weekly"),
|
||||
},
|
||||
VulnerabilityScanConfig: armotypes.VulnerabilityScanConfig{
|
||||
ScanFrequency: armotypes.ScanFrequency("daily"),
|
||||
CriticalPriorityThreshold: 1,
|
||||
HighPriorityThreshold: 2,
|
||||
MediumPriorityThreshold: 3,
|
||||
ScanNewDeployment: true,
|
||||
AllowlistRegistries: []string{"a", "b"},
|
||||
BlocklistRegistries: []string{"c", "d"},
|
||||
},
|
||||
SlackConfigurations: armotypes.SlackSettings{
|
||||
Token: "slack-token",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mockLoginResponse() *feLoginResponse {
|
||||
return &feLoginResponse{
|
||||
Token: "access-token",
|
||||
RefreshToken: "refresh-token",
|
||||
Expires: "expiry-time",
|
||||
ExpiresIn: 123,
|
||||
}
|
||||
}
|
||||
|
||||
func mockPostureReport(t testing.TB, reportID, cluster string) *PostureReport {
|
||||
fixture := filepath.Join(testutils.CurrentDir(), "testdata", "mock_posture_report.json")
|
||||
|
||||
buf, err := os.ReadFile(fixture)
|
||||
require.NoError(t, err)
|
||||
|
||||
var report PostureReport
|
||||
require.NoError(t,
|
||||
jsoniter.Unmarshal(buf, &report),
|
||||
)
|
||||
|
||||
return &report
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,202 +0,0 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// KSCloudOption allows to configure the behavior of the KS Cloud client.
|
||||
KSCloudOption func(*ksCloudOptions)
|
||||
|
||||
// ksCloudOptions holds all the configurable parts of the KS Cloud client.
|
||||
ksCloudOptions struct {
|
||||
httpClient *http.Client
|
||||
cloudReportURL string
|
||||
cloudUIURL string
|
||||
timeout *time.Duration
|
||||
withTrace bool
|
||||
}
|
||||
|
||||
// request option instructs post/get/delete to alter the outgoing request
|
||||
requestOption func(*requestOptions)
|
||||
|
||||
// requestOptions knows how to enrich a request with headers
|
||||
requestOptions struct {
|
||||
withJSON bool
|
||||
withToken string
|
||||
withCookie *http.Cookie
|
||||
withTrace bool
|
||||
headers map[string]string
|
||||
reqContext context.Context
|
||||
}
|
||||
)
|
||||
|
||||
// KS Cloud client options
|
||||
|
||||
// WithHTTPClient overrides the default http.Client used by the KS Cloud client.
|
||||
func WithHTTPClient(client *http.Client) KSCloudOption {
|
||||
return func(o *ksCloudOptions) {
|
||||
o.httpClient = client
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimeout sets a global timeout on a operations performed by the KS Cloud client.
|
||||
//
|
||||
// A value of 0 means no timeout.
|
||||
//
|
||||
// The default is 61s.
|
||||
func WithTimeout(timeout time.Duration) KSCloudOption {
|
||||
duration := timeout
|
||||
|
||||
return func(o *ksCloudOptions) {
|
||||
o.timeout = &duration
|
||||
}
|
||||
}
|
||||
|
||||
// WithReportURL specifies the URL to post reports.
|
||||
func WithReportURL(u string) KSCloudOption {
|
||||
return func(o *ksCloudOptions) {
|
||||
o.cloudReportURL = u
|
||||
}
|
||||
}
|
||||
|
||||
// WithFrontendURL specifies the URL to access the KS Cloud UI.
|
||||
func WithFrontendURL(u string) KSCloudOption {
|
||||
return func(o *ksCloudOptions) {
|
||||
o.cloudUIURL = u
|
||||
}
|
||||
}
|
||||
|
||||
// WithTrace toggles requests dump for inspection & debugging.
|
||||
func WithTrace(enabled bool) KSCloudOption {
|
||||
return func(o *ksCloudOptions) {
|
||||
o.withTrace = enabled
|
||||
}
|
||||
}
|
||||
|
||||
var defaultClient = &http.Client{
|
||||
Timeout: 61 * time.Second,
|
||||
}
|
||||
|
||||
// ksCloudOptionsWithDefaults sets defaults for the KS client and applies overrides.
|
||||
func ksCloudOptionsWithDefaults(opts []KSCloudOption) *ksCloudOptions {
|
||||
options := &ksCloudOptions{
|
||||
httpClient: defaultClient,
|
||||
}
|
||||
|
||||
for _, apply := range opts {
|
||||
apply(options)
|
||||
}
|
||||
|
||||
if options.timeout != nil {
|
||||
// non-default timeout (0 means no timeout)
|
||||
// clone the client and override the timeout
|
||||
client := *options.httpClient
|
||||
client.Timeout = *options.timeout
|
||||
options.httpClient = &client
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// http request options
|
||||
|
||||
// withContentJSON sets JSON content type for a request
|
||||
func withContentJSON(enabled bool) requestOption {
|
||||
return func(o *requestOptions) {
|
||||
o.withJSON = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// withToken sets an Authorization header for a request
|
||||
func withToken(token string) requestOption {
|
||||
return func(o *requestOptions) {
|
||||
o.withToken = token
|
||||
}
|
||||
}
|
||||
|
||||
// withCookie sets an authentication cookie for a request
|
||||
func withCookie(cookie *http.Cookie) requestOption {
|
||||
return func(o *requestOptions) {
|
||||
o.withCookie = cookie
|
||||
}
|
||||
}
|
||||
|
||||
// withExtraHeaders adds extra headers to a request
|
||||
func withExtraHeaders(headers map[string]string) requestOption {
|
||||
return func(o *requestOptions) {
|
||||
o.headers = headers
|
||||
}
|
||||
}
|
||||
|
||||
/* not used yet
|
||||
// withContext sets the context of a request.
|
||||
//
|
||||
// By default, context.Background() is used.
|
||||
func withContext(ctx context.Context) requestOption {
|
||||
return func(o *requestOptions) {
|
||||
o.reqContext = ctx
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// withTrace dumps requests for debugging
|
||||
func withTrace(enabled bool) requestOption {
|
||||
return func(o *requestOptions) {
|
||||
o.withTrace = enabled
|
||||
}
|
||||
}
|
||||
|
||||
func (o *requestOptions) setHeaders(req *http.Request) {
|
||||
if o.withJSON {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
if len(o.withToken) > 0 {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", o.withToken))
|
||||
}
|
||||
|
||||
if o.withCookie != nil {
|
||||
req.AddCookie(o.withCookie)
|
||||
}
|
||||
|
||||
for k, v := range o.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// traceReq dumps the content of an outgoing request for inspecting or debugging the client.
|
||||
func (o *requestOptions) traceReq(req *http.Request) {
|
||||
if !o.withTrace {
|
||||
return
|
||||
}
|
||||
|
||||
dump, _ := httputil.DumpRequestOut(req, true)
|
||||
log.Printf("%s\n", dump)
|
||||
}
|
||||
|
||||
// traceResp dumps the content of an API response for inspecting or debugging the client.
|
||||
func (o *requestOptions) traceResp(resp *http.Response) {
|
||||
if !o.withTrace {
|
||||
return
|
||||
}
|
||||
|
||||
dump, _ := httputil.DumpResponse(resp, true)
|
||||
log.Printf("%s\n", dump)
|
||||
}
|
||||
|
||||
func requestOptionsWithDefaults(opts []requestOption) *requestOptions {
|
||||
o := &requestOptions{
|
||||
reqContext: context.Background(),
|
||||
}
|
||||
for _, apply := range opts {
|
||||
apply(o)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
|
||||
// buildAPIURL builds an URL pointing to the API backend.
|
||||
func (api *KSCloudAPI) buildAPIURL(pth string, pairs ...string) string {
|
||||
return buildQuery(url.URL{
|
||||
Scheme: api.scheme,
|
||||
Host: api.host,
|
||||
Path: pth,
|
||||
}, pairs...)
|
||||
}
|
||||
|
||||
// buildUIURL builds an URL pointing to the UI frontend.
|
||||
func (api *KSCloudAPI) buildUIURL(pth string, pairs ...string) string {
|
||||
return buildQuery(url.URL{
|
||||
Scheme: api.uischeme,
|
||||
Host: api.uihost,
|
||||
Path: pth,
|
||||
}, pairs...)
|
||||
}
|
||||
|
||||
// buildAuthURL builds an URL pointing to the authentication endpoint.
|
||||
func (api *KSCloudAPI) buildAuthURL(pth string, pairs ...string) string {
|
||||
return buildQuery(url.URL{
|
||||
Scheme: api.authscheme,
|
||||
Host: api.authhost,
|
||||
Path: pth,
|
||||
}, pairs...)
|
||||
}
|
||||
|
||||
// buildReportURL builds an URL pointing to the reporting endpoint.
|
||||
func (api *KSCloudAPI) buildReportURL(pth string, pairs ...string) string {
|
||||
return buildQuery(url.URL{
|
||||
Scheme: api.reportscheme,
|
||||
Host: api.reporthost,
|
||||
Path: pth,
|
||||
}, pairs...)
|
||||
}
|
||||
|
||||
// buildQuery builds an URL with query params.
|
||||
//
|
||||
// Params are provided in pairs (param name, value).
|
||||
func buildQuery(u url.URL, pairs ...string) string {
|
||||
if len(pairs)%2 != 0 {
|
||||
panic("dev error: buildURL accepts query params in (name, value) pairs")
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
|
||||
for i := 0; i < len(pairs)-1; i += 2 {
|
||||
param := pairs[i]
|
||||
value := pairs[i+1]
|
||||
|
||||
q.Add(param, value)
|
||||
}
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
u.Path = path.Clean(u.Path)
|
||||
|
||||
return u.String()
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBuildURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ks := NewKSCloudAPICustomized(
|
||||
"api.example.com", "auth.example.com", // required
|
||||
WithFrontendURL("ui.example.com"), // optional
|
||||
WithReportURL("report.example.com"), // optional
|
||||
)
|
||||
|
||||
t.Run("should build API URL with query params on https host", func(t *testing.T) {
|
||||
require.Equal(t,
|
||||
"https://api.example.com/path?q1=v1&q2=v2",
|
||||
ks.buildAPIURL("/path", "q1", "v1", "q2", "v2"),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("should build API URL with query params on http host", func(t *testing.T) {
|
||||
ku := NewKSCloudAPICustomized("http://api.example.com", "auth.example.com")
|
||||
|
||||
require.Equal(t,
|
||||
"http://api.example.com/path?q1=v1&q2=v2",
|
||||
ku.buildAPIURL("/path", "q1", "v1", "q2", "v2"),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("should panic when params are not provided in pairs", func(t *testing.T) {
|
||||
require.Panics(t, func() {
|
||||
// notice how the linter detects wrong args
|
||||
_ = ks.buildAPIURL("/path", "q1", "v1", "q2") //nolint:staticcheck
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should build UI URL with query params on https host", func(t *testing.T) {
|
||||
require.Equal(t,
|
||||
"https://ui.example.com/path?q1=v1&q2=v2",
|
||||
ks.buildUIURL("/path", "q1", "v1", "q2", "v2"),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("should build report URL with query params on https host", func(t *testing.T) {
|
||||
require.Equal(t,
|
||||
"https://report.example.com/path?q1=v1&q2=v2",
|
||||
ks.buildReportURL("/path", "q1", "v1", "q2", "v2"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestViewURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ks := NewKSCloudAPICustomized(
|
||||
"api.example.com", "auth.example.com", // required
|
||||
WithFrontendURL("ui.example.com"), // optional
|
||||
WithReportURL("report.example.com"), // optional
|
||||
)
|
||||
ks.SetAccountID("me")
|
||||
ks.SetInvitationToken("invite")
|
||||
|
||||
t.Run("should render UI report URL", func(t *testing.T) {
|
||||
require.Equal(t, "https://ui.example.com/repository-scanning/xyz", ks.ViewReportURL("xyz"))
|
||||
})
|
||||
|
||||
t.Run("should render UI dashboard URL", func(t *testing.T) {
|
||||
require.Equal(t, "https://ui.example.com/dashboard", ks.ViewDashboardURL())
|
||||
})
|
||||
|
||||
t.Run("should render UI RBAC URL", func(t *testing.T) {
|
||||
require.Equal(t, "https://ui.example.com/rbac-visualizer", ks.ViewRBACURL())
|
||||
})
|
||||
|
||||
t.Run("should render UI scan URL", func(t *testing.T) {
|
||||
require.Equal(t, "https://ui.example.com/compliance/cluster", ks.ViewScanURL("cluster"))
|
||||
})
|
||||
|
||||
t.Run("should render UI sign URL", func(t *testing.T) {
|
||||
require.Equal(t, "https://ui.example.com/account/sign-up?customerGUID=me&invitationToken=invite&utm_medium=createaccount&utm_source=ARMOgithub", ks.ViewSignURL())
|
||||
})
|
||||
}
|
||||
@@ -1,28 +1,9 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseHost picks a host from a hostname or an URL and detects the scheme.
|
||||
//
|
||||
// The default scheme is https. This may be altered by specifying an explicit http://hostname URL.
|
||||
func parseHost(host string) (string, string) {
|
||||
if strings.HasPrefix(host, "http://") {
|
||||
return "http", strings.Replace(host, "http://", "", 1) // cut... index ...
|
||||
}
|
||||
|
||||
// default scheme
|
||||
return "https", strings.Replace(host, "https://", "", 1)
|
||||
}
|
||||
|
||||
func isNativeFramework(framework string) bool {
|
||||
return contains(NativeFrameworks, framework)
|
||||
}
|
||||
|
||||
func contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if strings.EqualFold(v, str) {
|
||||
@@ -32,51 +13,3 @@ func contains(s []string, str string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func min(a, b int64) int64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// errAPI reports an API error, with a cap on the length of the error message.
|
||||
func errAPI(resp *http.Response) error {
|
||||
const maxSize = 1024
|
||||
|
||||
reason := new(strings.Builder)
|
||||
if resp.Body != nil {
|
||||
size := min(resp.ContentLength, maxSize)
|
||||
if size > 0 {
|
||||
reason.Grow(int(size))
|
||||
}
|
||||
|
||||
_, _ = io.CopyN(reason, resp.Body, size)
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
return fmt.Errorf("http-error: '%s', reason: '%s'", resp.Status, reason.String())
|
||||
}
|
||||
|
||||
// errAuth returns an authentication error.
|
||||
//
|
||||
// Authentication errors upon login croak a less detailed message.
|
||||
func errAuth(resp *http.Response) error {
|
||||
return fmt.Errorf("error authenticating: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
func readString(rdr io.Reader, sizeHint int64) (string, error) {
|
||||
|
||||
// if the response is empty, return an empty string
|
||||
if sizeHint < 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
b.Grow(int(sizeHint))
|
||||
_, err := io.Copy(&b, rdr)
|
||||
|
||||
return b.String(), err
|
||||
}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package getter
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseHost(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("should recognize http scheme", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const input = "http://localhost:7555"
|
||||
scheme, host := parseHost(input)
|
||||
require.Equal(t, "http", scheme)
|
||||
require.Equal(t, "localhost:7555", host)
|
||||
})
|
||||
|
||||
t.Run("should recognize https scheme", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const input = "https://localhost:7555"
|
||||
scheme, host := parseHost(input)
|
||||
require.Equal(t, "https", scheme)
|
||||
require.Equal(t, "localhost:7555", host)
|
||||
})
|
||||
|
||||
t.Run("should adopt https scheme by default", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const input = "portal-dev.armo.cloud"
|
||||
scheme, host := parseHost(input)
|
||||
require.Equal(t, "https", scheme)
|
||||
require.Equal(t, "portal-dev.armo.cloud", host)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsNativeFramework(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.Truef(t, isNativeFramework("nSa"), "expected nsa to be native (case insensitive)")
|
||||
require.Falsef(t, isNativeFramework("foo"), "expected framework to be custom")
|
||||
}
|
||||
|
||||
func Test_readString(t *testing.T) {
|
||||
type args struct {
|
||||
rdr io.Reader
|
||||
sizeHint int64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "should return empty string if sizeHint is negative",
|
||||
args: args{
|
||||
rdr: nil,
|
||||
sizeHint: -1,
|
||||
},
|
||||
want: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should return empty string if sizeHint is zero",
|
||||
args: args{
|
||||
rdr: &io.LimitedReader{},
|
||||
sizeHint: 0,
|
||||
},
|
||||
want: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should return empty string if sizeHint is positive",
|
||||
args: args{
|
||||
rdr: &io.LimitedReader{},
|
||||
sizeHint: 1,
|
||||
},
|
||||
want: "",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := readString(tt.args.rdr, tt.args.sizeHint)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("readString() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("readString() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -7,43 +7,24 @@ import (
|
||||
)
|
||||
|
||||
type RootInfo struct {
|
||||
Logger string // logger level
|
||||
LoggerName string // logger name ("pretty"/"zap"/"none")
|
||||
CacheDir string // cached dir
|
||||
DisableColor bool // Disable Color
|
||||
EnableColor bool // Force enable Color
|
||||
|
||||
KSCloudBEURLs string // Kubescape Cloud URL
|
||||
KSCloudBEURLsDep string // Kubescape Cloud URL
|
||||
Logger string // logger level
|
||||
LoggerName string // logger name ("pretty"/"zap"/"none")
|
||||
CacheDir string // cached dir
|
||||
DisableColor bool // Disable Color
|
||||
EnableColor bool // Force enable Color
|
||||
DiscoveryServerURL string // Discovery Server URL (See https://github.com/kubescape/backend/tree/main/pkg/servicediscovery)
|
||||
KubeContext string // context name
|
||||
}
|
||||
type CloudURLs struct {
|
||||
CloudReportURL string
|
||||
CloudAPIURL string
|
||||
CloudUIURL string
|
||||
CloudAuthURL string
|
||||
}
|
||||
|
||||
type Credentials struct {
|
||||
Account string
|
||||
ClientID string
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
// To check if the user's credentials: accountID / clientID / secretKey are valid.
|
||||
func (credentials *Credentials) Validate() error {
|
||||
|
||||
// To check if the provided account ID is valid
|
||||
func ValidateAccountID(accountID string) error {
|
||||
// Check if the Account-ID is valid
|
||||
if _, err := uuid.Parse(credentials.Account); credentials.Account != "" && err != nil {
|
||||
return fmt.Errorf("bad argument: account must be a valid UUID")
|
||||
}
|
||||
// Check if the Client-ID is valid
|
||||
if _, err := uuid.Parse(credentials.ClientID); credentials.ClientID != "" && err != nil {
|
||||
return fmt.Errorf("bad argument: account must be a valid UUID")
|
||||
}
|
||||
|
||||
// Check if the Secret-Key is valid
|
||||
if _, err := uuid.Parse(credentials.SecretKey); credentials.SecretKey != "" && err != nil {
|
||||
return fmt.Errorf("bad argument: account must be a valid UUID")
|
||||
if _, err := uuid.Parse(accountID); accountID != "" && err != nil {
|
||||
return fmt.Errorf("bad argument: accound ID must be a valid UUID")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2,11 +2,9 @@ package cautils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCredentials_Validate(t *testing.T) {
|
||||
func TestValidateAccountID(t *testing.T) {
|
||||
type fields struct {
|
||||
Account string
|
||||
ClientID string
|
||||
SecretKey string
|
||||
Account string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -27,44 +25,11 @@ func TestCredentials_Validate(t *testing.T) {
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid client ID",
|
||||
fields: fields{
|
||||
ClientID: "22019933-feac-4012-a8eb-e81461ba6655",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid client ID",
|
||||
fields: fields{
|
||||
ClientID: "22019933-feac-4012-a8eb-e81461ba665",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid secret key",
|
||||
fields: fields{
|
||||
SecretKey: "22019933-feac-4012-a8eb-e81461ba6655",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid secret key",
|
||||
fields: fields{
|
||||
SecretKey: "22019933-feac-4012-a8eb-e81461ba665",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
credentials := &Credentials{
|
||||
Account: tt.fields.Account,
|
||||
ClientID: tt.fields.ClientID,
|
||||
SecretKey: tt.fields.SecretKey,
|
||||
}
|
||||
if err := credentials.Validate(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Credentials.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
if err := ValidateAccountID(tt.fields.Account); (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidateAccountID() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -124,13 +124,11 @@ type ScanInfo struct {
|
||||
ComplianceThreshold float32 // Compliance score threshold
|
||||
FailThresholdSeverity string // Severity at and above which the command should fail
|
||||
Submit bool // Submit results to Kubescape Cloud BE
|
||||
CreateAccount bool // Create account in Kubescape Cloud BE if no account found in local cache
|
||||
ScanID string // Report id of the current scan
|
||||
HostSensorEnabled BoolPtrFlag // Deploy Kubescape K8s host scanner to collect data from certain controls
|
||||
HostSensorYamlPath string // Path to hostsensor file
|
||||
Local bool // Do not submit results
|
||||
Credentials Credentials // account ID
|
||||
KubeContext string // context name
|
||||
AccountID string // account ID
|
||||
FrameworkScan bool // false if scanning control
|
||||
ScanAll bool // true if scan all frameworks
|
||||
OmitRawResources bool // true if omit raw resources from the output
|
||||
@@ -296,11 +294,10 @@ func scanInfoToScanMetadata(ctx context.Context, scanInfo *ScanInfo) *reporthand
|
||||
}
|
||||
|
||||
func (scanInfo *ScanInfo) GetScanningContext() ScanningContext {
|
||||
input := ""
|
||||
if len(scanInfo.InputPatterns) > 0 {
|
||||
input = scanInfo.InputPatterns[0]
|
||||
return GetScanningContext(scanInfo.InputPatterns[0])
|
||||
}
|
||||
return GetScanningContext(input)
|
||||
return GetScanningContext("")
|
||||
}
|
||||
|
||||
// GetScanningContext get scanning context from the input param
|
||||
|
||||
@@ -2,6 +2,8 @@ package cautils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
@@ -23,13 +25,11 @@ func TestSetContextMetadata(t *testing.T) {
|
||||
/*{
|
||||
ctx := reporthandlingv2.ContextMetadata{}
|
||||
setContextMetadata(&ctx, "https://github.com/kubescape/kubescape")
|
||||
|
||||
assert.Nil(t, ctx.ClusterContextMetadata)
|
||||
assert.Nil(t, ctx.DirectoryContextMetadata)
|
||||
assert.Nil(t, ctx.FileContextMetadata)
|
||||
assert.Nil(t, ctx.HelmContextMetadata)
|
||||
assert.NotNil(t, ctx.RepoContextMetadata)
|
||||
|
||||
assert.Equal(t, "kubescape", ctx.RepoContextMetadata.Repo)
|
||||
assert.Equal(t, "kubescape", ctx.RepoContextMetadata.Owner)
|
||||
assert.Equal(t, "master", ctx.RepoContextMetadata.Branch)
|
||||
@@ -37,12 +37,18 @@ func TestSetContextMetadata(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetHostname(t *testing.T) {
|
||||
// Test that the hostname is not empty
|
||||
assert.NotEqual(t, "", getHostname())
|
||||
}
|
||||
|
||||
func TestGetScanningContext(t *testing.T) {
|
||||
// Test with empty input
|
||||
assert.Equal(t, ContextCluster, GetScanningContext(""))
|
||||
|
||||
// Test with Git URL input
|
||||
assert.Equal(t, ContextGitURL, GetScanningContext("https://github.com/kubescape/kubescape"))
|
||||
|
||||
// TODO: Add more tests with other input types
|
||||
}
|
||||
|
||||
func TestScanInfoFormats(t *testing.T) {
|
||||
@@ -71,3 +77,30 @@ func TestScanInfoFormats(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetScanningContextWithFile(t *testing.T) {
|
||||
// Test with a file
|
||||
dir, err := os.MkdirTemp("", "example")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
filePath := filepath.Join(dir, "file.txt")
|
||||
if _, err := os.Create(filePath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, ContextFile, GetScanningContext(filePath))
|
||||
}
|
||||
|
||||
func TestGetScanningContextWithDir(t *testing.T) {
|
||||
// Test with a directory
|
||||
dir, err := os.MkdirTemp("", "example")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
assert.Equal(t, ContextDir, GetScanningContext(dir))
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ type IVersionCheckHandler interface {
|
||||
|
||||
func NewIVersionCheckHandler(ctx context.Context) IVersionCheckHandler {
|
||||
if BuildNumber == "" {
|
||||
logger.L().Ctx(ctx).Warning("Unknown build number, this might affect your scan results. Please make sure you are updated to latest version")
|
||||
logger.L().Ctx(ctx).Warning("Unknown build number, this might affect your scan results. Please make sure that you are running the latest version")
|
||||
}
|
||||
|
||||
if v, ok := os.LookupEnv(CLIENT_ENV); ok && v != "" {
|
||||
|
||||
@@ -4,47 +4,35 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
)
|
||||
|
||||
func (ks *Kubescape) SetCachedConfig(setConfig *metav1.SetConfig) error {
|
||||
|
||||
tenant := getTenantConfig(nil, "", "", nil)
|
||||
tenant := cautils.GetTenantConfig("", "", "", nil)
|
||||
|
||||
if setConfig.Account != "" {
|
||||
tenant.GetConfigObj().AccountID = setConfig.Account
|
||||
}
|
||||
if setConfig.SecretKey != "" {
|
||||
tenant.GetConfigObj().SecretKey = setConfig.SecretKey
|
||||
}
|
||||
if setConfig.ClientID != "" {
|
||||
tenant.GetConfigObj().ClientID = setConfig.ClientID
|
||||
}
|
||||
if setConfig.CloudAPIURL != "" {
|
||||
tenant.GetConfigObj().CloudAPIURL = setConfig.CloudAPIURL
|
||||
}
|
||||
if setConfig.CloudAuthURL != "" {
|
||||
tenant.GetConfigObj().CloudAuthURL = setConfig.CloudAuthURL
|
||||
}
|
||||
if setConfig.CloudReportURL != "" {
|
||||
tenant.GetConfigObj().CloudReportURL = setConfig.CloudReportURL
|
||||
}
|
||||
if setConfig.CloudUIURL != "" {
|
||||
tenant.GetConfigObj().CloudUIURL = setConfig.CloudUIURL
|
||||
}
|
||||
|
||||
return tenant.UpdateCachedConfig()
|
||||
}
|
||||
|
||||
// View cached configurations
|
||||
func (ks *Kubescape) ViewCachedConfig(viewConfig *metav1.ViewConfig) error {
|
||||
tenant := getTenantConfig(nil, "", "", getKubernetesApi()) // change k8sinterface
|
||||
tenant := cautils.GetTenantConfig("", "", "", getKubernetesApi()) // change k8sinterface
|
||||
fmt.Fprintf(viewConfig.Writer, "%s\n", tenant.GetConfigObj().Config())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ks *Kubescape) DeleteCachedConfig(ctx context.Context, deleteConfig *metav1.DeleteConfig) error {
|
||||
|
||||
tenant := getTenantConfig(nil, "", "", nil) // change k8sinterface
|
||||
tenant := cautils.GetTenantConfig("", "", "", nil) // change k8sinterface
|
||||
return tenant.DeleteCachedConfig(ctx)
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
v1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
)
|
||||
|
||||
func (ks *Kubescape) DeleteExceptions(delExceptions *v1.DeleteExceptions) error {
|
||||
|
||||
// load cached config
|
||||
getTenantConfig(&delExceptions.Credentials, "", "", getKubernetesApi())
|
||||
|
||||
// login kubescape SaaS
|
||||
ksCloudAPI := getter.GetKSCloudAPIConnector()
|
||||
if err := ksCloudAPI.Login(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range delExceptions.Exceptions {
|
||||
exceptionName := delExceptions.Exceptions[i]
|
||||
if exceptionName == "" {
|
||||
continue
|
||||
}
|
||||
logger.L().Info("Deleting exception", helpers.String("name", exceptionName))
|
||||
if err := ksCloudAPI.DeleteException(exceptionName); err != nil {
|
||||
return fmt.Errorf("failed to delete exception '%s', reason: %s", exceptionName, err.Error())
|
||||
}
|
||||
logger.L().Success("Exception deleted successfully")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
)
|
||||
@@ -91,7 +92,7 @@ func downloadArtifacts(ctx context.Context, downloadInfo *metav1.DownloadInfo) e
|
||||
}
|
||||
|
||||
func downloadConfigInputs(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
|
||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||
tenant := cautils.GetTenantConfig(downloadInfo.AccountID, "", "", getKubernetesApi())
|
||||
|
||||
controlsInputsGetter := getConfigInputsGetter(ctx, downloadInfo.Identifier, tenant.GetAccountID(), nil)
|
||||
controlInputs, err := controlsInputsGetter.GetControlsInputs(tenant.GetContextName())
|
||||
@@ -114,7 +115,7 @@ func downloadConfigInputs(ctx context.Context, downloadInfo *metav1.DownloadInfo
|
||||
}
|
||||
|
||||
func downloadExceptions(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
|
||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||
tenant := cautils.GetTenantConfig(downloadInfo.AccountID, "", "", getKubernetesApi())
|
||||
exceptionsGetter := getExceptionsGetter(ctx, "", tenant.GetAccountID(), nil)
|
||||
|
||||
exceptions, err := exceptionsGetter.GetExceptions(tenant.GetContextName())
|
||||
@@ -136,7 +137,7 @@ func downloadExceptions(ctx context.Context, downloadInfo *metav1.DownloadInfo)
|
||||
|
||||
func downloadAttackTracks(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
|
||||
var err error
|
||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||
tenant := cautils.GetTenantConfig(downloadInfo.AccountID, "", "", getKubernetesApi())
|
||||
|
||||
attackTracksGetter := getAttackTracksGetter(ctx, "", tenant.GetAccountID(), nil)
|
||||
|
||||
@@ -160,9 +161,9 @@ func downloadAttackTracks(ctx context.Context, downloadInfo *metav1.DownloadInfo
|
||||
|
||||
func downloadFramework(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
|
||||
|
||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||
tenant := cautils.GetTenantConfig(downloadInfo.AccountID, "", "", getKubernetesApi())
|
||||
|
||||
g := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), true, nil)
|
||||
g := getPolicyGetter(ctx, nil, tenant.GetAccountID(), true, nil)
|
||||
|
||||
if downloadInfo.Identifier == "" {
|
||||
// if framework name not specified - download all frameworks
|
||||
@@ -202,9 +203,9 @@ func downloadFramework(ctx context.Context, downloadInfo *metav1.DownloadInfo) e
|
||||
|
||||
func downloadControl(ctx context.Context, downloadInfo *metav1.DownloadInfo) error {
|
||||
|
||||
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())
|
||||
tenant := cautils.GetTenantConfig(downloadInfo.AccountID, "", "", getKubernetesApi())
|
||||
|
||||
g := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), false, nil)
|
||||
g := getPolicyGetter(ctx, nil, tenant.GetAccountID(), false, nil)
|
||||
|
||||
if downloadInfo.Identifier == "" {
|
||||
// TODO - support
|
||||
|
||||
@@ -30,12 +30,6 @@ func getKubernetesApi() *k8sinterface.KubernetesApi {
|
||||
}
|
||||
return k8sinterface.NewKubernetesApi()
|
||||
}
|
||||
func getTenantConfig(credentials *cautils.Credentials, clusterName string, customClusterName string, k8s *k8sinterface.KubernetesApi) cautils.ITenantConfig {
|
||||
if !k8sinterface.IsConnectedToCluster() || k8s == nil {
|
||||
return cautils.NewLocalConfig(getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||
}
|
||||
return cautils.NewClusterConfig(k8s, getter.GetKSCloudAPIConnector(), credentials, clusterName, customClusterName)
|
||||
}
|
||||
|
||||
func getExceptionsGetter(ctx context.Context, useExceptions string, accountID string, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IExceptionsGetter {
|
||||
if useExceptions != "" {
|
||||
@@ -74,7 +68,7 @@ func getReporter(ctx context.Context, tenantConfig cautils.ITenantConfig, report
|
||||
if scanInfo.GetScanningContext() != cautils.ContextCluster {
|
||||
submitData = reporterv2.SubmitContextRepository
|
||||
}
|
||||
return reporterv2.NewReportEventReceiver(tenantConfig.GetConfigObj(), reportID, submitData)
|
||||
return reporterv2.NewReportEventReceiver(tenantConfig, reportID, submitData)
|
||||
}
|
||||
if tenantConfig.GetAccountID() == "" {
|
||||
// Add link only when scanning a cluster using a framework
|
||||
@@ -89,7 +83,7 @@ func getReporter(ctx context.Context, tenantConfig cautils.ITenantConfig, report
|
||||
return reporterv2.NewReportMock("", message)
|
||||
}
|
||||
|
||||
func getResourceHandler(ctx context.Context, scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor, registryAdaptors *resourcehandler.RegistryAdaptors) resourcehandler.IResourceHandler {
|
||||
func getResourceHandler(ctx context.Context, scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig, k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor) resourcehandler.IResourceHandler {
|
||||
ctx, span := otel.Tracer("").Start(ctx, "getResourceHandler")
|
||||
defer span.End()
|
||||
|
||||
@@ -100,7 +94,7 @@ func getResourceHandler(ctx context.Context, scanInfo *cautils.ScanInfo, tenantC
|
||||
|
||||
getter.GetKSCloudAPIConnector()
|
||||
rbacObjects := getRBACHandler(tenantConfig, k8s, scanInfo.Submit)
|
||||
return resourcehandler.NewK8sResourceHandler(k8s, hostSensorHandler, rbacObjects, registryAdaptors)
|
||||
return resourcehandler.NewK8sResourceHandler(k8s, hostSensorHandler, rbacObjects, tenantConfig.GetContextName())
|
||||
}
|
||||
|
||||
// getHostSensorHandler yields a IHostSensor that knows how to collect a host's scanned resources.
|
||||
@@ -153,57 +147,59 @@ func policyIdentifierIdentities(pi []cautils.PolicyIdentifier) string {
|
||||
func setSubmitBehavior(scanInfo *cautils.ScanInfo, tenantConfig cautils.ITenantConfig) {
|
||||
|
||||
/*
|
||||
If keep-local OR scan type which is not submittable - Do not send report
|
||||
|
||||
If CloudReportURL not set - Do not send report
|
||||
|
||||
If There is no account - Do not send report
|
||||
If CloudReportURL is set
|
||||
If There is no account -
|
||||
Generate Account & Submit report
|
||||
|
||||
If There is account -
|
||||
keep-local - Do not send report
|
||||
Default - Submit report
|
||||
If There is account -
|
||||
Invalid Account ID - Do not send report
|
||||
Valid Account - Submit report
|
||||
|
||||
*/
|
||||
|
||||
if getter.GetKSCloudAPIConnector().GetCloudAPIURL() == "" {
|
||||
// do not submit control/workload scanning
|
||||
if !isScanTypeForSubmission(scanInfo.ScanType) || scanInfo.Local {
|
||||
scanInfo.Submit = false
|
||||
return
|
||||
}
|
||||
|
||||
// do not submit control scanning
|
||||
if !scanInfo.FrameworkScan {
|
||||
if getter.GetKSCloudAPIConnector().GetCloudReportURL() == "" {
|
||||
scanInfo.Submit = false
|
||||
return
|
||||
}
|
||||
|
||||
if scanInfo.Local {
|
||||
scanInfo.Submit = false
|
||||
return
|
||||
}
|
||||
|
||||
// do not submit single resource scan to BE
|
||||
if scanInfo.ScanObject != nil {
|
||||
scanInfo.Submit = false
|
||||
return
|
||||
}
|
||||
|
||||
// If There is no account, or if the account is not legal, do not submit
|
||||
if _, err := uuid.Parse(tenantConfig.GetAccountID()); err != nil {
|
||||
scanInfo.Submit = false
|
||||
} else {
|
||||
// a new account will be created if a report URL is set and there is no account ID
|
||||
if tenantConfig.GetAccountID() == "" {
|
||||
scanInfo.Submit = true
|
||||
return
|
||||
}
|
||||
|
||||
if scanInfo.CreateAccount {
|
||||
scanInfo.Submit = true
|
||||
_, err := uuid.Parse(tenantConfig.GetAccountID())
|
||||
if err != nil {
|
||||
logger.L().Warning("account is not a valid UUID", helpers.Error(err))
|
||||
}
|
||||
|
||||
// submit if account is valid
|
||||
scanInfo.Submit = err == nil
|
||||
}
|
||||
|
||||
func isScanTypeForSubmission(scanType cautils.ScanTypes) bool {
|
||||
if scanType == cautils.ScanTypeControl || scanType == cautils.ScanTypeWorkload {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// setPolicyGetter set the policy getter - local file/github release/Kubescape Cloud API
|
||||
func getPolicyGetter(ctx context.Context, loadPoliciesFromFile []string, tenantEmail string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
|
||||
func getPolicyGetter(ctx context.Context, loadPoliciesFromFile []string, accountID string, frameworkScope bool, downloadReleasedPolicy *getter.DownloadReleasedPolicy) getter.IPolicyGetter {
|
||||
if len(loadPoliciesFromFile) > 0 {
|
||||
return getter.NewLoadPolicy(loadPoliciesFromFile)
|
||||
}
|
||||
if tenantEmail != "" && getter.GetKSCloudAPIConnector().GetCloudAPIURL() != "" && frameworkScope {
|
||||
if accountID != "" && getter.GetKSCloudAPIConnector().GetCloudAPIURL() != "" && frameworkScope {
|
||||
g := getter.GetKSCloudAPIConnector() // download policy from Kubescape Cloud backend
|
||||
return g
|
||||
}
|
||||
@@ -277,12 +273,12 @@ func getAttackTracksGetter(ctx context.Context, attackTracks, accountID string,
|
||||
}
|
||||
|
||||
// getUIPrinter returns a printer that will be used to print to the program’s UI (terminal)
|
||||
func GetUIPrinter(ctx context.Context, scanInfo *cautils.ScanInfo) printer.IPrinter {
|
||||
func GetUIPrinter(ctx context.Context, scanInfo *cautils.ScanInfo, clusterName string) printer.IPrinter {
|
||||
var p printer.IPrinter
|
||||
if helpers.ToLevel(logger.L().GetLevel()) >= helpers.WarningLevel {
|
||||
p = &printerv2.SilentPrinter{}
|
||||
} else {
|
||||
p = printerv2.NewPrettyPrinter(scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View), scanInfo.ScanType, scanInfo.InputPatterns)
|
||||
p = printerv2.NewPrettyPrinter(scanInfo.VerboseMode, scanInfo.FormatVersion, scanInfo.PrintAttackTree, cautils.ViewTypes(scanInfo.View), scanInfo.ScanType, scanInfo.InputPatterns, clusterName)
|
||||
|
||||
// Since the UI of the program is a CLI (Stdout), it means that it should always print to Stdout
|
||||
p.SetWriter(ctx, os.Stdout.Name())
|
||||
|
||||
@@ -88,7 +88,7 @@ func Test_getUIPrinter(t *testing.T) {
|
||||
View: string(tt.args.viewType),
|
||||
}
|
||||
|
||||
got := GetUIPrinter(tt.args.ctx, scanInfo)
|
||||
got := GetUIPrinter(tt.args.ctx, scanInfo, "test-cluster")
|
||||
|
||||
assert.Equal(t, tt.want.structType, reflect.TypeOf(got).String())
|
||||
|
||||
@@ -183,3 +183,49 @@ func TestGetSensorHandler(t *testing.T) {
|
||||
|
||||
// TODO(fredbi): need to share the k8s client mock to test a happy path / deployment failure path
|
||||
}
|
||||
|
||||
func TestIsScanTypeForSubmission(t *testing.T) {
|
||||
test := []struct {
|
||||
name string
|
||||
scanType cautils.ScanTypes
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "cluster scan",
|
||||
scanType: cautils.ScanTypeCluster,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "repo scan",
|
||||
scanType: cautils.ScanTypeRepo,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "workload scan",
|
||||
scanType: cautils.ScanTypeWorkload,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "control scan",
|
||||
scanType: cautils.ScanTypeControl,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "framework scan",
|
||||
scanType: cautils.ScanTypeFramework,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "image scan",
|
||||
scanType: cautils.ScanTypeImage,
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range test {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := isScanTypeForSubmission(tt.scanType)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,22 +53,22 @@ func (ks *Kubescape) List(ctx context.Context, listPolicies *metav1.ListPolicies
|
||||
}
|
||||
|
||||
func listFrameworks(ctx context.Context, listPolicies *metav1.ListPolicies) ([]string, error) {
|
||||
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
|
||||
policyGetter := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), true, nil)
|
||||
tenant := cautils.GetTenantConfig(listPolicies.AccountID, "", "", getKubernetesApi()) // change k8sinterface
|
||||
policyGetter := getPolicyGetter(ctx, nil, tenant.GetAccountID(), true, nil)
|
||||
|
||||
return listFrameworksNames(policyGetter), nil
|
||||
}
|
||||
|
||||
func listControls(ctx context.Context, listPolicies *metav1.ListPolicies) ([]string, error) {
|
||||
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi()) // change k8sinterface
|
||||
tenant := cautils.GetTenantConfig(listPolicies.AccountID, "", "", getKubernetesApi()) // change k8sinterface
|
||||
|
||||
policyGetter := getPolicyGetter(ctx, nil, tenant.GetTenantEmail(), false, nil)
|
||||
policyGetter := getPolicyGetter(ctx, nil, tenant.GetAccountID(), false, nil)
|
||||
return policyGetter.ListControls()
|
||||
}
|
||||
|
||||
func listExceptions(ctx context.Context, listPolicies *metav1.ListPolicies) ([]string, error) {
|
||||
// load tenant metav1
|
||||
tenant := getTenantConfig(&listPolicies.Credentials, "", "", getKubernetesApi())
|
||||
tenant := cautils.GetTenantConfig(listPolicies.AccountID, "", "", getKubernetesApi())
|
||||
|
||||
var exceptionsNames []string
|
||||
ksCloudAPI := getExceptionsGetter(ctx, "", tenant.GetAccountID(), nil)
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/go-logger/iconlogger"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
@@ -50,23 +49,17 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
|
||||
}
|
||||
|
||||
// ================== setup tenant object ======================================
|
||||
ctxTenant, spanTenant := otel.Tracer("").Start(ctx, "setup tenant")
|
||||
tenantConfig := getTenantConfig(&scanInfo.Credentials, k8sinterface.GetContextName(), scanInfo.CustomClusterName, k8s)
|
||||
tenantConfig := cautils.GetTenantConfig(scanInfo.AccountID, k8sinterface.GetContextName(), scanInfo.CustomClusterName, k8s)
|
||||
|
||||
// Set submit behavior AFTER loading tenant config
|
||||
setSubmitBehavior(scanInfo, tenantConfig)
|
||||
|
||||
if scanInfo.Submit {
|
||||
// submit - Create tenant & Submit report
|
||||
if err := tenantConfig.SetTenant(); err != nil {
|
||||
logger.L().Ctx(ctxTenant).Error(err.Error())
|
||||
}
|
||||
|
||||
if scanInfo.OmitRawResources {
|
||||
logger.L().Ctx(ctx).Warning("omit-raw-resources flag will be ignored in submit mode")
|
||||
}
|
||||
}
|
||||
spanTenant.End()
|
||||
|
||||
// ================== version testing ======================================
|
||||
|
||||
@@ -82,13 +75,9 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
|
||||
}
|
||||
spanHostScanner.End()
|
||||
|
||||
// ================== setup registry adaptors ======================================
|
||||
|
||||
registryAdaptors, _ := resourcehandler.NewRegistryAdaptors()
|
||||
|
||||
// ================== setup resource collector object ======================================
|
||||
|
||||
resourceHandler := getResourceHandler(ctx, scanInfo, tenantConfig, k8s, hostSensorHandler, registryAdaptors)
|
||||
resourceHandler := getResourceHandler(ctx, scanInfo, tenantConfig, k8s, hostSensorHandler)
|
||||
|
||||
// ================== setup reporter & printer objects ======================================
|
||||
|
||||
@@ -96,9 +85,9 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
|
||||
reportHandler := getReporter(ctx, tenantConfig, scanInfo.ScanID, scanInfo.Submit, scanInfo.FrameworkScan, *scanInfo)
|
||||
|
||||
// setup printers
|
||||
outputPrinters := GetOutputPrinters(scanInfo, ctx)
|
||||
outputPrinters := GetOutputPrinters(scanInfo, ctx, tenantConfig.GetContextName())
|
||||
|
||||
uiPrinter := GetUIPrinter(ctx, scanInfo)
|
||||
uiPrinter := GetUIPrinter(ctx, scanInfo, tenantConfig.GetContextName())
|
||||
|
||||
// ================== return interface ======================================
|
||||
|
||||
@@ -112,12 +101,16 @@ func getInterfaces(ctx context.Context, scanInfo *cautils.ScanInfo) componentInt
|
||||
}
|
||||
}
|
||||
|
||||
func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context) []printer.IPrinter {
|
||||
func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context, clusterName string) []printer.IPrinter {
|
||||
formats := scanInfo.Formats()
|
||||
|
||||
outputPrinters := make([]printer.IPrinter, 0)
|
||||
for _, format := range formats {
|
||||
printerHandler := resultshandling.NewPrinter(ctx, format, scanInfo.FormatVersion, scanInfo.PrintAttackTree, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View))
|
||||
if err := resultshandling.ValidatePrinter(scanInfo.ScanType, scanInfo.GetScanningContext(), format); err != nil {
|
||||
logger.L().Ctx(ctx).Fatal(err.Error())
|
||||
}
|
||||
|
||||
printerHandler := resultshandling.NewPrinter(ctx, format, scanInfo.FormatVersion, scanInfo.PrintAttackTree, scanInfo.VerboseMode, cautils.ViewTypes(scanInfo.View), clusterName)
|
||||
printerHandler.SetWriter(ctx, scanInfo.Output)
|
||||
outputPrinters = append(outputPrinters, printerHandler)
|
||||
}
|
||||
@@ -126,23 +119,18 @@ func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context) []printe
|
||||
|
||||
func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) {
|
||||
ctxInit, spanInit := otel.Tracer("").Start(ctx, "initialization")
|
||||
logger.InitLogger(iconlogger.LoggerName)
|
||||
logger.L().Start("Kubescape scanner initializing")
|
||||
|
||||
// ===================== Initialization =====================
|
||||
scanInfo.Init(ctxInit) // initialize scan info
|
||||
|
||||
interfaces := getInterfaces(ctxInit, scanInfo)
|
||||
|
||||
cautils.ClusterName = interfaces.tenantConfig.GetContextName() // TODO - Deprecated
|
||||
cautils.CustomerGUID = interfaces.tenantConfig.GetAccountID() // TODO - Deprecated
|
||||
interfaces.report.SetClusterName(interfaces.tenantConfig.GetContextName())
|
||||
interfaces.report.SetCustomerGUID(interfaces.tenantConfig.GetAccountID())
|
||||
interfaces.report.SetTenantConfig(interfaces.tenantConfig)
|
||||
|
||||
downloadReleasedPolicy := getter.NewDownloadReleasedPolicy() // download config inputs from github release
|
||||
|
||||
// set policy getter only after setting the customerGUID
|
||||
scanInfo.Getters.PolicyGetter = getPolicyGetter(ctxInit, scanInfo.UseFrom, interfaces.tenantConfig.GetTenantEmail(), scanInfo.FrameworkScan, downloadReleasedPolicy)
|
||||
scanInfo.Getters.PolicyGetter = getPolicyGetter(ctxInit, scanInfo.UseFrom, interfaces.tenantConfig.GetAccountID(), scanInfo.FrameworkScan, downloadReleasedPolicy)
|
||||
scanInfo.Getters.ControlsInputsGetter = getConfigInputsGetter(ctxInit, scanInfo.ControlsInputs, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
|
||||
scanInfo.Getters.ExceptionsGetter = getExceptionsGetter(ctxInit, scanInfo.UseExceptions, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
|
||||
scanInfo.Getters.AttackTracksGetter = getAttackTracksGetter(ctxInit, scanInfo.AttackTracks, interfaces.tenantConfig.GetAccountID(), downloadReleasedPolicy)
|
||||
@@ -165,7 +153,7 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
|
||||
|
||||
// ===================== policies =====================
|
||||
ctxPolicies, spanPolicies := otel.Tracer("").Start(ctxInit, "policies")
|
||||
policyHandler := policyhandler.NewPolicyHandler()
|
||||
policyHandler := policyhandler.NewPolicyHandler(interfaces.tenantConfig.GetContextName())
|
||||
scanData, err := policyHandler.CollectPolicies(ctxPolicies, scanInfo.PolicyIdentifier, scanInfo)
|
||||
if err != nil {
|
||||
spanInit.End()
|
||||
@@ -188,8 +176,8 @@ func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*res
|
||||
defer spanOpa.End()
|
||||
|
||||
deps := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig(), interfaces.tenantConfig.GetContextName())
|
||||
reportResults := opaprocessor.NewOPAProcessor(scanData, deps)
|
||||
if err := reportResults.ProcessRulesListener(ctxOpa, cautils.NewProgressHandler(""), scanInfo); err != nil {
|
||||
reportResults := opaprocessor.NewOPAProcessor(scanData, deps, interfaces.tenantConfig.GetContextName())
|
||||
if err = reportResults.ProcessRulesListener(ctxOpa, cautils.NewProgressHandler(""), scanInfo); err != nil {
|
||||
// TODO - do something
|
||||
return resultsHandling, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
)
|
||||
|
||||
func (ks *Kubescape) Submit(ctx context.Context, submitInterfaces cliinterfaces.SubmitInterfaces) error {
|
||||
|
||||
// list resources
|
||||
report, err := submitInterfaces.SubmitObjects.SetResourcesReport()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allresources, err := submitInterfaces.SubmitObjects.ListAllResources()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// report
|
||||
o := &cautils.OPASessionObj{
|
||||
Report: report,
|
||||
AllResources: allresources,
|
||||
Metadata: &report.Metadata,
|
||||
}
|
||||
if err := submitInterfaces.Reporter.Submit(ctx, o); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Data has been submitted successfully")
|
||||
submitInterfaces.Reporter.DisplayReportURL()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ks *Kubescape) SubmitExceptions(ctx context.Context, credentials *cautils.Credentials, excPath string) error {
|
||||
logger.L().Info("submitting exceptions", helpers.String("path", excPath))
|
||||
|
||||
// load cached config
|
||||
tenantConfig := getTenantConfig(credentials, "", "", getKubernetesApi())
|
||||
if err := tenantConfig.SetTenant(); err != nil {
|
||||
logger.L().Ctx(ctx).Warning("failed setting account ID", helpers.Error(err))
|
||||
}
|
||||
|
||||
// load exceptions from file
|
||||
loader := getter.NewLoadPolicy([]string{excPath})
|
||||
exceptions, err := loader.GetExceptions("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// login kubescape SaaS
|
||||
ksCloudAPI := getter.GetKSCloudAPIConnector()
|
||||
if err := ksCloudAPI.Login(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ksCloudAPI.PostExceptions(exceptions); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.L().Success("Exceptions submitted successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -4,12 +4,8 @@ import "io"
|
||||
|
||||
type SetConfig struct {
|
||||
Account string
|
||||
ClientID string
|
||||
SecretKey string
|
||||
CloudReportURL string
|
||||
CloudAPIURL string
|
||||
CloudUIURL string
|
||||
CloudAuthURL string
|
||||
}
|
||||
|
||||
type ViewConfig struct {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package v1
|
||||
|
||||
import "github.com/kubescape/kubescape/v2/core/cautils"
|
||||
|
||||
type DeleteExceptions struct {
|
||||
Credentials cautils.Credentials
|
||||
Exceptions []string
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
package v1
|
||||
|
||||
import "github.com/kubescape/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
|
||||
Identifier string // identifier of artifact to download
|
||||
Credentials cautils.Credentials
|
||||
Path string // directory to save artifact. Default is "~/.kubescape/"
|
||||
FileName string // can be empty
|
||||
Target string // type of artifact to download
|
||||
Identifier string // identifier of artifact to download
|
||||
AccountID string
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package v1
|
||||
|
||||
import "github.com/kubescape/kubescape/v2/core/cautils"
|
||||
|
||||
type ListPolicies struct {
|
||||
Target string
|
||||
Format string
|
||||
Credentials cautils.Credentials
|
||||
Target string
|
||||
Format string
|
||||
AccountID string
|
||||
}
|
||||
|
||||
type ListResponse struct {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package v1
|
||||
|
||||
import "github.com/kubescape/kubescape/v2/core/cautils"
|
||||
|
||||
type Submit struct {
|
||||
Credentials cautils.Credentials
|
||||
AccountID string
|
||||
}
|
||||
|
||||
type Delete struct {
|
||||
Credentials cautils.Credentials
|
||||
AccountID string
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/meta/cliinterfaces"
|
||||
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
|
||||
)
|
||||
@@ -16,18 +15,11 @@ type IKubescape interface {
|
||||
List(ctx context.Context, listPolicies *metav1.ListPolicies) error // TODO - return list response
|
||||
Download(ctx context.Context, downloadInfo *metav1.DownloadInfo) error // TODO - return downloaded policies
|
||||
|
||||
// submit
|
||||
Submit(ctx context.Context, submitInterfaces cliinterfaces.SubmitInterfaces) error // TODO - func should receive object
|
||||
SubmitExceptions(ctx context.Context, credentials *cautils.Credentials, excPath string) error // TODO - remove
|
||||
|
||||
// config
|
||||
SetCachedConfig(setConfig *metav1.SetConfig) error
|
||||
ViewCachedConfig(viewConfig *metav1.ViewConfig) error
|
||||
DeleteCachedConfig(ctx context.Context, deleteConfig *metav1.DeleteConfig) error
|
||||
|
||||
// delete
|
||||
DeleteExceptions(deleteexceptions *metav1.DeleteExceptions) error
|
||||
|
||||
// fix
|
||||
Fix(ctx context.Context, fixInfo *metav1.FixInfo) error
|
||||
}
|
||||
|
||||
@@ -31,19 +31,15 @@ type IJobProgressNotificationClient interface {
|
||||
Stop()
|
||||
}
|
||||
|
||||
const (
|
||||
heuristicAllocResources = 100
|
||||
heuristicAllocControls = 100
|
||||
)
|
||||
|
||||
// OPAProcessor processes Open Policy Agent rules.
|
||||
type OPAProcessor struct {
|
||||
clusterName string
|
||||
regoDependenciesData *resources.RegoDependenciesData
|
||||
*cautils.OPASessionObj
|
||||
opaRegisterOnce sync.Once
|
||||
}
|
||||
|
||||
func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *resources.RegoDependenciesData) *OPAProcessor {
|
||||
func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *resources.RegoDependenciesData, clusterName string) *OPAProcessor {
|
||||
if regoDependenciesData != nil && sessionObj != nil {
|
||||
regoDependenciesData.PostureControlInputs = sessionObj.RegoInputData.PostureControlInputs
|
||||
regoDependenciesData.DataControlInputs = sessionObj.RegoInputData.DataControlInputs
|
||||
@@ -52,6 +48,7 @@ func NewOPAProcessor(sessionObj *cautils.OPASessionObj, regoDependenciesData *re
|
||||
return &OPAProcessor{
|
||||
OPASessionObj: sessionObj,
|
||||
regoDependenciesData: regoDependenciesData,
|
||||
clusterName: clusterName,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +119,7 @@ func (opap *OPAProcessor) Process(ctx context.Context, policies *cautils.Policie
|
||||
func (opap *OPAProcessor) loggerStartScanning() {
|
||||
targetScan := opap.OPASessionObj.Metadata.ScanMetadata.ScanningTarget
|
||||
if reporthandlingv2.Cluster == targetScan {
|
||||
logger.L().Start("Scanning", helpers.String(targetScan.String(), cautils.ClusterName))
|
||||
logger.L().Start("Scanning", helpers.String(targetScan.String(), opap.clusterName))
|
||||
} else {
|
||||
logger.L().Start("Scanning " + targetScan.String())
|
||||
}
|
||||
@@ -131,7 +128,7 @@ func (opap *OPAProcessor) loggerStartScanning() {
|
||||
func (opap *OPAProcessor) loggerDoneScanning() {
|
||||
targetScan := opap.OPASessionObj.Metadata.ScanMetadata.ScanningTarget
|
||||
if reporthandlingv2.Cluster == targetScan {
|
||||
logger.L().StopSuccess("Done scanning", helpers.String(targetScan.String(), cautils.ClusterName))
|
||||
logger.L().StopSuccess("Done scanning", helpers.String(targetScan.String(), opap.clusterName))
|
||||
} else {
|
||||
logger.L().StopSuccess("Done scanning " + targetScan.String())
|
||||
}
|
||||
@@ -241,23 +238,14 @@ func (opap *OPAProcessor) processRule(ctx context.Context, rule *reporthandling.
|
||||
}
|
||||
|
||||
ruleResult.SetStatus(apis.StatusFailed, nil)
|
||||
for _, failedPath := range ruleResponse.FailedPaths {
|
||||
ruleResult.Paths = append(ruleResult.Paths, armotypes.PosturePaths{FailedPath: failedPath})
|
||||
}
|
||||
|
||||
for _, fixPath := range ruleResponse.FixPaths {
|
||||
ruleResult.Paths = append(ruleResult.Paths, armotypes.PosturePaths{FixPath: fixPath})
|
||||
}
|
||||
|
||||
if ruleResponse.FixCommand != "" {
|
||||
ruleResult.Paths = append(ruleResult.Paths, armotypes.PosturePaths{FixCommand: ruleResponse.FixCommand})
|
||||
}
|
||||
ruleResult.Paths = appendPaths(ruleResult.Paths, ruleResponse.FailedPaths, ruleResponse.FixPaths, ruleResponse.FixCommand, failedResource.GetID())
|
||||
// if ruleResponse has relatedObjects, add it to ruleResult
|
||||
if len(ruleResponse.RelatedObjects) > 0 {
|
||||
for _, relatedObject := range ruleResponse.RelatedObjects {
|
||||
wl := objectsenvelopes.NewObject(relatedObject.Object)
|
||||
if wl != nil {
|
||||
ruleResult.RelatedResourcesIDs = append(ruleResult.RelatedResourcesIDs, wl.GetID())
|
||||
ruleResult.Paths = appendPaths(ruleResult.Paths, relatedObject.FailedPaths, relatedObject.FixPaths, relatedObject.FixCommand, wl.GetID())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,6 +257,20 @@ func (opap *OPAProcessor) processRule(ctx context.Context, rule *reporthandling.
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
// appendPaths appends the failedPaths, fixPaths and fixCommand to the paths slice with the resourceID
|
||||
func appendPaths(paths []armotypes.PosturePaths, failedPaths []string, fixPaths []armotypes.FixPath, fixCommand string, resourceID string) []armotypes.PosturePaths {
|
||||
for _, failedPath := range failedPaths {
|
||||
paths = append(paths, armotypes.PosturePaths{ResourceID: resourceID, FailedPath: failedPath})
|
||||
}
|
||||
for _, fixPath := range fixPaths {
|
||||
paths = append(paths, armotypes.PosturePaths{ResourceID: resourceID, FixPath: fixPath})
|
||||
}
|
||||
if fixCommand != "" {
|
||||
paths = append(paths, armotypes.PosturePaths{ResourceID: resourceID, FixCommand: fixCommand})
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
func (opap *OPAProcessor) runOPAOnSingleRule(ctx context.Context, rule *reporthandling.PolicyRule, k8sObjects []map[string]interface{}, getRuleData func(*reporthandling.PolicyRule) string, ruleRegoDependenciesData resources.RegoDependenciesData) ([]reporthandling.RuleResponse, error) {
|
||||
switch rule.RuleLanguage {
|
||||
case reporthandling.RegoLanguage, reporthandling.RegoLanguage2:
|
||||
@@ -374,11 +376,3 @@ func (opap *OPAProcessor) makeRegoDeps(configInputs []string, fixedControlInputs
|
||||
PostureControlInputs: postureControlInputs,
|
||||
}
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -192,7 +193,7 @@ func TestProcessResourcesResult(t *testing.T) {
|
||||
opaSessionObj.K8SResources = k8sResources
|
||||
opaSessionObj.AllResources[deployment.GetID()] = deployment
|
||||
|
||||
opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock())
|
||||
opap := NewOPAProcessor(opaSessionObj, resources.NewRegoDependenciesDataMock(), "test")
|
||||
opap.AllPolicies = policies
|
||||
opap.Process(context.TODO(), policies, nil)
|
||||
|
||||
@@ -306,8 +307,10 @@ func TestProcessRule(t *testing.T) {
|
||||
ControlConfigurations: map[string][]string{},
|
||||
Status: "failed",
|
||||
SubStatus: "",
|
||||
Paths: nil,
|
||||
Exception: nil,
|
||||
Paths: []armotypes.PosturePaths{
|
||||
{ResourceID: "/v1/default/Service/fake-service-1", FailedPath: "spec.type"},
|
||||
},
|
||||
Exception: nil,
|
||||
RelatedResourcesIDs: []string{
|
||||
"/v1/default/Service/fake-service-1",
|
||||
},
|
||||
@@ -333,3 +336,74 @@ func TestProcessRule(t *testing.T) {
|
||||
assert.Equal(t, tc.expectedResult, resources)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendPaths(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
paths []armotypes.PosturePaths
|
||||
failedPaths []string
|
||||
fixPaths []armotypes.FixPath
|
||||
fixCommand string
|
||||
resourceID string
|
||||
expected []armotypes.PosturePaths
|
||||
}{
|
||||
{
|
||||
name: "Only FailedPaths",
|
||||
paths: []armotypes.PosturePaths{{ResourceID: "1", FailedPath: "path1"}},
|
||||
failedPaths: []string{"path2", "path3"},
|
||||
resourceID: "2",
|
||||
expected: []armotypes.PosturePaths{
|
||||
{ResourceID: "1", FailedPath: "path1"},
|
||||
{ResourceID: "2", FailedPath: "path2"},
|
||||
{ResourceID: "2", FailedPath: "path3"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Only FixPaths",
|
||||
paths: []armotypes.PosturePaths{},
|
||||
fixPaths: []armotypes.FixPath{
|
||||
{Path: "path2", Value: "command2"},
|
||||
{Path: "path3", Value: "command3"},
|
||||
},
|
||||
resourceID: "2",
|
||||
expected: []armotypes.PosturePaths{
|
||||
{ResourceID: "2", FixPath: armotypes.FixPath{Path: "path2", Value: "command2"}},
|
||||
{ResourceID: "2", FixPath: armotypes.FixPath{Path: "path3", Value: "command3"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Only FixCommand",
|
||||
paths: []armotypes.PosturePaths{},
|
||||
fixCommand: "fix command",
|
||||
resourceID: "2",
|
||||
expected: []armotypes.PosturePaths{
|
||||
{ResourceID: "2", FixCommand: "fix command"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "All types of paths",
|
||||
paths: []armotypes.PosturePaths{{ResourceID: "1", FailedPath: "path1"}},
|
||||
failedPaths: []string{"path2"},
|
||||
fixPaths: []armotypes.FixPath{
|
||||
{Path: "path3", Value: "command3"},
|
||||
},
|
||||
fixCommand: "fix command",
|
||||
resourceID: "2",
|
||||
expected: []armotypes.PosturePaths{
|
||||
{ResourceID: "1", FailedPath: "path1"},
|
||||
{ResourceID: "2", FailedPath: "path2"},
|
||||
{ResourceID: "2", FixPath: armotypes.FixPath{Path: "path3", Value: "command3"}},
|
||||
{ResourceID: "2", FixCommand: "fix command"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := appendPaths(tt.paths, tt.failedPaths, tt.fixPaths, tt.fixCommand, tt.resourceID)
|
||||
if !reflect.DeepEqual(result, tt.expected) {
|
||||
t.Errorf("Expected %v, but got %v", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (opap *OPAProcessor) updateResults(ctx context.Context) {
|
||||
t.SetExceptions(
|
||||
resource,
|
||||
opap.Exceptions,
|
||||
cautils.ClusterName,
|
||||
opap.clusterName,
|
||||
opap.AllPolicies.Controls, // update status depending on action required
|
||||
resourcesresults.WithExceptionsProcessor(processor),
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ var policyHandlerInstance *PolicyHandler
|
||||
|
||||
// PolicyHandler
|
||||
type PolicyHandler struct {
|
||||
clusterName string
|
||||
getters *cautils.Getters
|
||||
cachedPolicyIdentifiers *TimedCache[[]string]
|
||||
cachedFrameworks *TimedCache[[]reporthandling.Framework]
|
||||
@@ -33,10 +34,11 @@ type PolicyHandler struct {
|
||||
|
||||
// NewPolicyHandler creates and returns an instance of the `PolicyHandler`. The function initializes the `PolicyHandler` only if it hasn't been previously created.
|
||||
// The PolicyHandler supports caching of downloaded policies and exceptions by setting the `POLICIES_CACHE_TTL` environment variable (default is no caching).
|
||||
func NewPolicyHandler() *PolicyHandler {
|
||||
func NewPolicyHandler(clusterName string) *PolicyHandler {
|
||||
if policyHandlerInstance == nil {
|
||||
cacheTtl := getPoliciesCacheTtl()
|
||||
policyHandlerInstance = &PolicyHandler{
|
||||
clusterName: clusterName,
|
||||
cachedPolicyIdentifiers: NewTimedCache[[]string](cacheTtl),
|
||||
cachedFrameworks: NewTimedCache[[]reporthandling.Framework](cacheTtl),
|
||||
cachedExceptions: NewTimedCache[[]armotypes.PostureExceptionPolicy](cacheTtl),
|
||||
@@ -194,7 +196,7 @@ func (policyHandler *PolicyHandler) getExceptions() ([]armotypes.PostureExceptio
|
||||
return cachedExceptions, nil
|
||||
}
|
||||
|
||||
exceptions, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.ClusterName)
|
||||
exceptions, err := policyHandler.getters.ExceptionsGetter.GetExceptions(policyHandler.clusterName)
|
||||
if err == nil {
|
||||
policyHandler.cachedExceptions.Set(exceptions)
|
||||
}
|
||||
@@ -208,7 +210,7 @@ func (policyHandler *PolicyHandler) getControlInputs() (map[string][]string, err
|
||||
return cachedControlInputs, nil
|
||||
}
|
||||
|
||||
controlInputs, err := policyHandler.getters.ControlsInputsGetter.GetControlsInputs(cautils.ClusterName)
|
||||
controlInputs, err := policyHandler.getters.ControlsInputsGetter.GetControlsInputs(policyHandler.clusterName)
|
||||
if err == nil {
|
||||
policyHandler.cachedControlInputs.Set(controlInputs)
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# Integrate With Vulnerability Server
|
||||
|
||||
There are some controls that check the relation between the kubernetes manifest and vulnerabilities.
|
||||
For these controls to work properly, it is necessary to
|
||||
## Supported Servers
|
||||
* Armosec
|
||||
|
||||
# Integrate With Armosec Server
|
||||
|
||||
1. Navigate to the [armosec.io](https://cloud.armosec.io?utm_source=github&utm_medium=repository)
|
||||
2. Click Profile(top right icon)->"User Management"->"API Tokens" and Generate a token
|
||||
3. Copy the clientID and secretKey and run:
|
||||
```
|
||||
kubescape config set clientID <>
|
||||
```
|
||||
```
|
||||
kubescape config set secretKey <>
|
||||
```
|
||||
4. Confirm the keys are set
|
||||
```
|
||||
kubescape config view
|
||||
```
|
||||
Expecting:
|
||||
```
|
||||
{
|
||||
"accountID": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
"clientID": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
"secretKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||
}
|
||||
```
|
||||
> **Note**
|
||||
> If you are missing the `accountID` field, set it by running `kubescape config set accountID <>`
|
||||
|
||||
For CICD, set environments variables as following:
|
||||
```
|
||||
KS_ACCOUNT_ID // account id
|
||||
KS_CLIENT_ID // client id
|
||||
KS_SECRET_KEY // access key
|
||||
```
|
||||
@@ -1,100 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/containerscan"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
)
|
||||
|
||||
func NewKSAdaptor(api *getter.KSCloudAPI) *KSCivAdaptor {
|
||||
return &KSCivAdaptor{
|
||||
ksCloudAPI: api,
|
||||
}
|
||||
}
|
||||
|
||||
func (ksCivAdaptor *KSCivAdaptor) Login() error {
|
||||
if ksCivAdaptor.ksCloudAPI.IsLoggedIn() {
|
||||
return nil
|
||||
}
|
||||
return ksCivAdaptor.ksCloudAPI.Login()
|
||||
}
|
||||
func (ksCivAdaptor *KSCivAdaptor) GetImagesVulnerabilities(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageVulnerabilityReport, error) {
|
||||
resultList := make([]registryvulnerabilities.ContainerImageVulnerabilityReport, 0)
|
||||
for _, toPin := range imageIDs {
|
||||
imageID := toPin
|
||||
result, err := ksCivAdaptor.GetImageVulnerability(&imageID)
|
||||
if err != nil {
|
||||
logger.L().Debug("failed to get image vulnerabilities", helpers.String("image", imageID.Tag), helpers.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
resultList = append(resultList, *result)
|
||||
}
|
||||
|
||||
return resultList, nil
|
||||
}
|
||||
|
||||
func (ksCivAdaptor *KSCivAdaptor) GetImageVulnerability(imageID *registryvulnerabilities.ContainerImageIdentifier) (*registryvulnerabilities.ContainerImageVulnerabilityReport, error) {
|
||||
// First
|
||||
containerScanId, err := ksCivAdaptor.getImageLastScanId(imageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if containerScanId == "" {
|
||||
return nil, fmt.Errorf("last scan ID is empty")
|
||||
}
|
||||
|
||||
filter := []map[string]string{{"containersScanID": containerScanId}}
|
||||
pageSize := 300
|
||||
pageNumber := 1
|
||||
request := V2ListRequest{PageSize: &pageSize, PageNum: &pageNumber, InnerFilters: filter, OrderBy: "timestamp:desc"}
|
||||
requestBody, _ := json.Marshal(request)
|
||||
requestUrl := fmt.Sprintf("https://%s/api/v1/vulnerability/scanResultsDetails?customerGUID=%s", ksCivAdaptor.ksCloudAPI.GetCloudAPIURL(), ksCivAdaptor.ksCloudAPI.GetAccountID())
|
||||
|
||||
resp, err := ksCivAdaptor.ksCloudAPI.Post(requestUrl, map[string]string{"Content-Type": "application/json"}, requestBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scanDetailsResult := struct {
|
||||
Total struct {
|
||||
Value int `json:"value"`
|
||||
Relation string `json:"relation"`
|
||||
} `json:"total"`
|
||||
Response containerscan.VulnerabilitiesList `json:"response"`
|
||||
Cursor string `json:"cursor"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal([]byte(resp), &scanDetailsResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vulnerabilities := responseObjectToVulnerabilities(scanDetailsResult.Response)
|
||||
|
||||
resultImageVulnerabilityReport := registryvulnerabilities.ContainerImageVulnerabilityReport{
|
||||
ImageID: *imageID,
|
||||
Vulnerabilities: vulnerabilities,
|
||||
}
|
||||
|
||||
return &resultImageVulnerabilityReport, nil
|
||||
}
|
||||
|
||||
func (ksCivAdaptor *KSCivAdaptor) DescribeAdaptor() string {
|
||||
return "armo image vulnerabilities scanner, docs: https://hub.armosec.io/docs/configuration-of-image-vulnerabilities"
|
||||
}
|
||||
|
||||
func (ksCivAdaptor *KSCivAdaptor) GetImagesInformation(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageInformation, error) {
|
||||
// TODO
|
||||
return []registryvulnerabilities.ContainerImageInformation{}, nil
|
||||
}
|
||||
|
||||
func (ksCivAdaptor *KSCivAdaptor) GetImagesScanStatus(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageScanStatus, error) {
|
||||
// TODO
|
||||
return []registryvulnerabilities.ContainerImageScanStatus{}, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSum(t *testing.T) {
|
||||
var err error
|
||||
var adaptor registryvulnerabilities.IContainerImageVulnerabilityAdaptor
|
||||
|
||||
adaptor, err = NewArmoAdaptorMock()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, adaptor.Login())
|
||||
|
||||
imageVulnerabilityReport, err := adaptor.GetImageVulnerability(®istryvulnerabilities.ContainerImageIdentifier{Tag: "gke.gcr.io/gcp-compute-persistent-disk-csi-driver:v1.3.4-gke.0"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 25, len(imageVulnerabilityReport.Vulnerabilities))
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,68 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/containerscan"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
)
|
||||
|
||||
func (armoCivAdaptor *KSCivAdaptor) getImageLastScanId(imageID *registryvulnerabilities.ContainerImageIdentifier) (string, error) {
|
||||
filter := []map[string]string{{"imageTag": imageID.Tag, "status": "Success"}}
|
||||
pageSize := 1
|
||||
pageNumber := 1
|
||||
request := V2ListRequest{PageSize: &pageSize, PageNum: &pageNumber, InnerFilters: filter, OrderBy: "timestamp:desc"}
|
||||
requestBody, _ := json.Marshal(request)
|
||||
requestUrl := fmt.Sprintf("https://%s/api/v1/vulnerability/scanResultsSumSummary?customerGUID=%s", armoCivAdaptor.ksCloudAPI.GetCloudAPIURL(), armoCivAdaptor.ksCloudAPI.GetAccountID())
|
||||
|
||||
resp, err := armoCivAdaptor.ksCloudAPI.Post(requestUrl, map[string]string{"Content-Type": "application/json"}, requestBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
scanSummartResult := struct {
|
||||
Total struct {
|
||||
Value int `json:"value"`
|
||||
Relation string `json:"relation"`
|
||||
} `json:"total"`
|
||||
Response []containerscan.ElasticContainerScanSummaryResult `json:"response"`
|
||||
Cursor string `json:"cursor"`
|
||||
}{}
|
||||
err = json.Unmarshal([]byte(resp), &scanSummartResult)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(scanSummartResult.Response) < pageSize {
|
||||
return "", fmt.Errorf("did not get response for image %s", imageID.Tag)
|
||||
}
|
||||
|
||||
return scanSummartResult.Response[0].ContainerScanID, nil
|
||||
}
|
||||
|
||||
func responseObjectToVulnerabilities(vulnerabilitiesList containerscan.VulnerabilitiesList) []registryvulnerabilities.Vulnerability {
|
||||
vulnerabilities := make([]registryvulnerabilities.Vulnerability, len(vulnerabilitiesList))
|
||||
for i, vulnerabilityEntry := range vulnerabilitiesList {
|
||||
vulnerabilities[i].Description = vulnerabilityEntry.Description
|
||||
vulnerabilities[i].Fixes = make([]registryvulnerabilities.FixedIn, len(vulnerabilityEntry.Fixes))
|
||||
for j, fix := range vulnerabilityEntry.Fixes {
|
||||
vulnerabilities[i].Fixes[j].ImgTag = fix.ImgTag
|
||||
vulnerabilities[i].Fixes[j].Name = fix.Name
|
||||
vulnerabilities[i].Fixes[j].Version = fix.Version
|
||||
}
|
||||
vulnerabilities[i].HealthStatus = vulnerabilityEntry.HealthStatus
|
||||
vulnerabilities[i].Link = vulnerabilityEntry.Link
|
||||
vulnerabilities[i].Metadata = vulnerabilityEntry.Metadata
|
||||
vulnerabilities[i].Name = vulnerabilityEntry.Name
|
||||
vulnerabilities[i].PackageVersion = vulnerabilityEntry.PackageVersion
|
||||
vulnerabilities[i].RelatedPackageName = vulnerabilityEntry.RelatedPackageName
|
||||
vulnerabilities[i].Relevancy = vulnerabilityEntry.Relevancy
|
||||
vulnerabilities[i].Severity = vulnerabilityEntry.Severity
|
||||
vulnerabilities[i].UrgentCount = vulnerabilityEntry.UrgentCount
|
||||
vulnerabilities[i].Categories = registryvulnerabilities.Categories{
|
||||
IsRCE: vulnerabilityEntry.Categories.IsRCE,
|
||||
}
|
||||
}
|
||||
return vulnerabilities
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
)
|
||||
|
||||
type V2ListRequest struct {
|
||||
// properties of the requested next page
|
||||
// Use ValidatePageProperties to set PageSize field
|
||||
PageSize *int `json:"pageSize,omitempty"`
|
||||
// One can leave it empty for 0, then call ValidatePageProperties
|
||||
PageNum *int `json:"pageNum,omitempty"`
|
||||
// The time window of the list to return. Default: since - beginning of the time, until - now.
|
||||
Since *time.Time `json:"since,omitempty"`
|
||||
Until *time.Time `json:"until,omitempty"`
|
||||
// Which elements of the list to return, each field can hold multiple values separated by comma
|
||||
// Example: ": {"severity": "High,Medium", "type": "61539,30303"}
|
||||
// An empty map means "return the complete list"
|
||||
InnerFilters []map[string]string `json:"innerFilters,omitempty"`
|
||||
// How to order (sort) the list, field name + sort order (asc/desc), like https://www.w3schools.com/sql/sql_orderby.asp
|
||||
// Example: "timestamp:asc,severity:desc"
|
||||
OrderBy string `json:"orderBy,omitempty"`
|
||||
// Cursor to the next page of former request. Not supported yet
|
||||
// Cursor cannot be used with another parameters of this struct
|
||||
Cursor string `json:"cursor,omitempty"`
|
||||
// FieldsList allow us to return only subset of the source document fields
|
||||
// Don't expose FieldsList outside without well designed decision
|
||||
FieldsList []string `json:"includeFields,omitempty"`
|
||||
FieldsReverseKeywordMap map[string]string `json:"-,omitempty"`
|
||||
}
|
||||
|
||||
type KSCivAdaptor struct {
|
||||
ksCloudAPI *getter.KSCloudAPI
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
# Container image vulnerability adaptor interface
|
||||
|
||||
## High level design of Kubescape
|
||||
|
||||
### Layers
|
||||
|
||||
* Controls and Rules: that actual control logic implementation, the "tests" themselves. Implemented in rego.
|
||||
* OPA engine: the [OPA](https://github.com/open-policy-agent/opa) rego interpreter.
|
||||
* Rules processor: Kubescape component, it enumerates and runs the controls while preparing all of the input data that the controls need for running.
|
||||
* Data sources: set of different modules providing data to the Rules processor so it can run the controls with them. Examples: Kubernetes objects, cloud vendor API objects and adding in this proposal the vulnerability information.
|
||||
* Cloud Image Vulnerability adaption interface: the subject of this proposal, it gives a common interface for different registry/vulnerability vendors to adapt to.
|
||||
* CIV adaptors: specific implementation of the CIV interface, example Harbor adaption.
|
||||
```
|
||||
-----------------------
|
||||
| Controls/Rules (rego) |
|
||||
-----------------------
|
||||
|
|
||||
-----------------------
|
||||
| OPA engine |
|
||||
-----------------------
|
||||
|
|
||||
-----------------------
|
||||
| Rules processor |
|
||||
-----------------------
|
||||
|
|
||||
-----------------------
|
||||
| Data sources |
|
||||
-----------------------
|
||||
|
|
||||
=======================
|
||||
| CIV adaption interface| <- Adding this layer in this proposal
|
||||
=======================
|
||||
|
|
||||
-----------------------
|
||||
| Specific CIV adaptors | <- Will be implemented based on this proposal
|
||||
-----------------------
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Functionalities to cover
|
||||
|
||||
The interface needs to cover the following functionalities:
|
||||
|
||||
* Authentication against the information source (abstracted login)
|
||||
* Triggering image scan (if applicable, the source might store vulnerabilities for images but cannot scan alone)
|
||||
* Reading image scan status (with last scan date and etc.)
|
||||
* Getting vulnerability information for a given image
|
||||
* Getting image information
|
||||
* Image manifests
|
||||
* Image BOMs (bill of material)
|
||||
|
||||
## Go API proposal
|
||||
|
||||
```go
|
||||
|
||||
/*type ContainerImageRegistryCredentials struct {
|
||||
Password string
|
||||
Tag string
|
||||
Hash string
|
||||
}*/
|
||||
|
||||
type ContainerImageIdentifier struct {
|
||||
Registry string
|
||||
Repository string
|
||||
Tag string
|
||||
Hash string
|
||||
}
|
||||
|
||||
type ContainerImageScanStatus struct {
|
||||
ImageID ContainerImageIdentifier
|
||||
IsScanAvailable bool
|
||||
IsBomAvailable bool
|
||||
LastScanDate time.Time
|
||||
}
|
||||
|
||||
type ContainerImageVulnerabilityReport struct {
|
||||
ImageID ContainerImageIdentifier
|
||||
// TBD
|
||||
}
|
||||
|
||||
type ContainerImageInformation struct {
|
||||
ImageID ContainerImageIdentifier
|
||||
Bom []string
|
||||
ImageManifest Manifest // will use here Docker package definition
|
||||
}
|
||||
|
||||
type IContainerImageVulnerabilityAdaptor interface {
|
||||
// Credentials are coming from user input (CLI or configuration file) and they are abstracted at string to string map level
|
||||
// so an example use would be like registry: "simpledockerregistry:80" and credentials like {"username":"joedoe","password":"abcd1234"}
|
||||
Login(registry string, credentials map[string]string) error
|
||||
|
||||
// For "help" purposes
|
||||
DescribeAdaptor() string
|
||||
|
||||
GetImagesScanStatus(imageIDs []ContainerImageIdentifier) ([]ContainerImageScanStatus, error)
|
||||
|
||||
GetImagesVulnerabilties(imageIDs []ContainerImageIdentifier) ([]ContainerImageVulnerabilityReport, error)
|
||||
|
||||
GetImagesInformation(imageIDs []ContainerImageIdentifier) ([]ContainerImageInformation, error)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
# Integration
|
||||
|
||||
# Input
|
||||
|
||||
The objects received from the interface will be converted to an IMetadata compatible objects as following
|
||||
|
||||
```json
|
||||
{
|
||||
"apiVersion": "armo.vuln.images/v1",
|
||||
"kind": "ImageVulnerabilities",
|
||||
"metadata": {
|
||||
"name": "nginx:latest"
|
||||
},
|
||||
"data": {
|
||||
// list of vulnerabilities
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# Output
|
||||
|
||||
The rego results will be a combination of the k8s artifact and the list of relevant CVEs for the control
|
||||
|
||||
```json
|
||||
{
|
||||
"apiVersion": "armo.vuln/v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "nginx"
|
||||
"namespace": "default"
|
||||
|
||||
},
|
||||
"relatedObjects": [
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "nginx"
|
||||
"namespace": "default"
|
||||
},
|
||||
"spec": {
|
||||
// podSpec
|
||||
},
|
||||
},
|
||||
{
|
||||
"apiVersion": "image.vulnscan.com/v1",
|
||||
"kind": "ImageVulnerabilities",
|
||||
"metadata": {
|
||||
"name": "nginx:latest",
|
||||
},
|
||||
"data": {
|
||||
// list of vulnerabilities
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
# GCP Adaptor
|
||||
|
||||
### How we add gcp adaptor
|
||||
|
||||
As there can be possiblities of use of multiple registries we check for each adaptor if we have required credentias. For every adaptor having credentials we append the adaptor to the adaptors slice.
|
||||
|
||||
Particularly for gcp, we frstly bring the `gcpCloudAPI` from the connector. We still haven't created a proper function that initiats the gcpCloudAPI with projectId, credentialsPath, credentialsCheck fields. We check for `credentialsCheck` bool which is set true when we have credentials(to be set when initializing the gcpCloudAPI)
|
||||
|
||||
### How we fetch vulnerabilities for images
|
||||
|
||||
Step 1:
|
||||
Get container analysis client
|
||||
For this we needs credentials of the service account. Out of few approaches here we are using [JSON key file](https://cloud.google.com/container-registry/docs/advanced-authentication#json-key) for credentials and path to this file should be stored in `credentialsPath`
|
||||
|
||||
Step 2:
|
||||
Do ListOccurrenceRequest
|
||||
For this we need the `projectID` and the `resourceUrl`. ProjectID should be provided by the users and resourceUrl is processed imageTag that we get from kubescape resources
|
||||
|
||||
Step 3:
|
||||
Get Occurrence iterator
|
||||
We use context and the request from the ListOccurenceRequest to get the iterators
|
||||
|
||||
|
||||
### How we convert the response to Vulnerabilities
|
||||
|
||||
Response from the iterator has two type of kinds i.e. Discovery and Vulnerabilties and both has differnent struct
|
||||
|
||||
### How can this adaptor be used by the user
|
||||
|
||||
To know about GCR service accounts follow https://cloud.google.com/container-registry/docs/gcr-service-account
|
||||
export variables
|
||||
`export KS_GCP_CREDENTIALS_PATH=<path to service account credentials file>`
|
||||
`export KS_GCP_PROJECT_ID=<your project ID>`
|
||||
@@ -1,24 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
)
|
||||
|
||||
type GCPAdaptor struct {
|
||||
GCPCloudAPI *getter.GCPCloudAPI
|
||||
}
|
||||
|
||||
type Mock struct {
|
||||
Name string
|
||||
Notename string
|
||||
CvssScore float32
|
||||
CreatedTime int64
|
||||
UpdatedTime int64
|
||||
Type string
|
||||
ShortDescription string
|
||||
AffectedCPEURI string
|
||||
AffectedPackage string
|
||||
FixAvailable bool
|
||||
AffectedVersion string
|
||||
FixedVersion string
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
containeranalysis "cloud.google.com/go/containeranalysis/apiv1"
|
||||
"github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
grafeaspb "google.golang.org/genproto/googleapis/grafeas/v1"
|
||||
)
|
||||
|
||||
func NewGCPAdaptor(GCPCloudAPI *getter.GCPCloudAPI) *GCPAdaptor {
|
||||
return &GCPAdaptor{
|
||||
GCPCloudAPI: GCPCloudAPI,
|
||||
}
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) Login() error {
|
||||
client, err := containeranalysis.NewClient(GCPAdaptor.GCPCloudAPI.GetContext(), option.WithCredentialsFile(GCPAdaptor.GCPCloudAPI.GetCredentialsPath()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
GCPAdaptor.GCPCloudAPI.SetClient(client)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) GetImagesVulnerabilities(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageVulnerabilityReport, error) {
|
||||
resultList := make([]registryvulnerabilities.ContainerImageVulnerabilityReport, 0)
|
||||
for _, toPin := range imageIDs {
|
||||
imageID := toPin
|
||||
result, err := GCPAdaptor.GetImageVulnerability(&imageID)
|
||||
if err != nil {
|
||||
logger.L().Debug("failed to get image vulnerabilities", helpers.String("image", imageID.Tag), helpers.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
resultList = append(resultList, *result)
|
||||
}
|
||||
|
||||
return resultList, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) GetImageVulnerability(imageID *registryvulnerabilities.ContainerImageIdentifier) (*registryvulnerabilities.ContainerImageVulnerabilityReport, error) {
|
||||
|
||||
resourceUrl := fmt.Sprintf("https://%s", imageID.Tag)
|
||||
|
||||
req := &grafeaspb.ListOccurrencesRequest{
|
||||
Parent: fmt.Sprintf("projects/%s", GCPAdaptor.GCPCloudAPI.GetProjectID()),
|
||||
Filter: fmt.Sprintf(`resourceUrl=%q`, resourceUrl),
|
||||
}
|
||||
|
||||
it := GCPAdaptor.GCPCloudAPI.GetClient().GetGrafeasClient().ListOccurrences(GCPAdaptor.GCPCloudAPI.GetContext(), req)
|
||||
occs := []*grafeaspb.Occurrence{}
|
||||
var count int
|
||||
for {
|
||||
occ, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
occs = append(occs, occ)
|
||||
count++
|
||||
}
|
||||
vulnerabilities := responseObjectToVulnerabilities(occs, count)
|
||||
|
||||
resultImageVulnerabilityReport := registryvulnerabilities.ContainerImageVulnerabilityReport{
|
||||
ImageID: *imageID,
|
||||
Vulnerabilities: vulnerabilities,
|
||||
}
|
||||
return &resultImageVulnerabilityReport, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) DescribeAdaptor() string {
|
||||
return "GCP image vulnerabilities scanner, docs: https://cloud.google.com/container-analysis/docs/container-analysis"
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) GetImagesInformation(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageInformation, error) {
|
||||
// TODO
|
||||
return []registryvulnerabilities.ContainerImageInformation{}, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptor *GCPAdaptor) GetImagesScanStatus(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageScanStatus, error) {
|
||||
// TODO
|
||||
return []registryvulnerabilities.ContainerImageScanStatus{}, nil
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSum(t *testing.T) {
|
||||
var err error
|
||||
var adaptor registryvulnerabilities.IContainerImageVulnerabilityAdaptor
|
||||
|
||||
adaptor, err = NewGCPAdaptorMock()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, adaptor.Login())
|
||||
|
||||
imageVulnerabilityReports, err := adaptor.GetImagesVulnerabilities([]registryvulnerabilities.ContainerImageIdentifier{{Tag: "gcr.io/myproject/nginx@sha256:1XXXXX"}, {Tag: "gcr.io/myproject/nginx@sha256:2XXXXX"}})
|
||||
assert.NoError(t, err)
|
||||
|
||||
for i := range imageVulnerabilityReports {
|
||||
var length int
|
||||
if i == 0 {
|
||||
length = 5
|
||||
} else if i == 1 {
|
||||
length = 3
|
||||
}
|
||||
assert.Equal(t, length, len(imageVulnerabilityReports[i].Vulnerabilities))
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
grafeaspb "google.golang.org/genproto/googleapis/grafeas/v1"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type GCPAdaptorMock struct {
|
||||
resultList []registryvulnerabilities.ContainerImageVulnerabilityReport
|
||||
}
|
||||
|
||||
func NewGCPAdaptorMock() (*GCPAdaptorMock, error) {
|
||||
return &GCPAdaptorMock{}, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) Login() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) GetImagesVulnerabilities(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageVulnerabilityReport, error) {
|
||||
resultList := make([]registryvulnerabilities.ContainerImageVulnerabilityReport, 0)
|
||||
for _, toPin := range imageIDs {
|
||||
imageID := toPin
|
||||
result, err := GCPAdaptorMock.GetImageVulnerability(&imageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resultList = append(resultList, *result)
|
||||
|
||||
return resultList, nil //nolint:staticcheck // we return at once and shorten the mocked result
|
||||
}
|
||||
|
||||
GCPAdaptorMock.resultList = resultList
|
||||
return GCPAdaptorMock.resultList, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) GetImageVulnerability(imageID *registryvulnerabilities.ContainerImageIdentifier) (*registryvulnerabilities.ContainerImageVulnerabilityReport, error) {
|
||||
vulnerability := []*grafeaspb.Occurrence_Vulnerability{}
|
||||
occurrence := []*grafeaspb.Occurrence{}
|
||||
arr := GetMockData()
|
||||
|
||||
for i := range arr {
|
||||
if imageID.Tag == "gcr.io/myproject/nginx@sha256:2XXXXX" && i == 4 {
|
||||
break
|
||||
}
|
||||
vulnerability = append(vulnerability, &grafeaspb.Occurrence_Vulnerability{
|
||||
Vulnerability: &grafeaspb.VulnerabilityOccurrence{
|
||||
Type: arr[i].Type,
|
||||
CvssScore: arr[i].CvssScore,
|
||||
ShortDescription: arr[i].ShortDescription,
|
||||
PackageIssue: []*grafeaspb.VulnerabilityOccurrence_PackageIssue{
|
||||
{
|
||||
FixedVersion: &grafeaspb.Version{
|
||||
FullName: arr[i].FixedVersion,
|
||||
},
|
||||
AffectedVersion: &grafeaspb.Version{
|
||||
FullName: arr[i].AffectedVersion,
|
||||
},
|
||||
AffectedCpeUri: arr[i].AffectedCPEURI,
|
||||
AffectedPackage: arr[i].AffectedPackage,
|
||||
},
|
||||
},
|
||||
FixAvailable: arr[i].FixAvailable,
|
||||
},
|
||||
})
|
||||
|
||||
occurrence = append(occurrence, &grafeaspb.Occurrence{
|
||||
Name: arr[i].Name,
|
||||
Kind: grafeaspb.NoteKind_ATTESTATION,
|
||||
NoteName: arr[i].Notename,
|
||||
CreateTime: ×tamppb.Timestamp{
|
||||
Seconds: arr[i].CreatedTime,
|
||||
},
|
||||
UpdateTime: ×tamppb.Timestamp{
|
||||
Seconds: arr[i].UpdatedTime,
|
||||
},
|
||||
Details: vulnerability[i],
|
||||
})
|
||||
}
|
||||
|
||||
vulnerabilities := responseObjectToVulnerabilities(occurrence, 5)
|
||||
|
||||
resultImageVulnerabilityReport := registryvulnerabilities.ContainerImageVulnerabilityReport{
|
||||
ImageID: *imageID,
|
||||
Vulnerabilities: vulnerabilities,
|
||||
}
|
||||
return &resultImageVulnerabilityReport, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) DescribeAdaptor() string {
|
||||
// TODO
|
||||
return ""
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) GetImagesInformation(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageInformation, error) {
|
||||
// TODO
|
||||
return []registryvulnerabilities.ContainerImageInformation{}, nil
|
||||
}
|
||||
|
||||
func (GCPAdaptorMock *GCPAdaptorMock) GetImagesScanStatus(imageIDs []registryvulnerabilities.ContainerImageIdentifier) ([]registryvulnerabilities.ContainerImageScanStatus, error) {
|
||||
// TODO
|
||||
return []registryvulnerabilities.ContainerImageScanStatus{}, nil
|
||||
}
|
||||
|
||||
//==============================================================================================================================
|
||||
//==============================================================================================================================
|
||||
//==============================================================================================================================
|
||||
|
||||
func GetMockData() []Mock {
|
||||
arr := []Mock{
|
||||
{
|
||||
Name: "projects/stable-furnace-356005/occurrences/41fd9fec-6fab-4531-a4ee-e7b97d518554",
|
||||
Notename: "projects/goog-vulnz/notes/CVE-2009-4487",
|
||||
CvssScore: 6.8,
|
||||
CreatedTime: 1661061853,
|
||||
UpdatedTime: 1661061853,
|
||||
Type: "OS",
|
||||
ShortDescription: "CVE-2009-4487",
|
||||
AffectedCPEURI: "cpe:/o:debian:debian_linux:11",
|
||||
AffectedPackage: "nginx",
|
||||
FixAvailable: true,
|
||||
AffectedVersion: "1.23.1-1~bullseye",
|
||||
FixedVersion: "",
|
||||
},
|
||||
{
|
||||
Name: "projects/stable-furnace-356005/occurrences/b28fa29f-5c2b-45c7-9727-2f1f02ed1957",
|
||||
Notename: "projects/goog-vulnz/notes/CVE-2017-17740",
|
||||
CvssScore: 2.3,
|
||||
CreatedTime: 3237628,
|
||||
UpdatedTime: 5989893,
|
||||
Type: "OS",
|
||||
ShortDescription: "CVE-2017-17740",
|
||||
AffectedCPEURI: "cpe:/o:debian:debian_linux:11",
|
||||
AffectedPackage: "openldap",
|
||||
FixAvailable: false,
|
||||
AffectedVersion: "1.3.5",
|
||||
FixedVersion: "1.3.5",
|
||||
},
|
||||
{
|
||||
Name: "projects/stable-furnace-356005/occurrences/b28fa29f-5c2b-45c7-9727-2f1f02ed1957",
|
||||
Notename: "projects/goog-vulnz/notes/CVE-2017-17740",
|
||||
CvssScore: 2.3,
|
||||
CreatedTime: 3237628,
|
||||
UpdatedTime: 5989893,
|
||||
Type: "OS",
|
||||
ShortDescription: "CVE-2017-17740",
|
||||
AffectedCPEURI: "cpe:/o:debian:debian_linux:11",
|
||||
AffectedPackage: "openldap",
|
||||
FixAvailable: false,
|
||||
AffectedVersion: "1.3.5",
|
||||
FixedVersion: "1.3.5",
|
||||
},
|
||||
{
|
||||
Name: "projects/stable-furnace-356005/occurrences/b28fa29f-5c2b-45c7-9727-2f1f02ed1957",
|
||||
Notename: "projects/goog-vulnz/notes/CVE-2017-17740",
|
||||
CvssScore: 2.3,
|
||||
CreatedTime: 3237628,
|
||||
UpdatedTime: 5989893,
|
||||
Type: "OS",
|
||||
ShortDescription: "CVE-2017-17740",
|
||||
AffectedCPEURI: "cpe:/o:debian:debian_linux:11",
|
||||
AffectedPackage: "openldap",
|
||||
FixAvailable: false,
|
||||
AffectedVersion: "1.3.5",
|
||||
FixedVersion: "1.3.5",
|
||||
},
|
||||
{
|
||||
Name: "projects/stable-furnace-356005/occurrences/b28fa29f-5c2b-45c7-9727-2f1f02ed1957",
|
||||
Notename: "projects/goog-vulnz/notes/CVE-2017-17740",
|
||||
CvssScore: 2.3,
|
||||
CreatedTime: 3237628,
|
||||
UpdatedTime: 5989893,
|
||||
Type: "OS",
|
||||
ShortDescription: "CVE-2017-17740",
|
||||
AffectedCPEURI: "cpe:/o:debian:debian_linux:11",
|
||||
AffectedPackage: "openldap",
|
||||
FixAvailable: false,
|
||||
AffectedVersion: "1.3.5",
|
||||
FixedVersion: "1.3.5",
|
||||
},
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
grafeaspb "google.golang.org/genproto/googleapis/grafeas/v1"
|
||||
)
|
||||
|
||||
func responseObjectToVulnerabilities(vulnerabilityList []*grafeaspb.Occurrence, count int) []registryvulnerabilities.Vulnerability {
|
||||
vulnerabilities := make([]registryvulnerabilities.Vulnerability, count)
|
||||
for i, vulnerabilityEntry := range vulnerabilityList {
|
||||
if vulnerabilityEntry.GetKind().String() != "DISCOVERY" {
|
||||
vulnerabilities[i].Name = vulnerabilityEntry.Name
|
||||
vulnerabilities[i].NoteName = vulnerabilityEntry.NoteName
|
||||
vulnerabilities[i].CreateTime = vulnerabilityEntry.CreateTime.AsTime()
|
||||
vulnerabilities[i].UpdateTime = vulnerabilityEntry.UpdateTime.AsTime()
|
||||
vulnerabilities[i].CVSS = vulnerabilityEntry.GetVulnerability().CvssScore
|
||||
vulnerabilities[i].AffectedCPEURI = vulnerabilityEntry.GetVulnerability().PackageIssue[0].AffectedCpeUri
|
||||
vulnerabilities[i].AffectedPackage = vulnerabilityEntry.GetVulnerability().PackageIssue[0].AffectedPackage
|
||||
vulnerabilities[i].AffectedVersion = vulnerabilityEntry.GetVulnerability().PackageIssue[0].AffectedVersion.FullName
|
||||
vulnerabilities[i].FixedVersion = vulnerabilityEntry.GetVulnerability().PackageIssue[0].FixedVersion.FullName
|
||||
vulnerabilities[i].FixedCPEURI = vulnerabilityEntry.GetVulnerability().PackageIssue[0].FixedCpeUri
|
||||
vulnerabilities[i].FixedPackege = vulnerabilityEntry.GetVulnerability().PackageIssue[0].FixedPackage
|
||||
vulnerabilities[i].FixAvailablePackage = vulnerabilityEntry.GetVulnerability().PackageIssue[0].GetFixAvailable()
|
||||
vulnerabilities[i].PackageType = vulnerabilityEntry.GetVulnerability().PackageIssue[0].PackageType
|
||||
vulnerabilities[i].EffectiveSeverityPackage = vulnerabilityEntry.GetVulnerability().PackageIssue[0].EffectiveSeverity.String()
|
||||
vulnerabilities[i].AffectedPackage = vulnerabilityEntry.GetVulnerability().PackageIssue[0].AffectedPackage
|
||||
vulnerabilities[i].Severity = vulnerabilityEntry.GetVulnerability().Severity.Enum().String()
|
||||
vulnerabilities[i].ShortDescription = vulnerabilityEntry.GetVulnerability().ShortDescription
|
||||
vulnerabilities[i].LongDescription = vulnerabilityEntry.GetVulnerability().LongDescription
|
||||
} else {
|
||||
vulnerabilities[i].Description = vulnerabilityEntry.GetDiscovery().String()
|
||||
}
|
||||
}
|
||||
|
||||
return vulnerabilities
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package registryvulnerabilities
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ContainerImageIdentifier struct {
|
||||
Registry string
|
||||
Repository string
|
||||
Tag string
|
||||
Hash string
|
||||
}
|
||||
|
||||
type ContainerImageScanStatus struct {
|
||||
ImageID ContainerImageIdentifier
|
||||
IsScanAvailable bool
|
||||
IsBomAvailable bool
|
||||
LastScanDate time.Time
|
||||
}
|
||||
|
||||
type FixedIn struct {
|
||||
Name string `json:"name"`
|
||||
ImgTag string `json:"imageTag"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
type Categories struct {
|
||||
IsRCE bool `json:"isRce"`
|
||||
}
|
||||
|
||||
type Vulnerability struct {
|
||||
Name string `json:"name"`
|
||||
RelatedPackageName string `json:"packageName"`
|
||||
PackageVersion string `json:"packageVersion"`
|
||||
Link string `json:"link"`
|
||||
Description string `json:"description"`
|
||||
Severity string `json:"severity"`
|
||||
Metadata interface{} `json:"metadata"`
|
||||
Fixes []FixedIn `json:"fixedIn"`
|
||||
Relevancy string `json:"relevant"` // use the related enum
|
||||
UrgentCount int `json:"urgent"`
|
||||
NeglectedCount int `json:"neglected"`
|
||||
HealthStatus string `json:"healthStatus"`
|
||||
Categories Categories `json:"categories"`
|
||||
NoteName string `json:",omitempty"`
|
||||
CreateTime time.Time `json:",omitempty"`
|
||||
UpdateTime time.Time `json:",omitempty"` // Vulnerablity started
|
||||
CVSS float32 `json:",omitempty"` // other cvss versions are available
|
||||
AffectedCPEURI string `json:",omitempty"` // Package issue
|
||||
AffectedPackage string `json:",omitempty"`
|
||||
AffectedVersion string `json:",omitempty"`
|
||||
FixedVersion string `json:",omitempty"`
|
||||
FixedCPEURI string `json:",omitempty"`
|
||||
FixedPackege string `json:",omitempty"`
|
||||
FixAvailablePackage bool `json:",omitempty"`
|
||||
PackageType string `json:",omitempty"`
|
||||
EffectiveSeverityPackage string `json:",omitempty"`
|
||||
ShortDescription string `json:",omitempty"` // Package issue ends
|
||||
LongDescription string `json:",omitempty"`
|
||||
EffectiveSeverity string `json:",omitempty"`
|
||||
FixAvailable bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
type ContainerImageVulnerabilityReport struct {
|
||||
ImageID ContainerImageIdentifier
|
||||
Vulnerabilities []Vulnerability
|
||||
}
|
||||
|
||||
type ContainerImageInformation struct {
|
||||
ImageID ContainerImageIdentifier
|
||||
Bom []string
|
||||
//ImageManifest Manifest // will use here Docker package definition
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package registryvulnerabilities
|
||||
|
||||
type IContainerImageVulnerabilityAdaptor interface {
|
||||
// Login Credentials are coming from user input (CLI or configuration file) and they are abstracted at string to string map level
|
||||
// so and example use would be like registry: "simpledockerregistry:80" and credentials like {"username":"joedoe","password":"abcd1234"}
|
||||
Login() error
|
||||
|
||||
// DescribeAdaptor For "help" purposes
|
||||
DescribeAdaptor() string
|
||||
|
||||
GetImagesScanStatus(imageIDs []ContainerImageIdentifier) ([]ContainerImageScanStatus, error)
|
||||
|
||||
GetImagesVulnerabilities(imageIDs []ContainerImageIdentifier) ([]ContainerImageVulnerabilityReport, error)
|
||||
GetImageVulnerability(imageID *ContainerImageIdentifier) (*ContainerImageVulnerabilityReport, error)
|
||||
|
||||
GetImagesInformation(imageIDs []ContainerImageIdentifier) ([]ContainerImageInformation, error)
|
||||
}
|
||||
@@ -11,13 +11,13 @@ import (
|
||||
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
helpersv1 "github.com/kubescape/opa-utils/reporthandling/helpers/v1"
|
||||
reporthandlingv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
reportv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/dynamic/fake"
|
||||
fakeclientset "k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/rest"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ func Test_getCloudMetadata(t *testing.T) {
|
||||
name: "Test_getCloudMetadata - GitVersion: GKE",
|
||||
args: args{
|
||||
opaSessionObj: &cautils.OPASessionObj{
|
||||
Report: &reporthandlingv2.PostureReport{
|
||||
Report: &reportv2.PostureReport{
|
||||
ClusterAPIServerInfo: &version.Info{
|
||||
GitVersion: "v1.25.4-gke.1600",
|
||||
},
|
||||
@@ -60,25 +60,14 @@ func Test_getCloudMetadata(t *testing.T) {
|
||||
},
|
||||
want: helpersv1.NewGKEMetadata(""),
|
||||
},
|
||||
{
|
||||
name: "Test_getCloudMetadata_context_GKE",
|
||||
args: args{
|
||||
opaSessionObj: &cautils.OPASessionObj{
|
||||
Report: &reporthandlingv2.PostureReport{
|
||||
ClusterAPIServerInfo: nil,
|
||||
},
|
||||
},
|
||||
kubeConfig: kubeConfig,
|
||||
context: "gke_xxx-xx-0000_us-central1-c_xxxx-1",
|
||||
},
|
||||
want: helpersv1.NewGKEMetadata(""),
|
||||
},
|
||||
{
|
||||
name: "Test_getCloudMetadata_context_EKS",
|
||||
args: args{
|
||||
opaSessionObj: &cautils.OPASessionObj{
|
||||
Report: &reporthandlingv2.PostureReport{
|
||||
ClusterAPIServerInfo: nil,
|
||||
Report: &reportv2.PostureReport{
|
||||
ClusterAPIServerInfo: &version.Info{
|
||||
GitVersion: "v1.25.4-eks.1600",
|
||||
},
|
||||
},
|
||||
},
|
||||
kubeConfig: kubeConfig,
|
||||
@@ -90,8 +79,10 @@ func Test_getCloudMetadata(t *testing.T) {
|
||||
name: "Test_getCloudMetadata_context_AKS",
|
||||
args: args{
|
||||
opaSessionObj: &cautils.OPASessionObj{
|
||||
Report: &reporthandlingv2.PostureReport{
|
||||
ClusterAPIServerInfo: nil,
|
||||
Report: &reportv2.PostureReport{
|
||||
ClusterAPIServerInfo: &version.Info{
|
||||
GitVersion: "v1",
|
||||
},
|
||||
},
|
||||
},
|
||||
kubeConfig: kubeConfig,
|
||||
@@ -100,10 +91,15 @@ func Test_getCloudMetadata(t *testing.T) {
|
||||
want: helpersv1.NewAKSMetadata(""),
|
||||
},
|
||||
}
|
||||
k8sinterface.K8SConfig = &rest.Config{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
k8sinterface.SetClusterContextName(tt.args.context)
|
||||
got := getCloudMetadata(tt.args.opaSessionObj, tt.args.kubeConfig)
|
||||
k8sinterface.SetClientConfigAPI(tt.args.kubeConfig)
|
||||
k8sinterface.SetK8SGitServerVersion(tt.args.opaSessionObj.Report.ClusterAPIServerInfo.GitVersion)
|
||||
k8sinterface.SetConnectedToCluster(true)
|
||||
|
||||
got := getCloudMetadata(tt.args.opaSessionObj)
|
||||
if got == nil {
|
||||
t.Errorf("getCloudMetadata() = %v, want %v", got, tt.want.Provider())
|
||||
return
|
||||
@@ -113,112 +109,10 @@ func Test_getCloudMetadata(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
k8sinterface.SetClusterContextName("")
|
||||
k8sinterface.SetClientConfigAPI(nil)
|
||||
}
|
||||
|
||||
func Test_isGKE(t *testing.T) {
|
||||
type args struct {
|
||||
config *clientcmdapi.Config
|
||||
context string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Test_isGKE",
|
||||
args: args{
|
||||
config: getKubeConfigMock(),
|
||||
context: "gke_xxx-xx-0000_us-central1-c_xxxx-1",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// set context
|
||||
k8sinterface.SetClusterContextName(tt.args.context)
|
||||
if got := isGKE(tt.args.config); got != tt.want {
|
||||
t.Errorf("isGKE() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isEKS(t *testing.T) {
|
||||
type args struct {
|
||||
config *clientcmdapi.Config
|
||||
context string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Test_isEKS",
|
||||
args: args{
|
||||
config: getKubeConfigMock(),
|
||||
context: "arn:aws:eks:eu-west-1:xxx:cluster/xxxx",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// set context
|
||||
k8sinterface.SetClusterContextName(tt.args.context)
|
||||
if got := isEKS(tt.args.config); got != tt.want {
|
||||
t.Errorf("isEKS() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isAKS(t *testing.T) {
|
||||
type args struct {
|
||||
config *clientcmdapi.Config
|
||||
context string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Test_isAKS",
|
||||
args: args{
|
||||
config: getKubeConfigMock(),
|
||||
context: "xxxx-2",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// set context
|
||||
k8sinterface.SetClusterContextName(tt.args.context)
|
||||
if got := isAKS(tt.args.config); got != tt.want {
|
||||
t.Errorf("isAKS() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/* unused for now.
|
||||
type iResourceHandlerMock struct{}
|
||||
|
||||
func (*iResourceHandlerMock) GetResources(*cautils.OPASessionObj, *identifiers.PortalDesignator) (*cautils.K8SResources, map[string]workloadinterface.IMetadata, *cautils.KSResources, error) {
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
func (*iResourceHandlerMock) GetClusterAPIServerInfo() *version.Info {
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
||||
// https://github.com/kubescape/kubescape/pull/1004
|
||||
// Cluster named .*eks.* config without a cloudconfig panics whereas we just want to scan a file
|
||||
func getResourceHandlerMock() *K8sResourceHandler {
|
||||
@@ -232,17 +126,17 @@ func getResourceHandlerMock() *K8sResourceHandler {
|
||||
Context: context.Background(),
|
||||
}
|
||||
|
||||
return NewK8sResourceHandler(k8s, nil, nil, nil)
|
||||
return NewK8sResourceHandler(k8s, nil, nil, "test")
|
||||
}
|
||||
func Test_CollectResources(t *testing.T) {
|
||||
resourceHandler := getResourceHandlerMock()
|
||||
objSession := &cautils.OPASessionObj{
|
||||
Metadata: &reporthandlingv2.Metadata{
|
||||
ScanMetadata: reporthandlingv2.ScanMetadata{
|
||||
Metadata: &reportv2.Metadata{
|
||||
ScanMetadata: reportv2.ScanMetadata{
|
||||
ScanningTarget: reportv2.Cluster,
|
||||
},
|
||||
},
|
||||
Report: &reporthandlingv2.PostureReport{
|
||||
Report: &reportv2.PostureReport{
|
||||
ClusterAPIServerInfo: nil,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@ package resourcehandler
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/go-logger/helpers"
|
||||
"github.com/kubescape/k8s-interface/cloudsupport"
|
||||
cloudsupportv1 "github.com/kubescape/k8s-interface/cloudsupport/v1"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
@@ -16,7 +14,6 @@ import (
|
||||
helpersv1 "github.com/kubescape/opa-utils/reporthandling/helpers/v1"
|
||||
reportv2 "github.com/kubescape/opa-utils/reporthandling/v2"
|
||||
"go.opentelemetry.io/otel"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
func CollectResources(ctx context.Context, rsrcHandler IResourceHandler, policyIdentifier []cautils.PolicyIdentifier, opaSessionObj *cautils.OPASessionObj, progressListener opaprocessor.IJobProgressNotificationClient, scanInfo *cautils.ScanInfo) error {
|
||||
@@ -47,7 +44,7 @@ func CollectResources(ctx context.Context, rsrcHandler IResourceHandler, policyI
|
||||
}
|
||||
|
||||
func setCloudMetadata(opaSessionObj *cautils.OPASessionObj) {
|
||||
iCloudMetadata := getCloudMetadata(opaSessionObj, k8sinterface.GetConfig())
|
||||
iCloudMetadata := getCloudMetadata(opaSessionObj)
|
||||
if iCloudMetadata == nil {
|
||||
return
|
||||
}
|
||||
@@ -67,53 +64,15 @@ func setCloudMetadata(opaSessionObj *cautils.OPASessionObj) {
|
||||
// 1. Get cloud provider from API server git version (EKS, GKE)
|
||||
// 2. Get cloud provider from kubeconfig by parsing the cluster context (EKS, GKE)
|
||||
// 3. Get cloud provider from kubeconfig by parsing the server URL (AKS)
|
||||
func getCloudMetadata(opaSessionObj *cautils.OPASessionObj, config *clientcmdapi.Config) apis.ICloudParser {
|
||||
|
||||
if config == nil {
|
||||
func getCloudMetadata(opaSessionObj *cautils.OPASessionObj) apis.ICloudParser {
|
||||
switch cloudsupportv1.GetCloudProvider() {
|
||||
case cloudsupportv1.AKS:
|
||||
return helpersv1.NewAKSMetadata(k8sinterface.GetContextName())
|
||||
case cloudsupportv1.EKS:
|
||||
return helpersv1.NewEKSMetadata(k8sinterface.GetContextName())
|
||||
case cloudsupportv1.GKE:
|
||||
return helpersv1.NewGKEMetadata(k8sinterface.GetContextName())
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
var provider string
|
||||
|
||||
// attempting to get cloud provider from API server git version
|
||||
if opaSessionObj.Report.ClusterAPIServerInfo != nil {
|
||||
provider = cloudsupport.GetCloudProvider(opaSessionObj.Report.ClusterAPIServerInfo.GitVersion)
|
||||
}
|
||||
|
||||
if provider == cloudsupportv1.AKS || isAKS(config) {
|
||||
return helpersv1.NewAKSMetadata(k8sinterface.GetContextName())
|
||||
}
|
||||
if provider == cloudsupportv1.EKS || isEKS(config) {
|
||||
return helpersv1.NewEKSMetadata(k8sinterface.GetContextName())
|
||||
}
|
||||
if provider == cloudsupportv1.GKE || isGKE(config) {
|
||||
return helpersv1.NewGKEMetadata(k8sinterface.GetContextName())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if the server is AKS. e.g. https://XXX.XX.XXX.azmk8s.io:443
|
||||
func isAKS(config *clientcmdapi.Config) bool {
|
||||
const serverIdentifierAKS = "azmk8s.io"
|
||||
if cluster, ok := config.Clusters[k8sinterface.GetContextName()]; ok {
|
||||
return strings.Contains(cluster.Server, serverIdentifierAKS)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the server is EKS. e.g. arn:aws:eks:eu-west-1:xxx:cluster/xxxx
|
||||
func isEKS(config *clientcmdapi.Config) bool {
|
||||
if context, ok := config.Contexts[k8sinterface.GetContextName()]; ok {
|
||||
return strings.Contains(context.Cluster, cloudsupportv1.EKS)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the server is GKE. e.g. gke_xxx-xx-0000_us-central1-c_xxxx-1
|
||||
func isGKE(config *clientcmdapi.Config) bool {
|
||||
if context, ok := config.Contexts[k8sinterface.GetContextName()]; ok {
|
||||
return strings.Contains(context.Cluster, cloudsupportv1.GKE)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -38,18 +38,18 @@ var cloudResourceGetterMapping = map[string]cloudResourceGetter{
|
||||
}
|
||||
|
||||
type K8sResourceHandler struct {
|
||||
clusterName string
|
||||
k8s *k8sinterface.KubernetesApi
|
||||
hostSensorHandler hostsensorutils.IHostSensor
|
||||
rbacObjectsAPI *cautils.RBACObjects
|
||||
registryAdaptors *RegistryAdaptors
|
||||
}
|
||||
|
||||
func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor, rbacObjects *cautils.RBACObjects, registryAdaptors *RegistryAdaptors) *K8sResourceHandler {
|
||||
func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, hostSensorHandler hostsensorutils.IHostSensor, rbacObjects *cautils.RBACObjects, clusterName string) *K8sResourceHandler {
|
||||
return &K8sResourceHandler{
|
||||
clusterName: clusterName,
|
||||
k8s: k8s,
|
||||
hostSensorHandler: hostSensorHandler,
|
||||
rbacObjectsAPI: rbacObjects,
|
||||
registryAdaptors: registryAdaptors,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,21 +94,6 @@ func (k8sHandler *K8sResourceHandler) GetResources(ctx context.Context, sessionO
|
||||
|
||||
logger.L().StopSuccess("Accessed Kubernetes objects")
|
||||
|
||||
// backswords compatibility - get image vulnerability resources
|
||||
if k8sHandler.registryAdaptors != nil {
|
||||
imgVulnResources := cautils.MapImageVulnResources(ksResourceMap)
|
||||
// check that controls use image vulnerability resources
|
||||
if len(imgVulnResources) > 0 {
|
||||
logger.L().Info("Requesting images vulnerabilities results")
|
||||
cautils.StartSpinner()
|
||||
if err := k8sHandler.registryAdaptors.collectImagesVulnerabilities(k8sResourcesMap, allResources, ksResourceMap); err != nil {
|
||||
cautils.SetInfoMapForResources(fmt.Sprintf("failed to pull image scanning data: %s. for more information: https://hub.armosec.io/docs/configuration-of-image-vulnerabilities", err.Error()), imgVulnResources, sessionObj.InfoMap)
|
||||
}
|
||||
cautils.StopSpinner()
|
||||
logger.L().Success("Requested images vulnerabilities results")
|
||||
}
|
||||
}
|
||||
|
||||
hostResources := cautils.MapHostResources(ksResourceMap)
|
||||
// check that controls use host sensor resources
|
||||
if len(hostResources) > 0 {
|
||||
@@ -204,10 +189,9 @@ func (k8sHandler *K8sResourceHandler) findScanObjectResource(resource *objectsen
|
||||
}
|
||||
|
||||
func (k8sHandler *K8sResourceHandler) collectCloudResources(ctx context.Context, sessionObj *cautils.OPASessionObj, allResources map[string]workloadinterface.IMetadata, externalResourceMap cautils.ExternalResources, cloudResources []string, progressListener opaprocessor.IJobProgressNotificationClient) error {
|
||||
clusterName := cautils.ClusterName
|
||||
provider := cloudsupport.GetCloudProvider(clusterName)
|
||||
provider := cloudsupport.GetCloudProvider()
|
||||
if provider == "" {
|
||||
return fmt.Errorf("failed to get cloud provider, cluster: %s", clusterName)
|
||||
return fmt.Errorf("failed to get cloud provider, cluster: %s", k8sHandler.clusterName)
|
||||
}
|
||||
|
||||
logger.L().Start("Downloading cloud resources")
|
||||
@@ -215,7 +199,7 @@ func (k8sHandler *K8sResourceHandler) collectCloudResources(ctx context.Context,
|
||||
if sessionObj.Metadata != nil && sessionObj.Metadata.ContextMetadata.ClusterContextMetadata != nil {
|
||||
sessionObj.Metadata.ContextMetadata.ClusterContextMetadata.CloudProvider = provider
|
||||
}
|
||||
logger.L().Debug("cloud", helpers.String("cluster", clusterName), helpers.String("clusterName", clusterName), helpers.String("provider", provider))
|
||||
logger.L().Debug("cloud", helpers.String("clusterName", k8sHandler.clusterName), helpers.String("provider", provider))
|
||||
|
||||
for resourceKind, resourceGetter := range cloudResourceGetterMapping {
|
||||
if !cloudResourceRequired(cloudResources, resourceKind) {
|
||||
@@ -223,7 +207,7 @@ func (k8sHandler *K8sResourceHandler) collectCloudResources(ctx context.Context,
|
||||
}
|
||||
|
||||
logger.L().Debug("Collecting cloud data ", helpers.String("resourceKind", resourceKind))
|
||||
wl, err := resourceGetter(clusterName, provider)
|
||||
wl, err := resourceGetter(k8sHandler.clusterName, provider)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), cloudv1.NotSupportedMsg) {
|
||||
// Return error with useful info on how to configure credentials for getting cloud provider info
|
||||
@@ -416,11 +400,11 @@ func (k8sHandler *K8sResourceHandler) collectHostResources(ctx context.Context,
|
||||
}
|
||||
|
||||
func (k8sHandler *K8sResourceHandler) collectRbacResources(allResources map[string]workloadinterface.IMetadata) error {
|
||||
logger.L().Start("Collecting RBAC resources")
|
||||
|
||||
if k8sHandler.rbacObjectsAPI == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.L().Start("Collecting RBAC resources")
|
||||
allRbacResources, err := k8sHandler.rbacObjectsAPI.ListAllResources()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
package resourcehandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
logger "github.com/kubescape/go-logger"
|
||||
"github.com/kubescape/k8s-interface/k8sinterface"
|
||||
"github.com/kubescape/k8s-interface/workloadinterface"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/cautils/getter"
|
||||
armosecadaptorv1 "github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/armosec/v1"
|
||||
gcpadaptorv1 "github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/gcp/v1"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/registryadaptors/registryvulnerabilities"
|
||||
|
||||
"github.com/kubescape/opa-utils/shared"
|
||||
)
|
||||
|
||||
const (
|
||||
ImagevulnerabilitiesObjectGroup = "armo.vuln.images"
|
||||
ImagevulnerabilitiesObjectVersion = "v1"
|
||||
ImagevulnerabilitiesObjectKind = "ImageVulnerabilities"
|
||||
)
|
||||
|
||||
type RegistryAdaptors struct {
|
||||
adaptors []registryvulnerabilities.IContainerImageVulnerabilityAdaptor
|
||||
}
|
||||
|
||||
func NewRegistryAdaptors() (*RegistryAdaptors, error) {
|
||||
// list supported adaptors
|
||||
registryAdaptors := &RegistryAdaptors{}
|
||||
adaptors, err := listAdaptors()
|
||||
if err != nil {
|
||||
return registryAdaptors, err
|
||||
}
|
||||
if len(adaptors) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
registryAdaptors.adaptors = adaptors
|
||||
return registryAdaptors, nil
|
||||
}
|
||||
|
||||
func (registryAdaptors *RegistryAdaptors) collectImagesVulnerabilities(k8sResourcesMap cautils.K8SResources, allResources map[string]workloadinterface.IMetadata, externalResourceMap cautils.ExternalResources) error {
|
||||
logger.L().Debug("Collecting images vulnerabilities")
|
||||
|
||||
if len(registryAdaptors.adaptors) == 0 {
|
||||
return fmt.Errorf("credentials are not configured for any registry adaptor")
|
||||
}
|
||||
|
||||
for i := range registryAdaptors.adaptors { // login and and get vulnerabilities
|
||||
if err := registryAdaptors.adaptors[i].Login(); err != nil {
|
||||
return fmt.Errorf("failed to login, adaptor: '%s', reason: '%s'", registryAdaptors.adaptors[i].DescribeAdaptor(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// list cluster images
|
||||
images := listImagesTags(k8sResourcesMap, allResources)
|
||||
imagesIdentifiers := imageTagsToContainerImageIdentifier(images)
|
||||
|
||||
imagesVulnerability := map[string][]registryvulnerabilities.Vulnerability{}
|
||||
for i := range registryAdaptors.adaptors { // login and and get vulnerabilities
|
||||
|
||||
vulnerabilities, err := registryAdaptors.adaptors[i].GetImagesVulnerabilities(imagesIdentifiers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for j := range vulnerabilities {
|
||||
imagesVulnerability[vulnerabilities[j].ImageID.Tag] = vulnerabilities[j].Vulnerabilities
|
||||
}
|
||||
}
|
||||
|
||||
// convert result to IMetadata object
|
||||
metaObjs := vulnerabilitiesToIMetadata(imagesVulnerability)
|
||||
|
||||
if len(metaObjs) == 0 {
|
||||
return fmt.Errorf("no vulnerabilities found for any of the images")
|
||||
}
|
||||
|
||||
// save in resources map
|
||||
for i := range metaObjs {
|
||||
allResources[metaObjs[i].GetID()] = metaObjs[i]
|
||||
}
|
||||
externalResourceMap[k8sinterface.JoinResourceTriplets(ImagevulnerabilitiesObjectGroup, ImagevulnerabilitiesObjectVersion, ImagevulnerabilitiesObjectKind)] = workloadinterface.ListMetaIDs(metaObjs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func vulnerabilitiesToIMetadata(vulnerabilities map[string][]registryvulnerabilities.Vulnerability) []workloadinterface.IMetadata {
|
||||
objs := []workloadinterface.IMetadata{}
|
||||
for i := range vulnerabilities {
|
||||
objs = append(objs, vulnerabilityToIMetadata(i, vulnerabilities[i]))
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
func vulnerabilityToIMetadata(imageTag string, vulnerabilities []registryvulnerabilities.Vulnerability) workloadinterface.IMetadata {
|
||||
obj := map[string]interface{}{}
|
||||
metadata := map[string]interface{}{}
|
||||
metadata["name"] = imageTag // store image tag as object name
|
||||
obj["kind"] = ImagevulnerabilitiesObjectKind
|
||||
obj["apiVersion"] = k8sinterface.JoinGroupVersion(ImagevulnerabilitiesObjectGroup, ImagevulnerabilitiesObjectVersion)
|
||||
obj["data"] = vulnerabilities
|
||||
obj["metadata"] = metadata
|
||||
|
||||
return workloadinterface.NewWorkloadObj(obj)
|
||||
}
|
||||
|
||||
// list all images tags
|
||||
func listImagesTags(k8sResourcesMap cautils.K8SResources, allResources map[string]workloadinterface.IMetadata) []string {
|
||||
images := []string{}
|
||||
for _, resources := range k8sResourcesMap {
|
||||
for j := range resources {
|
||||
if resource, ok := allResources[resources[j]]; ok {
|
||||
if resource.GetObjectType() == workloadinterface.TypeWorkloadObject {
|
||||
workload := workloadinterface.NewWorkloadObj(resource.GetObject())
|
||||
if containers, err := workload.GetContainers(); err == nil {
|
||||
for i := range containers {
|
||||
images = append(images, containers[i].Image)
|
||||
}
|
||||
}
|
||||
if containers, err := workload.GetInitContainers(); err == nil {
|
||||
for i := range containers {
|
||||
images = append(images, containers[i].Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shared.SliceStringToUnique(images)
|
||||
}
|
||||
|
||||
func imageTagsToContainerImageIdentifier(images []string) []registryvulnerabilities.ContainerImageIdentifier {
|
||||
imagesIdentifiers := make([]registryvulnerabilities.ContainerImageIdentifier, len(images))
|
||||
for i := range images {
|
||||
imageIdentifier := registryvulnerabilities.ContainerImageIdentifier{
|
||||
Tag: images[i],
|
||||
}
|
||||
// splitted := strings.Split(images[i], "/")
|
||||
// if len(splitted) == 1 {
|
||||
// imageIdentifier.Tag = splitted[0]
|
||||
// } else if len(splitted) == 2 {
|
||||
// imageIdentifier.Registry = splitted[0]
|
||||
// imageIdentifier.Tag = splitted[1]
|
||||
// } else if len(splitted) >= 3 {
|
||||
// imageIdentifier.Registry = splitted[0]
|
||||
// imageIdentifier.Repository = strings.Join(splitted[1:len(splitted)-1], "/")
|
||||
// imageIdentifier.Tag = splitted[len(splitted)-1]
|
||||
// }
|
||||
imagesIdentifiers[i] = imageIdentifier
|
||||
}
|
||||
return imagesIdentifiers
|
||||
}
|
||||
func listAdaptors() ([]registryvulnerabilities.IContainerImageVulnerabilityAdaptor, error) {
|
||||
|
||||
adaptors := []registryvulnerabilities.IContainerImageVulnerabilityAdaptor{}
|
||||
|
||||
ksCloudAPI := getter.GetKSCloudAPIConnector()
|
||||
if ksCloudAPI != nil {
|
||||
if ksCloudAPI.GetSecretKey() != "" && ksCloudAPI.GetClientID() != "" && ksCloudAPI.GetAccountID() != "" {
|
||||
adaptors = append(adaptors, armosecadaptorv1.NewKSAdaptor(getter.GetKSCloudAPIConnector()))
|
||||
}
|
||||
}
|
||||
|
||||
gcpCloudAPI := getter.GetGlobalGCPCloudAPIConnector()
|
||||
if gcpCloudAPI != nil {
|
||||
if gcpCloudAPI.GetCredentialsCheck() {
|
||||
adaptors = append(adaptors, gcpadaptorv1.NewGCPAdaptor(getter.GetGlobalGCPCloudAPIConnector()))
|
||||
}
|
||||
}
|
||||
|
||||
return adaptors, nil
|
||||
}
|
||||
@@ -56,6 +56,11 @@ func (hp *HtmlPrinter) PrintNextSteps() {
|
||||
}
|
||||
|
||||
func (hp *HtmlPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
if opaSessionObj == nil {
|
||||
logger.L().Ctx(ctx).Error("failed to print results, missing data")
|
||||
return
|
||||
}
|
||||
|
||||
tplFuncMap := template.FuncMap{
|
||||
"sum": func(nums ...int) int {
|
||||
total := 0
|
||||
|
||||
@@ -53,11 +53,11 @@ func (jp *JsonPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.O
|
||||
} else if imageScanData != nil {
|
||||
err = jp.PrintImageScan(ctx, imageScanData[0].PresenterConfig)
|
||||
} else {
|
||||
err = fmt.Errorf("failed to write results, no data provided")
|
||||
err = fmt.Errorf("no data provided")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.L().Ctx(ctx).Error("failed to write results", helpers.Error(err))
|
||||
logger.L().Ctx(ctx).Error("failed to write results in json format", helpers.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -75,7 +75,15 @@ func printConfigurationsScanning(opaSessionObj *cautils.OPASessionObj, ctx conte
|
||||
}
|
||||
|
||||
func (jp *JsonPrinter) PrintImageScan(ctx context.Context, scanResults *models.PresenterConfig) error {
|
||||
presenterConfig, _ := presenter.ValidatedConfig("json", "", false)
|
||||
if scanResults == nil {
|
||||
return fmt.Errorf("no image vulnerability data provided")
|
||||
}
|
||||
|
||||
presenterConfig, err := presenter.ValidatedConfig("json", "", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pres := presenter.GetPresenter(presenterConfig, *scanResults)
|
||||
|
||||
return pres.Present(jp.writer)
|
||||
|
||||
@@ -117,6 +117,11 @@ func (jp *JunitPrinter) PrintNextSteps() {
|
||||
}
|
||||
|
||||
func (jp *JunitPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
if opaSessionObj == nil {
|
||||
logger.L().Ctx(ctx).Error("failed to print results, missing data")
|
||||
return
|
||||
}
|
||||
|
||||
junitResult := testsSuites(opaSessionObj)
|
||||
postureReportStr, err := xml.Marshal(junitResult)
|
||||
if err != nil {
|
||||
|
||||
@@ -90,6 +90,11 @@ func (pp *PdfPrinter) PrintNextSteps() {
|
||||
}
|
||||
|
||||
func (pp *PdfPrinter) ActionPrint(ctx context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
if opaSessionObj == nil {
|
||||
logger.L().Ctx(ctx).Error("failed to print results, missing data")
|
||||
return
|
||||
}
|
||||
|
||||
sortedControlIDs := getSortedControlsIDs(opaSessionObj.Report.SummaryDetails.Controls)
|
||||
|
||||
infoToPrintInfo := mapInfoToPrintInfo(opaSessionObj.Report.SummaryDetails.Controls)
|
||||
|
||||
@@ -40,9 +40,10 @@ type PrettyPrinter struct {
|
||||
scanType cautils.ScanTypes
|
||||
inputPatterns []string
|
||||
mainPrinter prettyprinter.MainPrinter
|
||||
clusterName string
|
||||
}
|
||||
|
||||
func NewPrettyPrinter(verboseMode bool, formatVersion string, attackTree bool, viewType cautils.ViewTypes, scanType cautils.ScanTypes, inputPatterns []string) *PrettyPrinter {
|
||||
func NewPrettyPrinter(verboseMode bool, formatVersion string, attackTree bool, viewType cautils.ViewTypes, scanType cautils.ScanTypes, inputPatterns []string, clusterName string) *PrettyPrinter {
|
||||
prettyPrinter := &PrettyPrinter{
|
||||
verboseMode: verboseMode,
|
||||
formatVersion: formatVersion,
|
||||
@@ -50,6 +51,7 @@ func NewPrettyPrinter(verboseMode bool, formatVersion string, attackTree bool, v
|
||||
printAttackTree: attackTree,
|
||||
scanType: scanType,
|
||||
inputPatterns: inputPatterns,
|
||||
clusterName: clusterName,
|
||||
}
|
||||
|
||||
return prettyPrinter
|
||||
@@ -116,7 +118,11 @@ func (pp *PrettyPrinter) PrintImageScan(imageScanData []cautils.ImageScanData) {
|
||||
|
||||
func (pp *PrettyPrinter) ActionPrint(_ context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) {
|
||||
if opaSessionObj != nil {
|
||||
fmt.Fprintf(pp.writer, "\n"+getSeparator("^")+"\n")
|
||||
if isPrintSeparatorType(pp.scanType) {
|
||||
fmt.Fprintf(pp.writer, "\n"+getSeparator("^")+"\n")
|
||||
} else {
|
||||
fmt.Fprintf(pp.writer, "\n")
|
||||
}
|
||||
|
||||
sortedControlIDs := getSortedControlsIDs(opaSessionObj.Report.SummaryDetails.Controls) // ListControls().All())
|
||||
|
||||
@@ -131,7 +137,7 @@ func (pp *PrettyPrinter) ActionPrint(_ context.Context, opaSessionObj *cautils.O
|
||||
|
||||
pp.printOverview(opaSessionObj, pp.verboseMode)
|
||||
|
||||
pp.mainPrinter.PrintConfigurationsScanning(&opaSessionObj.Report.SummaryDetails, sortedControlIDs)
|
||||
pp.mainPrinter.PrintConfigurationsScanning(&opaSessionObj.Report.SummaryDetails, sortedControlIDs, opaSessionObj.TopWorkloadsByScore)
|
||||
|
||||
// When writing to Stdout, we aren’t really writing to an output file,
|
||||
// so no need to print that we are
|
||||
@@ -157,7 +163,8 @@ func (pp *PrettyPrinter) printOverview(opaSessionObj *cautils.OPASessionObj, pri
|
||||
|
||||
func (pp *PrettyPrinter) printHeader(opaSessionObj *cautils.OPASessionObj) {
|
||||
if pp.scanType == cautils.ScanTypeCluster || pp.scanType == cautils.ScanTypeRepo {
|
||||
cautils.InfoDisplay(pp.writer, "\nSecurity Overview\n\n")
|
||||
cautils.InfoDisplay(pp.writer, fmt.Sprintf("\nKubescape security posture overview for cluster: %s\n\n", pp.clusterName))
|
||||
cautils.SimpleDisplay(pp.writer, "In this overview, Kubescape shows you a summary of your cluster security posture, including the number of users who can perform administrative actions. For each result greater than 0, you should evaluate its need, and then define an exception to allow it. This baseline can be used to detect drift in future.\n\n")
|
||||
} else if pp.scanType == cautils.ScanTypeWorkload {
|
||||
ns := opaSessionObj.SingleResourceScan.GetNamespace()
|
||||
if ns == "" {
|
||||
@@ -330,3 +337,12 @@ func getSeparator(sep string) string {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func isPrintSeparatorType(scanType cautils.ScanTypes) bool {
|
||||
switch scanType {
|
||||
case cautils.ScanTypeCluster, cautils.ScanTypeRepo, cautils.ScanTypeImage, cautils.ScanTypeWorkload:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ package prettyprinter
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
|
||||
@@ -29,16 +31,15 @@ func (cp *ClusterPrinter) PrintImageScanning(summary *imageprinter.ImageScanSumm
|
||||
printImagesCommands(cp.writer, *summary)
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
func (cp *ClusterPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string, topWorkloadsByScore []reporthandling.IResource) {
|
||||
|
||||
cp.categoriesTablePrinter.PrintCategoriesTables(cp.writer, summaryDetails, sortedControlIDs)
|
||||
|
||||
printComplianceScore(cp.writer, filterComplianceFrameworks(summaryDetails.ListFrameworks()))
|
||||
|
||||
if len(summaryDetails.TopWorkloadsByScore) > 0 {
|
||||
cp.printTopWorkloads(summaryDetails)
|
||||
if len(topWorkloadsByScore) > 0 {
|
||||
cp.printTopWorkloads(topWorkloadsByScore)
|
||||
}
|
||||
|
||||
printComplianceScore(cp.writer, filterComplianceFrameworks(summaryDetails.ListFrameworks()))
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) PrintNextSteps() {
|
||||
@@ -47,20 +48,27 @@ func (cp *ClusterPrinter) PrintNextSteps() {
|
||||
|
||||
func (cp *ClusterPrinter) getNextSteps() []string {
|
||||
return []string{
|
||||
configScanVerboseRunText,
|
||||
installHelmText,
|
||||
CICDSetupText,
|
||||
runCommandsText,
|
||||
scanWorkloadText,
|
||||
installKubescapeText,
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *ClusterPrinter) printTopWorkloads(summaryDetails *reportsummary.SummaryDetails) {
|
||||
cautils.InfoTextDisplay(cp.writer, getTopWorkloadsTitle(len(summaryDetails.TopWorkloadsByScore)))
|
||||
func (cp *ClusterPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IResource) {
|
||||
txt := getTopWorkloadsTitle(len(topWorkloadsByScore))
|
||||
|
||||
for i, wl := range summaryDetails.TopWorkloadsByScore {
|
||||
cautils.InfoTextDisplay(cp.writer, txt)
|
||||
|
||||
cautils.SimpleDisplay(cp.writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt))))
|
||||
|
||||
cautils.SimpleDisplay(cp.writer, highStakesWlsText)
|
||||
|
||||
for i, wl := range topWorkloadsByScore {
|
||||
ns := wl.GetNamespace()
|
||||
name := wl.GetName()
|
||||
kind := wl.GetKind()
|
||||
cautils.SimpleDisplay(cp.writer, fmt.Sprintf("%d. namespace: %s, name: %s, kind: %s - '%s'\n", i+1, ns, name, kind, getCallToActionString(cp.getWorkloadScanCommand(ns, kind, name))))
|
||||
cautils.SimpleDisplay(cp.writer, fmt.Sprintf("%d. namespace: %s, name: %s, kind: %s\n", i+1, ns, name, kind))
|
||||
cautils.SimpleDisplay(cp.writer, fmt.Sprintf(" '%s'\n", getCallToActionString(cp.getWorkloadScanCommand(ns, kind, name))))
|
||||
}
|
||||
|
||||
cautils.InfoTextDisplay(cp.writer, "\n")
|
||||
|
||||
@@ -11,16 +11,16 @@ func TestClusterScan_getNextSteps(t *testing.T) {
|
||||
t.Errorf("Expected 3 next steps, got %d", len(nextSteps))
|
||||
}
|
||||
|
||||
if nextSteps[0] != configScanVerboseRunText {
|
||||
if nextSteps[0] != runCommandsText {
|
||||
t.Errorf("Expected %s, got %s", configScanVerboseRunText, nextSteps[0])
|
||||
}
|
||||
|
||||
if nextSteps[1] != installHelmText {
|
||||
t.Errorf("Expected %s, got %s", installHelmText, nextSteps[1])
|
||||
if nextSteps[1] != scanWorkloadText {
|
||||
t.Errorf("Expected %s, got %s", scanWorkloadText, nextSteps[1])
|
||||
}
|
||||
|
||||
if nextSteps[2] != CICDSetupText {
|
||||
t.Errorf("Expected %s, got %s", CICDSetupText, nextSteps[2])
|
||||
if nextSteps[2] != installKubescapeText {
|
||||
t.Errorf("Expected %s, got %s", installKubescapeText, nextSteps[2])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
|
||||
@@ -34,6 +35,6 @@ func (sp *SummaryPrinter) getVerboseMode() bool {
|
||||
return sp.verboseMode
|
||||
}
|
||||
|
||||
func (sp *SummaryPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
func (sp *SummaryPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string, topWorkloadsByScore []reporthandling.IResource) {
|
||||
sp.summaryTablePrinter.PrintSummaryTable(sp.writer, summaryDetails, sortedControlIDs)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
|
||||
@@ -43,13 +44,13 @@ func (ip *ImagePrinter) PrintImageScanningTable(summary imageprinter.ImageScanSu
|
||||
cautils.InfoTextDisplay(ip.writer, "\n")
|
||||
}
|
||||
|
||||
func (ip *ImagePrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
func (ip *ImagePrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string, topWorkloadsByScore []reporthandling.IResource) {
|
||||
}
|
||||
|
||||
func (ip *ImagePrinter) PrintNextSteps() {
|
||||
if ip.verboseMode {
|
||||
printNextSteps(ip.writer, []string{CICDSetupText, installHelmText}, true)
|
||||
printNextSteps(ip.writer, []string{installKubescapeText}, true)
|
||||
return
|
||||
}
|
||||
printNextSteps(ip.writer, []string{imageScanVerboseRunText, CICDSetupText, installHelmText}, true)
|
||||
printNextSteps(ip.writer, []string{imageScanVerboseRunText, installKubescapeText}, true)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ package prettyprinter
|
||||
|
||||
import (
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter"
|
||||
"github.com/kubescape/opa-utils/reporthandling"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
)
|
||||
|
||||
type MainPrinter interface {
|
||||
PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControls [][]string)
|
||||
PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControls [][]string, topWorkloadsByScore []reporthandling.IResource)
|
||||
PrintImageScanning(imageScanSummary *imageprinter.ImageScanSummary)
|
||||
PrintNextSteps()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package prettyprinter
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
|
||||
type RepoPrinter struct {
|
||||
writer *os.File
|
||||
inputPatterns []string
|
||||
categoriesTablePrinter configurationprinter.TablePrinter
|
||||
}
|
||||
|
||||
@@ -32,11 +32,11 @@ func (rp *RepoPrinter) PrintImageScanning(summary *imageprinter.ImageScanSummary
|
||||
printTopComponents(rp.writer, *summary)
|
||||
}
|
||||
|
||||
func (rp *RepoPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) {
|
||||
func (rp *RepoPrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string, topWorkloadsByScore []reporthandling.IResource) {
|
||||
rp.categoriesTablePrinter.PrintCategoriesTables(rp.writer, summaryDetails, sortedControlIDs)
|
||||
|
||||
if len(summaryDetails.TopWorkloadsByScore) > 1 {
|
||||
rp.printTopWorkloads(summaryDetails)
|
||||
if len(topWorkloadsByScore) > 1 {
|
||||
rp.printTopWorkloads(topWorkloadsByScore)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,22 +47,28 @@ func (rp *RepoPrinter) PrintNextSteps() {
|
||||
|
||||
func (rp *RepoPrinter) getNextSteps() []string {
|
||||
return []string{
|
||||
configScanVerboseRunText,
|
||||
runCommandsText,
|
||||
clusterScanRunText,
|
||||
CICDSetupText,
|
||||
installHelmText,
|
||||
scanWorkloadText,
|
||||
installKubescapeText,
|
||||
}
|
||||
}
|
||||
|
||||
func (rp *RepoPrinter) printTopWorkloads(summaryDetails *reportsummary.SummaryDetails) {
|
||||
cautils.InfoTextDisplay(rp.writer, getTopWorkloadsTitle(len(summaryDetails.TopWorkloadsByScore)))
|
||||
func (rp *RepoPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IResource) {
|
||||
txt := getTopWorkloadsTitle(len(topWorkloadsByScore))
|
||||
cautils.InfoTextDisplay(rp.writer, txt)
|
||||
|
||||
for i, wl := range summaryDetails.TopWorkloadsByScore {
|
||||
cautils.SimpleDisplay(rp.writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt))))
|
||||
|
||||
cautils.SimpleDisplay(rp.writer, highStakesWlsText)
|
||||
|
||||
for i, wl := range topWorkloadsByScore {
|
||||
ns := wl.GetNamespace()
|
||||
name := wl.GetName()
|
||||
kind := wl.GetKind()
|
||||
cmdPrefix := getWorkloadPrefixForCmd(ns, kind, name)
|
||||
cautils.SimpleDisplay(rp.writer, fmt.Sprintf("%d. %s - '%s'\n", i+1, cmdPrefix, getCallToActionString(rp.getWorkloadScanCommand(ns, kind, name, *wl.GetSource()))))
|
||||
cautils.SimpleDisplay(rp.writer, fmt.Sprintf("%d. %s\n", i+1, cmdPrefix))
|
||||
cautils.SimpleDisplay(rp.writer, fmt.Sprintf(" %s\n", getCallToActionString(rp.getWorkloadScanCommand(ns, kind, name, *wl.GetSource()))))
|
||||
}
|
||||
|
||||
cautils.InfoTextDisplay(rp.writer, "\n")
|
||||
|
||||
@@ -15,20 +15,20 @@ func TestRepoScan_getNextSteps(t *testing.T) {
|
||||
t.Errorf("Expected 4 next steps, got %d", len(nextSteps))
|
||||
}
|
||||
|
||||
if nextSteps[0] != configScanVerboseRunText {
|
||||
t.Errorf("Expected %s, got %s", configScanVerboseRunText, nextSteps[0])
|
||||
if nextSteps[0] != runCommandsText {
|
||||
t.Errorf("Expected %s, got %s", clusterScanRunText, nextSteps[0])
|
||||
}
|
||||
|
||||
if nextSteps[1] != clusterScanRunText {
|
||||
t.Errorf("Expected %s, got %s", clusterScanRunText, nextSteps[1])
|
||||
t.Errorf("Expected %s, got %s", runCommandsText, nextSteps[1])
|
||||
}
|
||||
|
||||
if nextSteps[2] != CICDSetupText {
|
||||
t.Errorf("Expected %s, got %s", CICDSetupText, nextSteps[2])
|
||||
if nextSteps[2] != scanWorkloadText {
|
||||
t.Errorf("Expected %s, got %s", scanWorkloadText, nextSteps[2])
|
||||
}
|
||||
|
||||
if nextSteps[3] != installHelmText {
|
||||
t.Errorf("Expected %s, got %s", installHelmText, nextSteps[3])
|
||||
if nextSteps[3] != installKubescapeText {
|
||||
t.Errorf("Expected %s, got %s", installKubescapeText, nextSteps[3])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package configurationprinter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/kubescape/kubescape/v2/core/cautils"
|
||||
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils"
|
||||
"github.com/kubescape/opa-utils/reporthandling/apis"
|
||||
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
@@ -15,10 +13,10 @@ const (
|
||||
docsPrefix = "https://hub.armosec.io/docs"
|
||||
scanControlPrefix = "$ kubescape scan control"
|
||||
controlNameHeader = "CONTROL NAME"
|
||||
statusHeader = "STATUS"
|
||||
statusHeader = ""
|
||||
docsHeader = "DOCS"
|
||||
resourcesHeader = "RESOURCES"
|
||||
runHeader = "RUN"
|
||||
runHeader = "VIEW DETAILS"
|
||||
)
|
||||
|
||||
// initializes the table headers and column alignments based on the category type
|
||||
@@ -31,8 +29,8 @@ func initCategoryTableData(categoryType CategoryType) ([]string, []int) {
|
||||
|
||||
func getCategoryStatusTypeHeaders() []string {
|
||||
headers := make([]string, 3)
|
||||
headers[0] = controlNameHeader
|
||||
headers[1] = statusHeader
|
||||
headers[0] = statusHeader
|
||||
headers[1] = controlNameHeader
|
||||
headers[2] = docsHeader
|
||||
|
||||
return headers
|
||||
@@ -48,7 +46,7 @@ func getCategoryCountingTypeHeaders() []string {
|
||||
}
|
||||
|
||||
func getStatusTypeAlignments() []int {
|
||||
return []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}
|
||||
return []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER}
|
||||
}
|
||||
|
||||
func getCountingTypeAlignments() []int {
|
||||
@@ -66,14 +64,14 @@ func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary, inf
|
||||
|
||||
rows := make([]string, 3)
|
||||
|
||||
rows[0] = controlSummary.GetName()
|
||||
if len(controlSummary.GetName()) > 50 {
|
||||
rows[0] = controlSummary.GetName()[:50] + "..."
|
||||
} else {
|
||||
rows[0] = controlSummary.GetName()
|
||||
}
|
||||
rows[0] = utils.GetStatusIcon(controlSummary.GetStatus().Status())
|
||||
|
||||
rows[1] = utils.GetStatusColor(controlSummary.GetStatus().Status())(getStatus(status, controlSummary, infoToPrintInfo))
|
||||
rows[1] = controlSummary.GetName()
|
||||
if len(controlSummary.GetName()) > 50 {
|
||||
rows[1] = controlSummary.GetName()[:50] + "..."
|
||||
} else {
|
||||
rows[1] = controlSummary.GetName()
|
||||
}
|
||||
|
||||
rows[2] = getDocsForControl(controlSummary)
|
||||
|
||||
@@ -81,14 +79,6 @@ func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary, inf
|
||||
|
||||
}
|
||||
|
||||
func getStatus(status apis.IStatus, controlSummary reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) string {
|
||||
// skipped is shown as action required
|
||||
if status.IsSkipped() {
|
||||
return fmt.Sprintf("%s %s", "action required", GetInfoColumn(controlSummary, infoToPrintInfo))
|
||||
}
|
||||
return string(controlSummary.GetStatus().Status())
|
||||
}
|
||||
|
||||
func getCategoryTableWriter(writer io.Writer, headers []string, columnAligments []int) *tablewriter.Table {
|
||||
table := tablewriter.NewWriter(writer)
|
||||
table.SetHeader(headers)
|
||||
|
||||
@@ -21,14 +21,14 @@ func TestInitCategoryTableData(t *testing.T) {
|
||||
{
|
||||
name: "Test1",
|
||||
categoryType: TypeCounting,
|
||||
expectedHeaders: []string{"CONTROL NAME", "RESOURCES", "RUN"},
|
||||
expectedHeaders: []string{"CONTROL NAME", "RESOURCES", "VIEW DETAILS"},
|
||||
expectedAlignments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT},
|
||||
},
|
||||
{
|
||||
name: "Test2",
|
||||
categoryType: TypeStatus,
|
||||
expectedHeaders: []string{"CONTROL NAME", "STATUS", "DOCS"},
|
||||
expectedAlignments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER},
|
||||
expectedHeaders: []string{"", "CONTROL NAME", "DOCS"},
|
||||
expectedAlignments: []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -53,12 +53,12 @@ func TestGetCategoryStatusTypeHeaders(t *testing.T) {
|
||||
t.Errorf("Expected 3 headers, got %d", len(headers))
|
||||
}
|
||||
|
||||
if headers[0] != controlNameHeader {
|
||||
t.Errorf("Expected %s, got %s", controlNameHeader, headers[0])
|
||||
if headers[0] != statusHeader {
|
||||
t.Errorf("Expected %s, got %s", statusHeader, headers[0])
|
||||
}
|
||||
|
||||
if headers[1] != statusHeader {
|
||||
t.Errorf("Expected %s, got %s", statusHeader, headers[1])
|
||||
if headers[1] != controlNameHeader {
|
||||
t.Errorf("Expected %s, got %s", controlNameHeader, headers[1])
|
||||
}
|
||||
|
||||
if headers[2] != docsHeader {
|
||||
@@ -93,12 +93,12 @@ func TestGetStatusTypeAlignments(t *testing.T) {
|
||||
t.Errorf("Expected 3 alignments, got %d", len(alignments))
|
||||
}
|
||||
|
||||
if alignments[0] != tablewriter.ALIGN_LEFT {
|
||||
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_LEFT, alignments[0])
|
||||
if alignments[0] != tablewriter.ALIGN_CENTER {
|
||||
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_CENTER, alignments[0])
|
||||
}
|
||||
|
||||
if alignments[1] != tablewriter.ALIGN_CENTER {
|
||||
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_CENTER, alignments[1])
|
||||
if alignments[1] != tablewriter.ALIGN_LEFT {
|
||||
t.Errorf("Expected %d, got %d", tablewriter.ALIGN_LEFT, alignments[1])
|
||||
}
|
||||
|
||||
if alignments[2] != tablewriter.ALIGN_CENTER {
|
||||
@@ -140,7 +140,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
|
||||
Status: apis.StatusFailed,
|
||||
ControlID: "ctrlID",
|
||||
},
|
||||
expectedRows: []string{"test", "failed", "https://hub.armosec.io/docs/ctrlid"},
|
||||
expectedRows: []string{"❌", "test", "https://hub.armosec.io/docs/ctrlid"},
|
||||
},
|
||||
{
|
||||
name: "skipped control",
|
||||
@@ -152,7 +152,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
|
||||
},
|
||||
ControlID: "ctrlID",
|
||||
},
|
||||
expectedRows: []string{"test", "action required *", "https://hub.armosec.io/docs/ctrlid"},
|
||||
expectedRows: []string{"⚠️", "test", "https://hub.armosec.io/docs/ctrlid"},
|
||||
infoToPrintInfo: []utils.InfoStars{
|
||||
{
|
||||
Info: "testInfo",
|
||||
@@ -167,7 +167,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
|
||||
Status: apis.StatusPassed,
|
||||
ControlID: "ctrlID",
|
||||
},
|
||||
expectedRows: []string{"test", "passed", "https://hub.armosec.io/docs/ctrlid"},
|
||||
expectedRows: []string{"✅", "test", "https://hub.armosec.io/docs/ctrlid"},
|
||||
},
|
||||
{
|
||||
name: "big name",
|
||||
@@ -176,7 +176,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) {
|
||||
Status: apis.StatusFailed,
|
||||
ControlID: "ctrlID",
|
||||
},
|
||||
expectedRows: []string{"testtesttesttesttesttesttesttesttesttesttesttestte...", "failed", "https://hub.armosec.io/docs/ctrlid"},
|
||||
expectedRows: []string{"❌", "testtesttesttesttesttesttesttesttesttesttesttestte...", "https://hub.armosec.io/docs/ctrlid"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -60,16 +60,3 @@ func (wp *WorkloadPrinter) renderSingleCategoryTable(categoryName string, catego
|
||||
func (wp *WorkloadPrinter) initCategoryTableData() ([]string, []int) {
|
||||
return getCategoryStatusTypeHeaders(), getStatusTypeAlignments()
|
||||
}
|
||||
|
||||
func (wp *WorkloadPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) []string {
|
||||
|
||||
row := make([]string, 3)
|
||||
|
||||
row[0] = controlSummary.GetName()
|
||||
|
||||
row[1] = getStatus(controlSummary.GetStatus(), controlSummary, infoToPrintInfo)
|
||||
|
||||
row[2] = getDocsForControl(controlSummary)
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user