Compare commits

...

71 Commits

Author SHA1 Message Date
dwertent
4d68ca6aa2 update exceptions support 2021-09-13 10:24:24 +03:00
Benyamin Hirschberg
d8f5f7975c Merge pull request #73 from BenHirschbergCa/dev
Inverting posture score
2021-09-13 08:34:22 +03:00
Ben Hirschberg
4cd8476837 Inverting posture score 2021-09-13 08:31:13 +03:00
Benyamin Hirschberg
112257449f Merge pull request #72 from BenHirschbergCa/dev
fixing API path
2021-09-13 08:23:22 +03:00
Ben Hirschberg
ff9cf4adf0 fixing API path 2021-09-13 08:22:25 +03:00
Benyamin Hirschberg
f2d387bc9c Merge pull request #71 from Daniel-GrunbergerCA/master
Support score, cluster name and results flag
2021-09-12 20:53:49 +03:00
Daniel-GrunbergerCA
00006ec721 rm omit empty 2021-09-12 18:49:13 +03:00
Daniel-GrunbergerCA
00aa6948ab Merge remote-tracking branch 'upstream/dev' 2021-09-12 18:47:31 +03:00
Daniel-GrunbergerCA
bd24ed3af7 add framework score 2021-09-12 18:37:50 +03:00
Daniel-GrunbergerCA
9fc455bcec Update posture report flag and cluster name 2021-09-12 18:13:24 +03:00
dwertent
49cbfe130c do not calculate score 2021-09-12 17:34:00 +03:00
David Wertenteil
9cd61dd996 Merge pull request #70 from Moshe-Rappaport-CA/master
Support control IDs
2021-09-12 17:30:27 +03:00
moshep
292c4aa060 support id 2021-09-12 16:13:33 +03:00
moshep
56a265930d Merge remote-tracking branch 'upstream/dev' 2021-09-12 14:21:19 +03:00
Daniel-GrunbergerCA
4171d110a4 separa interfaces 2021-09-12 11:06:43 +03:00
Daniel-GrunbergerCA
d0775565e9 Merge remote-tracking branch 'upstream/dev' 2021-09-12 09:40:17 +03:00
David Wertenteil
ca4d4a096c Support self registration 2021-09-10 01:20:28 +03:00
dwertent
ff27db6b83 Merge remote-tracking branch 'upstream/dev' 2021-09-10 00:58:24 +03:00
Daniel Grunberger
c9ecb6c563 Add version handling (#67)
* start version handling

* add version handling

* update latest version check

* update build.yaml

* erase unused vars

* fix build.yaml

* fix var name

* handle error
2021-09-10 00:57:33 +03:00
dwertent
b9e5782264 support self registeration 2021-09-10 00:56:51 +03:00
Daniel-GrunbergerCA
d852f81cb0 handle error 2021-09-09 19:56:29 +03:00
dwertent
6137aa5d8e support fronegg 2021-09-09 17:15:55 +03:00
Daniel-GrunbergerCA
131b67ee83 fix var name 2021-09-09 16:26:42 +03:00
Daniel-GrunbergerCA
db6f00be08 fix build.yaml 2021-09-09 15:23:34 +03:00
Daniel-GrunbergerCA
2ecc80985a Merge remote-tracking branch 'upstream/dev' 2021-09-09 15:18:56 +03:00
Daniel-GrunbergerCA
93dbfd5110 erase unused vars 2021-09-09 15:15:36 +03:00
Daniel-GrunbergerCA
ae0c384c85 update build.yaml 2021-09-09 15:02:48 +03:00
Benyamin Hirschberg
f60ff1fb26 Merge pull request #63 from zc2638/feat/dockerfile
add docker build
2021-09-09 14:59:46 +03:00
Daniel-GrunbergerCA
08a81696a1 update latest version check 2021-09-09 14:58:52 +03:00
Daniel-GrunbergerCA
8375a8ae63 add version handling 2021-09-09 14:37:25 +03:00
Daniel-GrunbergerCA
31d8cf5118 start version handling 2021-09-09 12:29:42 +03:00
dwertent
597b967e55 Merge remote-tracking branch 'upstream/dev' 2021-09-09 11:43:44 +03:00
dwertent
679238ec13 download from release 2021-09-09 09:53:08 +03:00
dwertent
94884ac3d7 Merge branch 'master' into dev 2021-09-09 09:50:45 +03:00
zc
77a9956d91 add docker build 2021-09-07 14:21:39 +08:00
dwertent
a1e639453d remove deffer func 2021-09-05 17:35:56 +03:00
dwertent
3f84ee3fcc update summary 2021-09-05 14:42:49 +03:00
dwertent
38103ac90b update screenshot 2021-09-05 14:39:13 +03:00
dwertent
13d27697e1 update readme 2021-09-05 14:33:32 +03:00
dwertent
942f356d19 support exceptions 2021-09-05 14:21:51 +03:00
dwertent
b87b687e2f support exceptions 2021-09-02 17:41:03 +03:00
dwertent
2e313719bb adding scaore and excpetion to code 2021-09-02 14:54:04 +03:00
dwertent
0c5eb48fdb Merge remote-tracking branch 'upstream/master' 2021-09-02 13:21:13 +03:00
dwertent
2ae2c81e0b rm download 2021-09-02 13:18:23 +03:00
dwertent
222b154505 store file localy 2021-08-31 17:08:02 +03:00
dwertent
67c2de74f1 adding download script 2021-08-31 17:05:16 +03:00
dwertent
4a9b36807a remove sudo 2021-08-31 16:43:37 +03:00
dwertent
c6241fab38 remove sudo 2021-08-31 16:42:12 +03:00
dwertent
afbc69c6d2 Merge remote-tracking branch 'upstream/dev' 2021-08-31 16:41:44 +03:00
dwertent
2779cb4e25 update module 2021-08-31 11:47:44 +03:00
dwertent
f46ee93539 update modul name 2021-08-31 11:39:27 +03:00
dwertent
3eb087e5c1 Merge remote-tracking branch 'upstream/dev' 2021-08-31 11:38:14 +03:00
dwertent
59c935e723 update output f 2021-08-31 09:00:52 +03:00
dwertent
bae45d277f Merge remote-tracking branch 'upstream/dev' 2021-08-31 08:47:37 +03:00
dwertent
0b6dfa9cd0 Merge remote-tracking branch 'upstream/dev' 2021-08-30 18:47:07 +03:00
dwertent
1ff3a6c92c support output to file 2021-08-30 18:44:42 +03:00
dwertent
f75cee0d78 support stdin input 2021-08-30 14:54:01 +03:00
dwertent
229f16cb01 Merge remote-tracking branch 'upstream/dev' 2021-08-30 13:52:58 +03:00
dwertent
2c6b1a440f update glob function 2021-08-30 08:53:34 +03:00
dwertent
37afc1352f adding helm support to readme 2021-08-29 13:34:40 +03:00
dwertent
9943119033 recursive glob 2021-08-29 13:15:34 +03:00
dwertent
41457ff551 Merge remote-tracking branch 'upstream/dev' 2021-08-29 10:38:42 +03:00
dwertent
82b64b5828 Merge remote-tracking branch 'origin/dev' 2021-08-29 10:35:59 +03:00
dwertent
229e8acc74 Merge remote-tracking branch 'origin/yamlsupport' 2021-08-29 10:35:32 +03:00
David Wertenteil
30324e1c01 Merge branch 'dev' into yamlsupport 2021-08-29 10:19:09 +03:00
dwertent
8ca356eae7 Merge remote-tracking branch 'upstream/master' 2021-08-29 10:09:54 +03:00
dwertent
29f4ae368d support url input, update readme 2021-08-29 10:08:49 +03:00
dwertent
409080f51b update package name o kubescape 2021-08-29 08:17:09 +03:00
dwertent
0b24c46279 Merge remote-tracking branch 'upstream/dev' 2021-08-26 18:30:50 +03:00
dwertent
49596c5ac1 split to function 2021-08-26 18:29:32 +03:00
dwertent
9bf79db8f8 Merge branch 'Daniel-GrunbergerCA-master' into dev 2021-08-26 12:22:34 +03:00
28 changed files with 567 additions and 80 deletions

View File

@@ -6,7 +6,6 @@ on:
pull_request:
branches: [ master ]
types: [ closed ]
jobs:
once:
name: Create release
@@ -38,9 +37,10 @@ jobs:
uses: actions/setup-go@v2
with:
go-version: 1.16
- name: Build
run: mkdir -p build/${{ matrix.os }} && go mod tidy && go build -ldflags "-w -s" -o build/${{ matrix.os }}/kubescape # && md5sum build/${{ matrix.os }}/kubescape > build/${{ matrix.os }}/kubescape.md5
env:
RELEASE: v1.0.${{ github.run_number }}
run: mkdir -p build/${{ matrix.os }} && go mod tidy && go build -ldflags "-w -s -X github.com/armosec/kubescape/cmd.BuildNumber=$RELEASE" -o build/${{ matrix.os }}/kubescape # && md5sum build/${{ matrix.os }}/kubescape > build/${{ matrix.os }}/kubescape.md5
- name: Upload Release binaries
id: upload-release-asset

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
*.vs*
*go.sum*
*kubescape*
*debug*
*debug*
.idea

View File

@@ -39,6 +39,7 @@ If you wish to scan all namespaces in your cluster, remove the `--exclude-namesp
| `--use-from` | | Load local framework object from specified path. If not used will download latest |
| `--use-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` |
| `--exceptions` | | Path to an [exceptions obj](examples/exceptions.json) |
| `--results-locally` | | Kubescape sends scan results to Armosec backend to allow users to control exceptions and maintain chronological scan results. Use this flag if you do not wish to use these features |
## Usage & Examples
@@ -125,6 +126,18 @@ go mod tidy && go build -o kubescape .
4. Enjoy :zany_face:
# How to build in Docker
1. Clone Project
```
git clone git@github.com:armosec/kubescape.git kubescape && cd "$_"
```
2. Build
```
docker build -t kubescape -f build/Dockerfile .
```
# Under the hood
## Tests

13
build/Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
FROM golang:1.16-alpine as builder
ENV GOPROXY=https://goproxy.io,direct
ENV GO111MODULE=on
WORKDIR /work
ADD . .
RUN go mod download
RUN GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w " -installsuffix cgo -o kubescape .
FROM alpine
COPY --from=builder /work/kubescape /usr/bin/kubescape
CMD ["kubescape"]

191
cautils/customerloader.go Normal file
View File

@@ -0,0 +1,191 @@
package cautils
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"github.com/armosec/kubescape/cautils/getter"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/armosec/kubescape/cautils/k8sinterface"
corev1 "k8s.io/api/core/v1"
)
const (
configMapName = "kubescape"
configFileName = "config"
)
type ConfigObj struct {
CustomerGUID string `json:"customerGUID"`
Token string `json:"token"`
}
func (co *ConfigObj) Json() []byte {
if b, err := json.Marshal(co); err == nil {
return b
}
return []byte{}
}
type IClusterConfig interface {
SetCustomerGUID() error
GetCustomerGUID() string
GenerateURL()
}
type ClusterConfig struct {
k8s *k8sinterface.KubernetesApi
defaultNS string
armoAPI *getter.ArmoAPI
configObj *ConfigObj
}
type EmptyConfig struct {
}
func (c *EmptyConfig) GenerateURL() {
}
func (c *EmptyConfig) SetCustomerGUID() error {
return nil
}
func (c *EmptyConfig) GetCustomerGUID() string {
return ""
}
func NewEmptyConfig() *EmptyConfig {
return &EmptyConfig{}
}
func NewClusterConfig(k8s *k8sinterface.KubernetesApi, armoAPI *getter.ArmoAPI) *ClusterConfig {
return &ClusterConfig{
k8s: k8s,
armoAPI: armoAPI,
defaultNS: k8sinterface.GetDefaultNamespace(),
}
}
func (c *ClusterConfig) update(configObj *ConfigObj) {
c.configObj = configObj
ioutil.WriteFile(getter.GetDefaultPath(configFileName+".json"), c.configObj.Json(), 0664)
}
func (c *ClusterConfig) GenerateURL() {
u := url.URL{}
u.Scheme = "https"
u.Host = getter.ArmoFEURL
u.Path = "account/sign-up"
q := u.Query()
q.Add("invitationToken", c.configObj.Token)
q.Add("customerGUID", c.configObj.CustomerGUID)
u.RawQuery = q.Encode()
fmt.Println("To view all controls and get remediations visit:")
InfoTextDisplay(os.Stdout, u.String()+"\n")
}
func (c *ClusterConfig) GetCustomerGUID() string {
return c.configObj.CustomerGUID
}
func (c *ClusterConfig) SetCustomerGUID() error {
// get from configMap
if configObj, _ := c.loadConfigFromConfigMap(); configObj != nil {
c.update(configObj)
return nil
}
// get from file
if configObj, _ := c.loadConfigFromFile(); configObj != nil {
c.update(configObj)
c.updateConfigMap()
return nil
}
// get from armoBE
if tenantResponse, err := c.armoAPI.GetCustomerGUID(); tenantResponse != nil {
c.update(&ConfigObj{CustomerGUID: tenantResponse.TenantID, Token: tenantResponse.Token})
return c.updateConfigMap()
} else {
return err
}
}
func (c *ClusterConfig) loadConfigFromConfigMap() (*ConfigObj, error) {
if c.k8s == nil {
return nil, nil
}
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
if err != nil {
return nil, err
}
if bData, err := json.Marshal(configMap.Data); err == nil {
return readConfig(bData)
}
return nil, nil
}
func (c *ClusterConfig) updateConfigMap() error {
if c.k8s == nil {
return nil
}
configMap, err := c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Get(context.Background(), configMapName, metav1.GetOptions{})
if err != nil {
configMap = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configMapName,
},
}
}
c.updateConfigData(configMap)
if err != nil {
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(c.defaultNS).Create(context.Background(), configMap, metav1.CreateOptions{})
} else {
_, err = c.k8s.KubernetesClient.CoreV1().ConfigMaps(configMap.Namespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
}
return err
}
func (c *ClusterConfig) updateConfigData(configMap *corev1.ConfigMap) {
if len(configMap.Data) == 0 {
configMap.Data = make(map[string]string)
}
m := c.ToMapString()
for k, v := range m {
if s, ok := v.(string); ok {
configMap.Data[k] = s
}
}
}
func (c *ClusterConfig) loadConfigFromFile() (*ConfigObj, error) {
dat, err := ioutil.ReadFile(getter.GetDefaultPath(configFileName + ".json"))
if err != nil {
return nil, err
}
return readConfig(dat)
}
func readConfig(dat []byte) (*ConfigObj, error) {
if len(dat) == 0 {
return nil, nil
}
configObj := &ConfigObj{}
err := json.Unmarshal(dat, configObj)
return configObj, err
}
func (c *ClusterConfig) ToMapString() map[string]interface{} {
m := map[string]interface{}{}
bc, _ := json.Marshal(c.configObj)
json.Unmarshal(bc, &m)
return m
}

View File

@@ -1,9 +1,7 @@
package getter
import (
"fmt"
"net/http"
"strings"
"github.com/armosec/kubescape/cautils/armotypes"
"github.com/armosec/kubescape/cautils/opapolicy"
@@ -13,16 +11,21 @@ import (
// =============================================== ArmoAPI ===============================================================
// =======================================================================================================================
const (
ArmoBEURL = "eggdashbe.eudev3.cyberarmorsoft.com"
ArmoERURL = "report.eudev3.cyberarmorsoft.com"
ArmoFEURL = "armoui.eudev3.cyberarmorsoft.com"
// ArmoURL = "https://dashbe.euprod1.cyberarmorsoft.com"
)
// Armo API for downloading policies
type ArmoAPI struct {
httpClient *http.Client
baseURL string
}
func NewArmoAPI() *ArmoAPI {
return &ArmoAPI{
httpClient: &http.Client{},
baseURL: "https://dashbe.euprod1.cyberarmorsoft.com",
}
}
func (armoAPI *ArmoAPI) GetFramework(name string) (*opapolicy.Framework, error) {
@@ -35,20 +38,11 @@ func (armoAPI *ArmoAPI) GetFramework(name string) (*opapolicy.Framework, error)
if err = JSONDecoder(respStr).Decode(framework); err != nil {
return nil, err
}
SaveFrameworkInFile(framework, GetDefaultPath(name))
SaveFrameworkInFile(framework, GetDefaultPath(name+".json"))
return framework, err
}
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
requestURI := "v1/armoFrameworks"
requestURI += fmt.Sprintf("?customerGUID=%s", "11111111-1111-1111-1111-111111111111")
requestURI += fmt.Sprintf("&frameworkName=%s", strings.ToUpper(frameworkName))
requestURI += "&getRules=true"
return urlEncoder(fmt.Sprintf("%s/%s", armoAPI.baseURL, requestURI))
}
func (armoAPI *ArmoAPI) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
exceptions := []armotypes.PostureExceptionPolicy{}
if customerGUID == "" {
@@ -66,11 +60,21 @@ func (armoAPI *ArmoAPI) GetExceptions(customerGUID, clusterName string) ([]armot
return exceptions, nil
}
func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) string {
requestURI := "api/v1/armoPostureExceptions"
requestURI += fmt.Sprintf("?customerGUID=%s", customerGUID)
if clusterName != "" {
requestURI += fmt.Sprintf("&clusterName=%s", clusterName)
func (armoAPI *ArmoAPI) GetCustomerGUID() (*TenantResponse, error) {
respStr, err := HttpGetter(armoAPI.httpClient, armoAPI.getCustomerURL())
if err != nil {
return nil, err
}
return urlEncoder(fmt.Sprintf("%s/%s", armoAPI.baseURL, requestURI))
tenant := &TenantResponse{}
if err = JSONDecoder(respStr).Decode(tenant); err != nil {
return nil, err
}
return tenant, nil
}
type TenantResponse struct {
TenantID string `json:"tenantId"`
Token string `json:"token"`
Expires string `json:"expires"`
}

View File

@@ -0,0 +1,44 @@
package getter
import (
"net/url"
"strings"
)
func (armoAPI *ArmoAPI) getFrameworkURL(frameworkName string) string {
u := url.URL{}
u.Scheme = "https"
u.Host = ArmoBEURL
u.Path = "v1/armoFrameworks"
q := u.Query()
q.Add("customerGUID", "11111111-1111-1111-1111-111111111111")
q.Add("frameworkName", strings.ToUpper(frameworkName))
q.Add("getRules", "true")
u.RawQuery = q.Encode()
return u.String()
}
func (armoAPI *ArmoAPI) getExceptionsURL(customerGUID, clusterName string) string {
u := url.URL{}
u.Scheme = "https"
u.Host = ArmoBEURL
u.Path = "api/v1/armoPostureExceptions"
q := u.Query()
q.Add("customerGUID", customerGUID)
// if clusterName != "" { // TODO - fix customer name support in Armo BE
// q.Add("clusterName", clusterName)
// }
u.RawQuery = q.Encode()
return u.String()
}
func (armoAPI *ArmoAPI) getCustomerURL() string {
u := url.URL{}
u.Scheme = "https"
u.Host = ArmoBEURL
u.Path = "api/v1/createTenant"
return u.String()
}

View File

@@ -6,7 +6,6 @@ import (
"io/ioutil"
"net/http"
"github.com/armosec/kubescape/cautils/armotypes"
"github.com/armosec/kubescape/cautils/opapolicy"
)
@@ -27,10 +26,6 @@ func NewDownloadReleasedPolicy() *DownloadReleasedPolicy {
}
}
func (drp *DownloadReleasedPolicy) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
return []armotypes.PostureExceptionPolicy{}, nil
}
func (drp *DownloadReleasedPolicy) GetFramework(name string) (*opapolicy.Framework, error) {
drp.setURL(name)
respStr, err := HttpGetter(drp.httpClient, drp.hostURL)
@@ -43,7 +38,7 @@ func (drp *DownloadReleasedPolicy) GetFramework(name string) (*opapolicy.Framewo
return framework, err
}
SaveFrameworkInFile(framework, GetDefaultPath(name))
SaveFrameworkInFile(framework, GetDefaultPath(name+".json"))
return framework, err
}

View File

@@ -7,6 +7,8 @@ import (
type IPolicyGetter interface {
GetFramework(name string) (*opapolicy.Framework, error)
GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error)
// GetScores(scope, customerName, namespace string) ([]armotypes.PostureExceptionPolicy, error)
}
type IExceptionsGetter interface {
GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error)
}

View File

@@ -13,8 +13,8 @@ import (
"github.com/armosec/kubescape/cautils/opapolicy"
)
func GetDefaultPath(frameworkName string) string {
defaultfilePath := filepath.Join(DefaultLocalStore, frameworkName+".json")
func GetDefaultPath(name string) string {
defaultfilePath := filepath.Join(DefaultLocalStore, name)
if homeDir, err := os.UserHomeDir(); err == nil {
defaultfilePath = filepath.Join(homeDir, defaultfilePath)
}

View File

@@ -9,12 +9,15 @@ import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
// DO NOT REMOVE - load cloud providers auth
_ "k8s.io/client-go/plugin/pkg/client/auth"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
var ConnectedToCluster = true
// K8SConfig pointer to k8s config
var K8SConfig *restclient.Config
@@ -27,8 +30,15 @@ type KubernetesApi struct {
// NewKubernetesApi -
func NewKubernetesApi() *KubernetesApi {
var kubernetesClient *kubernetes.Clientset
var err error
kubernetesClient, err := kubernetes.NewForConfig(GetK8sConfig())
if !IsConnectedToCluster() {
fmt.Println(fmt.Errorf("failed to load kubernetes config: no configuration has been provided, try setting KUBECONFIG environment variable"))
os.Exit(1)
}
kubernetesClient, err = kubernetes.NewForConfig(GetK8sConfig())
if err != nil {
fmt.Printf("Failed to load config file, reason: %s", err.Error())
os.Exit(1)
@@ -53,23 +63,54 @@ var RunningIncluster bool
func LoadK8sConfig() error {
kubeconfig, err := config.GetConfig()
if err != nil {
return fmt.Errorf("failed to load kubernetes config: %s\n", strings.ReplaceAll(err.Error(), "KUBERNETES_MASTER", "KUBECONFIG"))
return fmt.Errorf("failed to load kubernetes config: %s", strings.ReplaceAll(err.Error(), "KUBERNETES_MASTER", "KUBECONFIG"))
}
if _, err := restclient.InClusterConfig(); err == nil {
RunningIncluster = true
}
K8SConfig = kubeconfig
return nil
}
// GetK8sConfig get config. load if not loaded yet
func GetK8sConfig() *restclient.Config {
if K8SConfig == nil {
if err := LoadK8sConfig(); err != nil {
// print error
fmt.Printf("%s", err.Error())
os.Exit(1)
}
if !IsConnectedToCluster() {
return nil
}
return K8SConfig
}
func IsConnectedToCluster() bool {
if K8SConfig == nil {
if err := LoadK8sConfig(); err != nil {
ConnectedToCluster = false
}
}
return ConnectedToCluster
}
func GetClusterName() string {
if !ConnectedToCluster {
return ""
}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{})
config, err := kubeConfig.RawConfig()
if err != nil {
return ""
}
// TODO - Handle if empty
return config.CurrentContext
}
func GetDefaultNamespace() string {
clientCfg, err := clientcmd.NewDefaultClientConfigLoadingRules().Load()
if err != nil {
return "default"
}
namespace := clientCfg.Contexts[clientCfg.CurrentContext].Namespace
if namespace == "" {
namespace = "default"
}
return namespace
}

View File

@@ -42,10 +42,11 @@ type FrameworkReport struct {
type ControlReport struct {
armotypes.PortalBase `json:",inline"`
Name string `json:"name"`
ID string `json:"id"`
RuleReports []RuleReport `json:"ruleReports"`
Remediation string `json:"remediation"`
Description string `json:"description"`
Score float32 `json:"score,omitempty"`
Score float32 `json:"score"`
BaseScore float32 `json:"baseScore,omitempty"`
ARMOImprovement float32 `json:"ARMOImprovement,omitempty"`
}
@@ -100,6 +101,7 @@ type PolicyRule struct {
// Control represents a collection of rules which are combined together to single purpose
type Control struct {
armotypes.PortalBase `json:",inline"`
ID string `json:"id"`
CreationTime string `json:"creationTime"`
Description string `json:"description"`
Remediation string `json:"remediation"`

View File

@@ -137,3 +137,62 @@ func (ruleReport *RuleReport) GetNumberOfWarningResources() int {
}
return sum
}
func (postureReport *PostureReport) RemoveData() {
for i := range postureReport.FrameworkReports {
postureReport.FrameworkReports[i].RemoveData()
}
}
func (frameworkReport *FrameworkReport) RemoveData() {
for i := range frameworkReport.ControlReports {
frameworkReport.ControlReports[i].RemoveData()
}
}
func (controlReport *ControlReport) RemoveData() {
for i := range controlReport.RuleReports {
controlReport.RuleReports[i].RemoveData()
}
}
func (ruleReport *RuleReport) RemoveData() {
for i := range ruleReport.RuleResponses {
ruleReport.RuleResponses[i].RemoveData()
}
}
func (r *RuleResponse) RemoveData() {
r.AlertObject.ExternalObjects = nil
keepFields := []string{"kind", "apiVersion", "metadata"}
keepMetadataFields := []string{"name", "namespace", "labels"}
for i := range r.AlertObject.K8SApiObjects {
deleteFromMap(r.AlertObject.K8SApiObjects[i], keepFields)
for k := range r.AlertObject.K8SApiObjects[i] {
if k == "metadata" {
if b, ok := r.AlertObject.K8SApiObjects[i][k].(map[string]interface{}); ok {
deleteFromMap(b, keepMetadataFields)
r.AlertObject.K8SApiObjects[i][k] = b
}
}
}
}
}
func deleteFromMap(m map[string]interface{}, keepFields []string) {
for k := range m {
if StringInSlice(keepFields, k) {
continue
}
delete(m, k)
}
}
func StringInSlice(strSlice []string, str string) bool {
for i := range strSlice {
if strSlice[i] == str {
return true
}
}
return false
}

View File

@@ -19,10 +19,11 @@ type ScanInfo struct {
InputPatterns []string
Silent bool
FailThreshold uint16
DoNotSendResults bool
}
type Getters struct {
ExceptionsGetter getter.IPolicyGetter
ExceptionsGetter getter.IExceptionsGetter
PolicyGetter getter.IPolicyGetter
}
@@ -48,7 +49,7 @@ func (scanInfo *ScanInfo) setUseFrom() {
return
}
if scanInfo.UseDefault {
scanInfo.UseFrom = getter.GetDefaultPath(scanInfo.PolicyIdentifier.Name)
scanInfo.UseFrom = getter.GetDefaultPath(scanInfo.PolicyIdentifier.Name + ".json")
}
}
@@ -80,3 +81,8 @@ func (scanInfo *ScanInfo) setOutputFile() {
func (scanInfo *ScanInfo) ScanRunningCluster() bool {
return len(scanInfo.InputPatterns) == 0
}
// func (scanInfo *ScanInfo) ConnectedToCluster(k8s k8sinterface.) bool {
// _, err := k8s.KubernetesClient.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
// return err == nil
// }

View File

@@ -22,9 +22,9 @@ var downloadCmd = &cobra.Command{
},
RunE: func(cmd *cobra.Command, args []string) error {
downloadInfo.FrameworkName = args[1]
g := getter.NewArmoAPI()
g := getter.NewDownloadReleasedPolicy()
if downloadInfo.Path == "" {
downloadInfo.Path = getter.GetDefaultPath(downloadInfo.FrameworkName)
downloadInfo.Path = getter.GetDefaultPath(downloadInfo.FrameworkName + ".json")
}
frameworks, err := g.GetFramework(downloadInfo.FrameworkName)
if err != nil {

View File

@@ -10,6 +10,7 @@ import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/armotypes"
"github.com/armosec/kubescape/cautils/getter"
"github.com/armosec/kubescape/cautils/k8sinterface"
"github.com/armosec/kubescape/cautils/opapolicy"
"github.com/armosec/kubescape/opaprocessor"
@@ -93,7 +94,7 @@ func init() {
frameworkCmd.Flags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. print output to file and not stdout")
frameworkCmd.Flags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
frameworkCmd.Flags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 0, "Failure threshold is the percent bellow which the command fails and returns exit code -1")
frameworkCmd.Flags().BoolVarP(&scanInfo.DoNotSendResults, "results-locally", "", false, "Kubescape sends scan results to Armosec backend to allow users to control exceptions and maintain chronological scan results. Use this flag if you do not wish to use these features")
}
func CliSetup() error {
@@ -105,7 +106,9 @@ func CliSetup() error {
}
var k8s *k8sinterface.KubernetesApi
if scanInfo.ScanRunningCluster() {
if !scanInfo.ScanRunningCluster() {
k8sinterface.ConnectedToCluster = false
} else {
k8s = k8sinterface.NewKubernetesApi()
}
@@ -115,12 +118,29 @@ func CliSetup() error {
// policy handler setup
policyHandler := policyhandler.NewPolicyHandler(&processNotification, k8s)
// cli handler setup
cli := NewCLIHandler(policyHandler)
if err := cli.Scan(); err != nil {
panic(err)
// load cluster config
var clusterConfig cautils.IClusterConfig
if !scanInfo.DoNotSendResults && k8sinterface.ConnectedToCluster {
clusterConfig = cautils.NewClusterConfig(k8s, getter.NewArmoAPI())
} else {
clusterConfig = cautils.NewEmptyConfig()
}
if err := clusterConfig.SetCustomerGUID(); err != nil {
fmt.Println(err)
}
cautils.CustomerGUID = clusterConfig.GetCustomerGUID()
cautils.ClusterName = k8sinterface.GetClusterName()
// cli handler setup
go func() {
cli := NewCLIHandler(policyHandler)
if err := cli.Scan(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}()
// processor setup - rego run
go func() {
opaprocessorObj := opaprocessor.NewOPAProcessorHandler(&processNotification, &reportResults)
@@ -130,6 +150,9 @@ func CliSetup() error {
resultsHandling := resultshandling.NewResultsHandler(&reportResults, reporter.NewReportEventReceiver(), printer.NewPrinter(scanInfo.Format, scanInfo.Output))
score := resultsHandling.HandleResults()
// print report url
clusterConfig.GenerateURL()
adjustedFailThreshold := float32(scanInfo.FailThreshold) / 100
if score < adjustedFailThreshold {
return fmt.Errorf("Scan score is bellow threshold")
@@ -156,12 +179,10 @@ func (clihandler *CLIHandler) Scan() error {
}
switch policyNotification.NotificationType {
case opapolicy.TypeExecPostureScan:
go func() {
if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, clihandler.scanInfo); err != nil {
fmt.Printf("%v\n", err)
os.Exit(0)
}
}()
//
if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, clihandler.scanInfo); err != nil {
return err
}
default:
return fmt.Errorf("notification type '%s' Unknown", policyNotification.NotificationType)
}

49
cmd/version.go Normal file
View File

@@ -0,0 +1,49 @@
package cmd
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/spf13/cobra"
)
var BuildNumber string
var versionCmd = &cobra.Command{
Use: "version",
Short: "Get current version",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("Your current version is: " + BuildNumber)
return nil
},
}
func GetLatestVersion() (string, error) {
latestVersion := "https://api.github.com/repos/armosec/kubescape/releases/latest"
resp, err := http.Get(latestVersion)
if err != nil {
return "", fmt.Errorf("failed to get latest releases from '%s', reason: %s", latestVersion, err.Error())
}
defer resp.Body.Close()
if resp.StatusCode < 200 || 301 < resp.StatusCode {
return "", fmt.Errorf("failed to download file, status code: %s", resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response body from '%s', reason: %s", latestVersion, err.Error())
}
var data map[string]interface{}
err = json.Unmarshal(body, &data)
if err != nil {
return "", fmt.Errorf("failed to unmarshal response body from '%s', reason: %s", latestVersion, err.Error())
}
return fmt.Sprintf("%v", data["tag_name"]), nil
}
func init() {
rootCmd.AddCommand(versionCmd)
}

View File

@@ -32,12 +32,12 @@ echo -e "\033[32m[V] Downloaded Kubescape"
# Ping download counter
curl --silent https://us-central1-elated-pottery-310110.cloudfunctions.net/kubescape-download-counter -o /dev/null
chmod +x $OUTPUT || sudo chmod +x $OUTPUT
rm -f /usr/local/bin/$KUBESCAPE_EXEC || sudo rm -f /usr/local/bin/$KUBESCAPE_EXEC
cp $OUTPUT /usr/local/bin || sudo cp $OUTPUT /usr/local/bin
chmod +x $OUTPUT 2>&1 || sudo chmod +x $OUTPUT
rm -f /usr/local/bin/$KUBESCAPE_EXEC 2>&1 || sudo rm -f /usr/local/bin/$KUBESCAPE_EXEC
cp $OUTPUT /usr/local/bin 2>&1 || sudo cp $OUTPUT /usr/local/bin
rm -rf $OUTPUT
echo -e "[V] Finished Installation"
echo -e "[V] Finished Installation." $($KUBESCAPE_EXEC version)
echo
echo -e "\033[35m Usage: $ $KUBESCAPE_EXEC scan framework nsa --exclude-namespaces kube-system,kube-public"

18
main.go
View File

@@ -1,7 +1,23 @@
package main
import "github.com/armosec/kubescape/cmd"
import (
"fmt"
"os"
"github.com/armosec/kubescape/cmd"
)
func main() {
CheckLatestVersion()
cmd.Execute()
}
func CheckLatestVersion() {
latest, err := cmd.GetLatestVersion()
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
} else if latest != cmd.BuildNumber {
fmt.Println("Warning: You are not updated to the latest release: " + latest)
}
}

View File

@@ -13,11 +13,11 @@ import (
"github.com/armosec/kubescape/cautils/opapolicy"
"github.com/armosec/kubescape/cautils/opapolicy/resources"
"github.com/golang/glog"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/rego"
"github.com/open-policy-agent/opa/storage"
uuid "github.com/satori/go.uuid"
)
const ScoreConfigPath = "/resources/config"
@@ -42,7 +42,7 @@ func NewOPAProcessor(sessionObj *cautils.OPASessionObj) *OPAProcessor {
func NewOPAProcessorHandler(processedPolicy, reportResults *chan *cautils.OPASessionObj) *OPAProcessorHandler {
regoDependenciesData := resources.NewRegoDependenciesData(k8sinterface.K8SConfig)
regoDependenciesData := resources.NewRegoDependenciesData(k8sinterface.GetK8sConfig())
store, err := regoDependenciesData.TOStorage()
if err != nil {
panic(err)
@@ -70,7 +70,7 @@ func (opaHandler *OPAProcessorHandler) ProcessRulesListenner() {
opap.updateResults()
// update score
opap.updateScore()
// opap.updateScore()
// report
*opaHandler.reportResults <- opaSessionObj
@@ -92,6 +92,7 @@ func (opap *OPAProcessor) Process() error {
}
opap.PostureReport.FrameworkReports = frameworkReports
opap.PostureReport.ReportID = uuid.NewV4().String()
opap.PostureReport.ReportGenerationTime = time.Now().UTC()
// glog.Infof(fmt.Sprintf("Done 'Process'. reportID: %s", opap.PostureReport.ReportID))
cautils.StopSpinner()
@@ -104,6 +105,7 @@ func (opap *OPAProcessor) processFramework(framework *opapolicy.Framework) (*opa
frameworkReport := opapolicy.FrameworkReport{}
frameworkReport.Name = framework.Name
controlReports := []opapolicy.ControlReport{}
for i := range framework.Controls {
controlReport, err := opap.processControl(&framework.Controls[i])
@@ -123,6 +125,7 @@ func (opap *OPAProcessor) processControl(control *opapolicy.Control) (*opapolicy
controlReport.PortalBase = control.PortalBase
controlReport.Name = control.Name
controlReport.ID = control.ID
controlReport.Description = control.Description
controlReport.Remediation = control.Remediation
@@ -219,6 +222,10 @@ func (opap *OPAProcessor) regoEval(inputObj []map[string]interface{}, compiledRe
func (opap *OPAProcessor) updateScore() {
if !k8sinterface.ConnectedToCluster {
return
}
// calculate score
s := score.NewScore(k8sinterface.NewKubernetesApi(), ScoreConfigPath)
s.Calculate(opap.PostureReport.FrameworkReports)
@@ -226,6 +233,8 @@ func (opap *OPAProcessor) updateScore() {
func (opap *OPAProcessor) updateResults() {
for f, frameworkReport := range opap.PostureReport.FrameworkReports {
sumFailed := 0
sumTotal := 0
for c, controlReport := range opap.PostureReport.FrameworkReports[f].ControlReports {
for r, ruleReport := range opap.PostureReport.FrameworkReports[f].ControlReports[c].RuleReports {
// editing the responses -> removing duplications, clearing secret data, etc.
@@ -235,6 +244,10 @@ func (opap *OPAProcessor) updateResults() {
ruleExceptions := exceptions.ListRuleExceptions(opap.Exceptions, frameworkReport.Name, controlReport.Name, ruleReport.Name)
exceptions.AddExceptionsToRuleResponses(opap.PostureReport.FrameworkReports[f].ControlReports[c].RuleReports[r].RuleResponses, ruleExceptions)
}
sumFailed += controlReport.GetNumberOfFailedResources()
sumTotal += controlReport.GetNumberOfResources()
opap.PostureReport.FrameworkReports[f].ControlReports[c].Score = float32(percentage(controlReport.GetNumberOfResources(), controlReport.GetNumberOfFailedResources()))
}
opap.PostureReport.FrameworkReports[f].Score = float32(percentage(sumTotal, sumTotal-sumFailed))
}
}

View File

@@ -139,3 +139,13 @@ func listMatchKinds(match []opapolicy.RuleMatchObjects) []string {
}
return matchKinds
}
func percentage(big, small int) int {
if big == 0 {
if small == 0 {
return 100
}
return 0
}
return int(float64(float64(big-small)/float64(big)) * 100)
}

View File

@@ -79,7 +79,7 @@ func (policyHandler *PolicyHandler) getPolicies(notification *opapolicy.PolicyNo
func (policyHandler *PolicyHandler) getResources(notification *opapolicy.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *cautils.ScanInfo) (*cautils.K8SResources, error) {
var k8sResources *cautils.K8SResources
var err error
if scanInfo.ScanRunningCluster() {
if k8sinterface.ConnectedToCluster {
k8sResources, err = policyHandler.getK8sResources(opaSessionObj.Frameworks, &notification.Designators, scanInfo.ExcludedNamespaces)
} else {
k8sResources, err = policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo)

View File

@@ -3,6 +3,7 @@ package policyhandler
import (
"fmt"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/armotypes"
"github.com/armosec/kubescape/cautils/opapolicy"
)
@@ -41,7 +42,7 @@ func (policyHandler *PolicyHandler) getFrameworkPolicies(policyName string) (*op
return nil, nil, err
}
receivedException, err := policyHandler.getters.ExceptionsGetter.GetExceptions("", "")
receivedException, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.CustomerGUID, cautils.ClusterName)
if err != nil {
return receivedFramework, nil, err
}

View File

@@ -178,6 +178,10 @@ func (printer *Printer) printResult(controlName string, controlSummary *ControlS
}
func (printer *Printer) PrintUrl(url string) {
cautils.InfoTextDisplay(printer.writer, url)
}
func generateRow(control string, cs ControlSummary) []string {
row := []string{control}
row = append(row, cs.ToSlice()...)
@@ -247,7 +251,7 @@ func (printer *Printer) getSortedControlsNames() []string {
}
func getWriter(outputFile string) *os.File {
os.Remove(outputFile)
if outputFile != "" {
f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {

View File

@@ -28,6 +28,8 @@ func (report *ReportEventReceiver) ActionSendReportListenner(opaSessionObj *caut
if cautils.CustomerGUID == "" {
return
}
//Add score
opaSessionObj.PostureReport.RemoveData()
if err := report.Send(opaSessionObj.PostureReport); err != nil {
fmt.Println(err)
}

View File

@@ -8,6 +8,7 @@ import (
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/getter"
"github.com/gofrs/uuid"
)
@@ -36,12 +37,12 @@ func initEventReceiverURL() *url.URL {
urlObj := url.URL{}
urlObj.Scheme = "https"
urlObj.Host = "report.euprod1.cyberarmorsoft.com"
urlObj.Host = getter.ArmoERURL
urlObj.Path = "/k8s/postureReport"
q := urlObj.Query()
q.Add("customerGUID", uuid.FromStringOrNil(cautils.CustomerGUID).String())
q.Add("clusterName", cautils.ClusterName)
urlObj.RawQuery = q.Encode()
return &urlObj
@@ -49,9 +50,7 @@ func initEventReceiverURL() *url.URL {
func hostToString(host *url.URL, reportID string) string {
q := host.Query()
if reportID != "" {
q.Add("reportID", reportID) // TODO - do we add the reportID?
}
q.Add("reportID", reportID) // TODO - do we add the reportID?
host.RawQuery = q.Encode()
return host.String()
}

View File

@@ -24,9 +24,9 @@ func (resultsHandler *ResultsHandler) HandleResults() float32 {
opaSessionObj := <-*resultsHandler.opaSessionObj
resultsHandler.reporterObj.ActionSendReportListenner(opaSessionObj)
score := resultsHandler.printerObj.ActionPrint(opaSessionObj)
resultsHandler.reporterObj.ActionSendReportListenner(opaSessionObj)
return score
}

View File

@@ -1,6 +1,7 @@
package exceptions
import (
"github.com/armosec/kubescape/cautils"
"github.com/armosec/kubescape/cautils/k8sinterface"
"github.com/armosec/kubescape/cautils/armotypes"
@@ -91,9 +92,9 @@ func hasException(designator *armotypes.PortalDesignator, workload k8sinterface.
return false // if designators are empty
}
// if cluster != "" && cluster != ClusterName { // TODO - where do we receive cluster name from?
// return false // cluster name does not match
// }
if cluster != "" && cautils.ClusterName != "" && cluster != cautils.ClusterName { // TODO - where do we receive cluster name from?
return false // cluster name does not match
}
if namespace != "" && !compareNamespace(workload, namespace) {
return false // namespaces do not match