Support yaml input and silent mode print

Support yaml input and silent mode print
This commit is contained in:
David Wertenteil
2021-08-26 18:37:42 +03:00
committed by GitHub
31 changed files with 1398 additions and 171 deletions

View File

@@ -1,7 +1,5 @@
package armotypes
import "github.com/golang/glog"
var IgnoreLabels = []string{AttributeCluster, AttributeNamespace}
// DigestPortalDesignator - get cluster namespace and labels from designator
@@ -12,7 +10,6 @@ func DigestPortalDesignator(designator *PortalDesignator) (string, string, map[s
// case DesignatorWlid: TODO
// case DesignatorWildWlid: TODO
default:
glog.Warningf("in 'digestPortalDesignator' designator type: '%v' not yet supported. please contact Armo team", designator.DesignatorType)
}
return "", "", nil
}

View File

@@ -10,6 +10,16 @@ import (
"github.com/mattn/go-isatty"
)
var silent = false
func SetSilentMode(s bool) {
silent = s
}
func IsSilent() bool {
return silent
}
var FailureDisplay = color.New(color.Bold, color.FgHiRed).FprintfFunc()
var FailureTextDisplay = color.New(color.Faint, color.FgHiRed).FprintfFunc()
var InfoDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
@@ -20,19 +30,41 @@ var DescriptionDisplay = color.New(color.Faint, color.FgWhite).FprintfFunc()
var Spinner *spinner.Spinner
func ScanStartDisplay() {
if IsSilent() {
return
}
InfoDisplay(os.Stdout, "ARMO security scanner starting\n")
}
func SuccessTextDisplay(str string) {
if IsSilent() {
return
}
SuccessDisplay(os.Stdout, "[success] ")
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
}
func ErrorDisplay(str string) {
if IsSilent() {
return
}
SuccessDisplay(os.Stdout, "[Error] ")
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
}
func ProgressTextDisplay(str string) {
if IsSilent() {
return
}
InfoDisplay(os.Stdout, "[progress] ")
SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n", str))
}
func StartSpinner() {
if isatty.IsTerminal(os.Stdout.Fd()) {
if !IsSilent() && isatty.IsTerminal(os.Stdout.Fd()) {
Spinner = spinner.New(spinner.CharSets[7], 100*time.Millisecond) // Build our new spinner
Spinner.Start()
}

View File

@@ -2,7 +2,6 @@ package k8sinterface
import (
"encoding/json"
"fmt"
"kube-escape/cautils/apis"
@@ -15,9 +14,16 @@ import (
type IWorkload interface {
IBasicWorkload
// Convert
ToUnstructured() (*unstructured.Unstructured, error)
ToString() string
Json() string // DEPRECATED
// GET
GetWlid() string
GetJobID() *apis.JobTracking
GetVersion() string
GetGroup() string
// SET
SetWlid(string)
@@ -27,6 +33,7 @@ type IWorkload interface {
SetJobID(apis.JobTracking)
SetCompatible()
SetIncompatible()
SetReplaceheaders()
// EXIST
IsIgnore() bool
@@ -37,6 +44,7 @@ type IWorkload interface {
// REMOVE
RemoveWlid()
RemoveSecretData()
RemoveInject()
RemoveIgnore()
RemoveUpdateTime()
@@ -62,8 +70,8 @@ type IBasicWorkload interface {
GetGenerateName() string
GetApiVersion() string
GetKind() string
GetInnerAnnotation() (string, bool)
GetPodAnnotation() (string, bool)
GetInnerAnnotation(string) (string, bool)
GetPodAnnotation(string) (string, bool)
GetAnnotation(string) (string, bool)
GetLabel(string) (string, bool)
GetAnnotations() map[string]string
@@ -72,16 +80,17 @@ type IBasicWorkload interface {
GetLabels() map[string]string
GetInnerLabels() map[string]string
GetPodLabels() map[string]string
GetJobLabels() map[string]string
GetVolumes() []corev1.Volume
GetContainers() []corev1.Container
GetInitContainers() []corev1.Container
GetVolumes() ([]corev1.Volume, error)
GetReplicas() int
GetContainers() ([]corev1.Container, error)
GetInitContainers() ([]corev1.Container, error)
GetOwnerReferences() ([]metav1.OwnerReference, error)
GetImagePullSecret() ([]corev1.LocalObjectReference, error)
GetServiceAccountName() string
GetSelector() (*metav1.LabelSelector, error)
GetResourceVersion() string
GetUID() string
GetPodSpec() (*corev1.PodSpec, error)
GetWorkload() map[string]interface{}
@@ -115,14 +124,17 @@ func NewWorkloadObj(workload map[string]interface{}) *Workload {
}
func (w *Workload) Json() string {
if w.workload == nil {
return w.ToString()
}
func (w *Workload) ToString() string {
if w.GetWorkload() == nil {
return ""
}
bWorkload, err := json.Marshal(w.workload)
bWorkload, err := json.Marshal(w.GetWorkload())
if err != nil {
return err.Error()
}
return fmt.Sprintf("%s", bWorkload)
return string(bWorkload)
}
func (workload *Workload) DeepCopy(w map[string]interface{}) {

View File

@@ -117,6 +117,10 @@ func (w *Workload) RemoveUpdateTime() {
w.RemoveAnnotation(cautils.CAUpdate) // DEPRECATED
w.RemoveAnnotation(cautils.ArmoUpdate)
}
func (w *Workload) RemoveSecretData() {
w.RemoveAnnotation("kubectl.kubernetes.io/last-applied-configuration")
delete(w.workload, "data")
}
func (w *Workload) RemovePodStatus() {
delete(w.workload, "status")
@@ -268,6 +272,26 @@ func (w *Workload) GetApiVersion() string {
return ""
}
func (w *Workload) GetVersion() string {
apiVersion := w.GetApiVersion()
splitted := strings.Split(apiVersion, "/")
if len(splitted) == 1 {
return splitted[0]
} else if len(splitted) == 2 {
return splitted[1]
}
return ""
}
func (w *Workload) GetGroup() string {
apiVersion := w.GetApiVersion()
splitted := strings.Split(apiVersion, "/")
if len(splitted) == 2 {
return splitted[0]
}
return ""
}
func (w *Workload) GetGenerateName() string {
if v, ok := InspectWorkload(w.workload, "metadata", "generateName"); ok {
return v.(string)
@@ -275,6 +299,16 @@ func (w *Workload) GetGenerateName() string {
return ""
}
func (w *Workload) GetReplicas() int {
if v, ok := InspectWorkload(w.workload, "spec", "replicas"); ok {
replicas, isok := v.(float64)
if isok {
return int(replicas)
}
}
return 1
}
func (w *Workload) GetKind() string {
if v, ok := InspectWorkload(w.workload, "kind"); ok {
return v.(string)
@@ -453,7 +487,7 @@ func (w *Workload) GetContainers() ([]corev1.Container, error) {
return containers, err
}
// GetContainers -
// GetInitContainers -
func (w *Workload) GetInitContainers() ([]corev1.Container, error) {
containers := []corev1.Container{}
@@ -606,52 +640,3 @@ func InspectWorkload(workload interface{}, scopes ...string) (val interface{}, k
return val, k
}
// // InspectWorkload -
// func InjectWorkload(workload interface{}, scopes []string, val string) {
// if len(scopes) == 0 {
// }
// if data, ok := workload.(map[string]interface{}); ok {
// InjectWorkload(data[scopes[0]], scopes[1:], val)
// } else {
// }
// }
// InjectWorkload -
// func InjectWorkload(workload interface{}, scopes []string, val string) {
// if len(scopes) == 0 {
// workload = ""
// }
// if data, ok := workload.(map[string]interface{}); ok {
// d := InjectWorkload(data[scopes[0]], scopes[1:], val)
// data[scopes[0]] = d
// return data
// } else {
// }
// }
// func (w *Workload) SetNamespace(ns string) {
// if v, k := w.workload["metadata"]; k {
// if vv, kk := v.(map[string]interface{}); kk {
// vv["namespace"] = ""
// // if v3, k3 := w.workload["namespace"]; k3 {
// // if v4, k4 := v.(map[string]interface{}); kk {
// // }
// // }
// v = vv
// }
// w.workload = v
// }
// // if data, ok := w.workload.(map[string]interface{}); ok {
// // val, k = InspectWorkload(data[scopes[0]], scopes[1:]...)
// // }
// }

View File

@@ -150,9 +150,9 @@ type PolicyIdentifier struct {
}
type ScanInfo struct {
PolicyIdentifier PolicyIdentifier `json:"policyIdentifier"`
Output string `json:"output"`
ExcludedNamespaces string `json:"excludedNamespaces"`
Input []string `json:"input"`
Silent bool `json:"silent"`
PolicyIdentifier PolicyIdentifier
Output string
ExcludedNamespaces string
InputPatterns []string
Silent bool
}

View File

@@ -2,6 +2,7 @@ package cmd
import (
"errors"
"flag"
"fmt"
"io/ioutil"
"kube-escape/cautils"
@@ -18,6 +19,8 @@ import (
)
var scanInfo opapolicy.ScanInfo
var supportedFrameworks = []string{"nsa", "mitre"}
var isSilent bool
type CLIHandler struct {
policyHandler *policyhandler.PolicyHandler
@@ -42,22 +45,22 @@ var frameworkCmd = &cobra.Command{
scanInfo.PolicyIdentifier = opapolicy.PolicyIdentifier{}
scanInfo.PolicyIdentifier.Kind = opapolicy.KindFramework
scanInfo.PolicyIdentifier.Name = args[0]
scanInfo.Input = args[1:]
scanInfo.InputPatterns = args[1:]
cautils.SetSilentMode(scanInfo.Silent)
CliSetup()
},
}
func isValidFramework(framework string) bool {
return framework == "nsa" || framework != "mitre"
return cautils.StringInSlice(supportedFrameworks, framework) != cautils.ValueNotFound
}
func init() {
scanCmd.AddCommand(frameworkCmd)
scanInfo = opapolicy.ScanInfo{}
frameworkCmd.Flags().StringVarP(&scanInfo.ExcludedNamespaces, "excluded-namespaces", "e", "", "namespaces to exclude from check")
frameworkCmd.Flags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "namespaces to exclude from check")
frameworkCmd.Flags().StringVarP(&scanInfo.Output, "output", "o", "pretty-printer", "output format")
frameworkCmd.Flags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "silent output")
}
func processYamlInput(yamls string) {
@@ -106,20 +109,19 @@ func NewCLIHandler(policyHandler *policyhandler.PolicyHandler) *CLIHandler {
}
func (clihandler *CLIHandler) Scan() error {
cautils.InfoDisplay(os.Stdout, "ARMO security scanner starting\n")
cautils.ScanStartDisplay()
policyNotification := &opapolicy.PolicyNotification{
NotificationType: opapolicy.TypeExecPostureScan,
Rules: []opapolicy.PolicyIdentifier{
*&clihandler.scanInfo.PolicyIdentifier,
clihandler.scanInfo.PolicyIdentifier,
},
Designators: armotypes.PortalDesignator{},
}
flag.Parse()
switch policyNotification.NotificationType {
case opapolicy.TypeExecPostureScan:
go func() {
if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, scanInfo.ExcludedNamespaces); err != nil {
if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification, clihandler.scanInfo); err != nil {
fmt.Printf("%v\n", err)
os.Exit(0)
}

5
examples/example.sh Normal file
View File

@@ -0,0 +1,5 @@
#! /bin/bash
echo "Testing Online Boutique yamls (https://github.com/GoogleCloudPlatform/microservices-demo)"
kubescape scan framework nsa online-boutique/*

View File

@@ -0,0 +1,8 @@
# ./kubernetes-manifests
:warning: Kubernetes manifests provided in this directory are not directly
deployable to a cluster. They are meant to be used with `skaffold` command to
insert the correct `image:` tags.
Use the manifests in [/release](/release) directory which are configured with
pre-built public images.

View File

@@ -0,0 +1,73 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: adservice
spec:
selector:
matchLabels:
app: adservice
template:
metadata:
labels:
app: adservice
spec:
serviceAccountName: default
terminationGracePeriodSeconds: 5
containers:
- name: server
image: adservice
ports:
- containerPort: 9555
env:
- name: PORT
value: "9555"
# - name: DISABLE_STATS
# value: "1"
# - name: DISABLE_TRACING
# value: "1"
#- name: JAEGER_SERVICE_ADDR
# value: "jaeger-collector:14268"
resources:
requests:
cpu: 200m
memory: 180Mi
limits:
cpu: 300m
memory: 300Mi
readinessProbe:
initialDelaySeconds: 20
periodSeconds: 15
exec:
command: ["/bin/grpc_health_probe", "-addr=:9555"]
livenessProbe:
initialDelaySeconds: 20
periodSeconds: 15
exec:
command: ["/bin/grpc_health_probe", "-addr=:9555"]
---
apiVersion: v1
kind: Service
metadata:
name: adservice
spec:
type: ClusterIP
selector:
app: adservice
ports:
- name: grpc
port: 9555
targetPort: 9555

View File

@@ -0,0 +1,59 @@
apiVersion: v1
data:
customer: Q3liZXJBcm1vclRlc3Rz
password: bml1ZGhmMjgzcnUyM3JrZQ==
username: ZHdlcnRlbnRAY3liZXJhcm1vci5pbw==
kind: Secret
metadata:
name: bi-monitor-secret
type: Opaque
---
apiVersion: v1
kind: Service
metadata:
name: bi-monitor
spec:
type: ClusterIP
selector:
app: bi-monitor
ports:
- name: http
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bi-monitor
labels:
app: bi-monitor
spec:
replicas: 1
selector:
matchLabels:
app: bi-monitor
template:
metadata:
labels:
app: bi-monitor
spec:
containers:
- name: monitor
image: quay.io/armosec/demoservice:v1-debian
env:
- name: THREAD_TIMEOUT
value: "10"
- name: SLEEP_DURATION
value: "1"
- name: DEMO_TARGETS
value: "http://frontend:80 https://cisco.com"
- name: CAA_SIGNATURE_DEBUG_DEEP
volumeMounts:
- name: bi-monitor-secret
mountPath: /etc/secrets
volumes:
- name: bi-monitor-secret
secret:
secretName: bi-monitor-secret

View File

@@ -0,0 +1,66 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: cartservice
spec:
selector:
matchLabels:
app: cartservice
template:
metadata:
labels:
app: cartservice
spec:
serviceAccountName: default
terminationGracePeriodSeconds: 5
containers:
- name: server
image: cartservice
ports:
- containerPort: 7070
env:
- name: REDIS_ADDR
value: "redis-cart:6379"
resources:
requests:
cpu: 200m
memory: 64Mi
limits:
cpu: 300m
memory: 128Mi
readinessProbe:
initialDelaySeconds: 15
exec:
command: ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"]
livenessProbe:
initialDelaySeconds: 15
periodSeconds: 10
exec:
command: ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"]
---
apiVersion: v1
kind: Service
metadata:
name: cartservice
spec:
type: ClusterIP
selector:
app: cartservice
ports:
- name: grpc
port: 7070
targetPort: 7070

View File

@@ -0,0 +1,82 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkoutservice
spec:
selector:
matchLabels:
app: checkoutservice
template:
metadata:
labels:
app: checkoutservice
spec:
serviceAccountName: default
containers:
- name: server
image: checkoutservice
ports:
- containerPort: 5050
readinessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:5050"]
livenessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:5050"]
env:
- name: PORT
value: "5050"
- name: PRODUCT_CATALOG_SERVICE_ADDR
value: "productcatalogservice:3550"
- name: SHIPPING_SERVICE_ADDR
value: "shippingservice:50051"
- name: PAYMENT_SERVICE_ADDR
value: "paymentservice:50051"
- name: EMAIL_SERVICE_ADDR
value: "emailservice:5000"
- name: CURRENCY_SERVICE_ADDR
value: "currencyservice:7000"
- name: CART_SERVICE_ADDR
value: "cartservice:7070"
# - name: DISABLE_STATS
# value: "1"
# - name: DISABLE_TRACING
# value: "1"
# - name: DISABLE_PROFILER
# value: "1"
# - name: JAEGER_SERVICE_ADDR
# value: "jaeger-collector:14268"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: checkoutservice
spec:
type: ClusterIP
selector:
app: checkoutservice
ports:
- name: grpc
port: 5050
targetPort: 5050

View File

@@ -0,0 +1,70 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: currencyservice
spec:
selector:
matchLabels:
app: currencyservice
template:
metadata:
labels:
app: currencyservice
spec:
serviceAccountName: default
terminationGracePeriodSeconds: 5
containers:
- name: server
image: currencyservice
ports:
- name: grpc
containerPort: 7000
env:
- name: PORT
value: "7000"
# - name: DISABLE_TRACING
# value: "1"
# - name: DISABLE_PROFILER
# value: "1"
# - name: DISABLE_DEBUGGER
# value: "1"
readinessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:7000"]
livenessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:7000"]
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: currencyservice
spec:
type: ClusterIP
selector:
app: currencyservice
ports:
- name: grpc
port: 7000
targetPort: 7000

View File

@@ -0,0 +1,69 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: emailservice
spec:
selector:
matchLabels:
app: emailservice
template:
metadata:
labels:
app: emailservice
spec:
serviceAccountName: default
terminationGracePeriodSeconds: 5
containers:
- name: server
image: emailservice
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
# - name: DISABLE_TRACING
# value: "1"
- name: DISABLE_PROFILER
value: "1"
readinessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:8080"]
livenessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:8080"]
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: emailservice
spec:
type: ClusterIP
selector:
app: emailservice
ports:
- name: grpc
port: 5000
targetPort: 8080

View File

@@ -0,0 +1,109 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
annotations:
sidecar.istio.io/rewriteAppHTTPProbers: "true"
spec:
serviceAccountName: default
containers:
- name: server
image: frontend
ports:
- containerPort: 8080
readinessProbe:
initialDelaySeconds: 10
httpGet:
path: "/_healthz"
port: 8080
httpHeaders:
- name: "Cookie"
value: "shop_session-id=x-readiness-probe"
livenessProbe:
initialDelaySeconds: 10
httpGet:
path: "/_healthz"
port: 8080
httpHeaders:
- name: "Cookie"
value: "shop_session-id=x-liveness-probe"
env:
- name: PORT
value: "8080"
- name: PRODUCT_CATALOG_SERVICE_ADDR
value: "productcatalogservice:3550"
- name: CURRENCY_SERVICE_ADDR
value: "currencyservice:7000"
- name: CART_SERVICE_ADDR
value: "cartservice:7070"
- name: RECOMMENDATION_SERVICE_ADDR
value: "recommendationservice:8080"
- name: SHIPPING_SERVICE_ADDR
value: "shippingservice:50051"
- name: CHECKOUT_SERVICE_ADDR
value: "checkoutservice:5050"
- name: AD_SERVICE_ADDR
value: "adservice:9555"
- name: ENV_PLATFORM
value: "gcp"
# - name: DISABLE_TRACING
# value: "1"
# - name: DISABLE_PROFILER
# value: "1"
# - name: JAEGER_SERVICE_ADDR
# value: "jaeger-collector:14268"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
type: ClusterIP
selector:
app: frontend
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: frontend-external
spec:
type: LoadBalancer
selector:
app: frontend
ports:
- name: http
port: 80
targetPort: 8080

View File

@@ -0,0 +1,47 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: loadgenerator
spec:
selector:
matchLabels:
app: loadgenerator
replicas: 1
template:
metadata:
labels:
app: loadgenerator
annotations:
sidecar.istio.io/rewriteAppHTTPProbers: "true"
spec:
serviceAccountName: default
terminationGracePeriodSeconds: 5
restartPolicy: Always
containers:
- name: main
image: loadgenerator
env:
- name: FRONTEND_ADDR
value: "frontend:80"
- name: USERS
value: "10"
resources:
requests:
cpu: 300m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi

View File

@@ -0,0 +1,63 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: paymentservice
spec:
selector:
matchLabels:
app: paymentservice
template:
metadata:
labels:
app: paymentservice
spec:
serviceAccountName: default
terminationGracePeriodSeconds: 5
containers:
- name: server
image: paymentservice
ports:
- containerPort: 50051
env:
- name: PORT
value: "50051"
readinessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051"]
livenessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051"]
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: paymentservice
spec:
type: ClusterIP
selector:
app: paymentservice
ports:
- name: grpc
port: 50051
targetPort: 50051

View File

@@ -0,0 +1,71 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: productcatalogservice
spec:
selector:
matchLabels:
app: productcatalogservice
template:
metadata:
labels:
app: productcatalogservice
spec:
serviceAccountName: default
terminationGracePeriodSeconds: 5
containers:
- name: server
image: productcatalogservice
ports:
- containerPort: 3550
env:
- name: PORT
value: "3550"
# - name: DISABLE_STATS
# value: "1"
# - name: DISABLE_TRACING
# value: "1"
# - name: DISABLE_PROFILER
# value: "1"
# - name: JAEGER_SERVICE_ADDR
# value: "jaeger-collector:14268"
readinessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:3550"]
livenessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:3550"]
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: productcatalogservice
spec:
type: ClusterIP
selector:
app: productcatalogservice
ports:
- name: grpc
port: 3550
targetPort: 3550

View File

@@ -0,0 +1,73 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: recommendationservice
spec:
selector:
matchLabels:
app: recommendationservice
template:
metadata:
labels:
app: recommendationservice
spec:
serviceAccountName: default
terminationGracePeriodSeconds: 5
containers:
- name: server
image: recommendationservice
ports:
- containerPort: 8080
readinessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:8080"]
livenessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:8080"]
env:
- name: PORT
value: "8080"
- name: PRODUCT_CATALOG_SERVICE_ADDR
value: "productcatalogservice:3550"
# - name: DISABLE_TRACING
# value: "1"
# - name: DISABLE_PROFILER
# value: "1"
# - name: DISABLE_DEBUGGER
# value: "1"
resources:
requests:
cpu: 100m
memory: 220Mi
limits:
cpu: 200m
memory: 450Mi
---
apiVersion: v1
kind: Service
metadata:
name: recommendationservice
spec:
type: ClusterIP
selector:
app: recommendationservice
ports:
- name: grpc
port: 8080
targetPort: 8080

View File

@@ -0,0 +1,66 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-cart
spec:
selector:
matchLabels:
app: redis-cart
template:
metadata:
labels:
app: redis-cart
spec:
containers:
- name: redis
image: redis:alpine
ports:
- containerPort: 6379
readinessProbe:
periodSeconds: 5
tcpSocket:
port: 6379
livenessProbe:
periodSeconds: 5
tcpSocket:
port: 6379
volumeMounts:
- mountPath: /data
name: redis-data
resources:
limits:
memory: 256Mi
cpu: 125m
requests:
cpu: 70m
memory: 200Mi
volumes:
- name: redis-data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: redis-cart
spec:
type: ClusterIP
selector:
app: redis-cart
ports:
- name: redis
port: 6379
targetPort: 6379

View File

@@ -0,0 +1,71 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apps/v1
kind: Deployment
metadata:
name: shippingservice
spec:
selector:
matchLabels:
app: shippingservice
template:
metadata:
labels:
app: shippingservice
spec:
serviceAccountName: default
containers:
- name: server
image: shippingservice
ports:
- containerPort: 50051
env:
- name: PORT
value: "50051"
# - name: DISABLE_STATS
# value: "1"
# - name: DISABLE_TRACING
# value: "1"
# - name: DISABLE_PROFILER
# value: "1"
# - name: JAEGER_SERVICE_ADDR
# value: "jaeger-collector:14268"
readinessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051"]
livenessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051"]
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: shippingservice
spec:
type: ClusterIP
selector:
app: shippingservice
ports:
- name: grpc
port: 50051
targetPort: 50051

18
go.mod
View File

@@ -3,7 +3,7 @@ module kube-escape
go 1.16
require (
github.com/aws/aws-sdk-go v1.40.20
github.com/aws/aws-sdk-go v1.40.30
github.com/briandowns/spinner v1.16.0
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/docker/docker v20.10.8+incompatible
@@ -12,23 +12,19 @@ require (
github.com/enescakir/emoji v1.0.0
github.com/fatih/color v1.12.0
github.com/francoispqt/gojay v1.2.13
github.com/fsnotify/fsnotify v1.5.0 // indirect
github.com/gofrs/uuid v4.0.0+incompatible
github.com/golang/glog v0.0.0-20210429001901-424d2337a529
github.com/golang/glog v1.0.0
github.com/mattn/go-isatty v0.0.13
github.com/olekukonko/tablewriter v0.0.5
github.com/open-policy-agent/opa v0.31.0
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/pquerna/cachecontrol v0.1.0 // indirect
github.com/satori/go.uuid v1.2.0
github.com/spf13/cobra v1.2.1
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
k8s.io/api v0.22.0
k8s.io/apimachinery v0.22.0
k8s.io/client-go v0.22.0
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.22.1
k8s.io/apimachinery v0.22.1
k8s.io/client-go v0.22.1
sigs.k8s.io/controller-runtime v0.9.6
)

View File

@@ -56,7 +56,6 @@ func (opap *OPAProcessor) ProcessRulesListenner() {
}
func (opap *OPAProcessor) ProcessRulesHandler(opaSessionObj *cautils.OPASessionObj) error {
glog.Infof(fmt.Sprintf("Starting 'ProcessRulesHandler'. reportID: %s", opaSessionObj.PostureReport.ReportID))
cautils.ProgressTextDisplay(fmt.Sprintf("Scanning cluster %s", cautils.ClusterName))
cautils.StartSpinner()
frameworkReports := []opapolicy.FrameworkReport{}
@@ -99,7 +98,6 @@ func (opap *OPAProcessor) ProcessRulesHandler(opaSessionObj *cautils.OPASessionO
opaSessionObj.PostureReport.FrameworkReports = frameworkReports
opaSessionObj.PostureReport.ReportGenerationTime = time.Now().UTC()
glog.Infof(fmt.Sprintf("Done 'ProcessRulesHandler'. reportID: %s", opaSessionObj.PostureReport.ReportID))
cautils.StopSpinner()
cautils.SuccessTextDisplay(fmt.Sprintf("Done scanning cluster %s", cautils.ClusterName))
return errs

View File

@@ -22,7 +22,7 @@ func getKubernetesObjects(k8sResources *cautils.K8SResources, match []opapolicy.
for _, groupResource := range groupResources {
if k8sObj, ok := (*k8sResources)[groupResource]; ok {
if k8sObj == nil {
glog.Errorf("Resource '%s' is nil, probably failed to pull the resource", groupResource)
// glog.Errorf("Resource '%s' is nil, probably failed to pull the resource", groupResource)
} else if v, k := k8sObj.([]map[string]interface{}); k {
k8sObjects = append(k8sObjects, v...)
} else if v, k := k8sObj.(map[string]interface{}); k {

View File

@@ -0,0 +1,185 @@
package policyhandler
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"kube-escape/cautils"
"kube-escape/cautils/k8sinterface"
"kube-escape/cautils/opapolicy"
"path/filepath"
"gopkg.in/yaml.v2"
)
var (
YAML_PREFIX = []string{".yaml", ".yml"}
JSON_PREFIX = []string{".json"}
)
func (policyHandler *PolicyHandler) loadResources(frameworks []opapolicy.Framework, scanInfo *opapolicy.ScanInfo) (*cautils.K8SResources, error) {
files, errs := listFiles(scanInfo.InputPatterns)
if len(errs) > 0 {
cautils.ErrorDisplay(fmt.Sprintf("%v", errs)) // TODO - print error
}
if len(files) == 0 {
return nil, fmt.Errorf("empty list of files - no files found")
}
workloads, errs := loadFiles(files)
if len(errs) > 0 {
cautils.ErrorDisplay(fmt.Sprintf("%v", errs)) // TODO - print error
}
if len(workloads) == 0 {
return nil, fmt.Errorf("empty list of workloads - no workloads valid workloads found")
}
allResources := mapResources(workloads)
// build resources map
k8sResources := setResourceMap(frameworks)
// save only relevant resources
for i := range allResources {
if _, ok := (*k8sResources)[i]; ok {
(*k8sResources)[i] = allResources[i]
}
}
return k8sResources, nil
}
func mapResources(workloads []k8sinterface.IWorkload) map[string][]map[string]interface{} {
allResources := map[string][]map[string]interface{}{}
for i := range workloads {
groupVersionResource, err := k8sinterface.GetGroupVersionResource(workloads[i].GetKind())
if err != nil {
// TODO - print warning
continue
}
if groupVersionResource.Group != workloads[i].GetGroup() || groupVersionResource.Version != workloads[i].GetVersion() {
// TODO - print warning
continue
}
resourceTriplets := k8sinterface.JoinResourceTriplets(groupVersionResource.Group, groupVersionResource.Version, groupVersionResource.Resource)
if r, ok := allResources[resourceTriplets]; ok {
r = append(r, workloads[i].GetWorkload())
allResources[resourceTriplets] = r
} else {
allResources[resourceTriplets] = []map[string]interface{}{workloads[i].GetWorkload()}
}
}
return allResources
}
// // build resources map
func loadFiles(filePaths []string) ([]k8sinterface.IWorkload, []error) {
workloads := []k8sinterface.IWorkload{}
errs := []error{}
for i := range filePaths {
w, e := loadFile(filePaths[i])
errs = append(errs, e...)
workloads = append(workloads, w...)
}
return workloads, errs
}
func loadFile(filePath string) ([]k8sinterface.IWorkload, []error) {
if isYaml(filePath) {
return loadYamlFile(filePath)
} else if isJson(filePath) {
return loadJsonFile(filePath)
}
return nil, []error{fmt.Errorf("file extension %s not supported, file name: %s", filepath.Ext(filePath), filePath)}
}
func listFiles(patterns []string) ([]string, []error) {
files := []string{}
errs := []error{}
for i := range patterns {
f, err := filepath.Glob(patterns[i])
if err != nil {
errs = append(errs, err)
} else {
files = append(files, f...)
}
}
return files, errs
}
func loadYamlFile(filePath string) ([]k8sinterface.IWorkload, []error) {
errs := []error{}
yamlFile, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, []error{err}
}
r := bytes.NewReader(yamlFile)
dec := yaml.NewDecoder(r)
yamlObjs := []k8sinterface.IWorkload{}
var t interface{}
for dec.Decode(&t) == nil {
j := convertYamlToJson(t)
if obj, ok := j.(map[string]interface{}); ok {
yamlObjs = append(yamlObjs, k8sinterface.NewWorkloadObj(obj))
} else {
errs = append(errs, fmt.Errorf("failed to convert yaml file %s file to map[string]interface", filePath))
}
}
return yamlObjs, errs
}
func loadJsonFile(filePath string) ([]k8sinterface.IWorkload, []error) {
workloads := []k8sinterface.IWorkload{}
jsonFile, err := ioutil.ReadFile(filePath)
if err != nil {
return workloads, []error{err}
}
var jsonObj interface{}
if err = json.Unmarshal(jsonFile, &jsonObj); err != nil {
return workloads, []error{err}
}
convertJsonToWorkload(jsonObj, &workloads)
return workloads, nil
}
func convertJsonToWorkload(jsonObj interface{}, workloads *[]k8sinterface.IWorkload) {
switch x := jsonObj.(type) {
case map[string]interface{}:
(*workloads) = append(*workloads, k8sinterface.NewWorkloadObj(x))
case []interface{}:
for i := range x {
convertJsonToWorkload(x[i], workloads)
}
}
}
func convertYamlToJson(i interface{}) interface{} {
switch x := i.(type) {
case map[interface{}]interface{}:
m2 := map[string]interface{}{}
for k, v := range x {
m2[k.(string)] = convertYamlToJson(v)
}
return m2
case []interface{}:
for i, v := range x {
x[i] = convertYamlToJson(v)
}
}
return i
}
func isYaml(filePath string) bool {
return cautils.StringInSlice(YAML_PREFIX, filepath.Ext(filePath)) != cautils.ValueNotFound
}
func isJson(filePath string) bool {
return cautils.StringInSlice(YAML_PREFIX, filepath.Ext(filePath)) != cautils.ValueNotFound
}

View File

@@ -0,0 +1,66 @@
package policyhandler
import (
"fmt"
"kube-escape/cautils"
"os"
"path/filepath"
"strings"
"testing"
)
func combine(base, rel string) string {
finalPath := []string{}
sBase := strings.Split(base, "/")
sRel := strings.Split(rel, "/")
for i := range sBase {
if cautils.StringInSlice(sRel, sBase[i]) != cautils.ValueNotFound {
finalPath = append(finalPath, sRel...)
break
}
finalPath = append(finalPath, sBase[i])
}
return fmt.Sprintf("/%s", filepath.Join(finalPath...))
}
func onlineBoutiquePath() string {
o, _ := os.Getwd()
return combine(o, "kubescape/examples/online-boutique/*")
}
func TestListFiles(t *testing.T) {
files, errs := listFiles([]string{onlineBoutiquePath()})
if len(errs) > 0 {
t.Error(errs)
}
expected := 14
if len(files) != expected {
t.Errorf("wrong number of files, expected: %d, found: %d", expected, len(files))
}
}
func TestLoadFiles(t *testing.T) {
files, _ := listFiles([]string{onlineBoutiquePath()})
loadFiles(files)
}
func TestLoadFile(t *testing.T) {
files, _ := listFiles([]string{strings.Replace(onlineBoutiquePath(), "*", "bi-monitor.yaml", 1)})
bb, err := loadFile(files[0])
if len(err) > 0 {
t.Errorf("%v", err)
}
for i := range bb {
t.Errorf("%s", bb[i].ToString())
}
}
func TestLoadResources(t *testing.T) {
// k8sResources, err = policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo)
// files, _ := listFiles([]string{onlineBoutiquePath()})
// bb, err := loadFile(files[0])
// if len(err) > 0 {
// t.Errorf("%v", err)
// }
// for i := range bb {
// t.Errorf("%s", bb[i].ToString())
// }
}

View File

@@ -7,8 +7,6 @@ import (
"kube-escape/cautils/k8sinterface"
"kube-escape/cautils/opapolicy"
"github.com/golang/glog"
)
// PolicyHandler -
@@ -26,38 +24,27 @@ func NewPolicyHandler(processPolicy *chan *cautils.OPASessionObj, k8s *k8sinterf
}
}
func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *opapolicy.PolicyNotification, excludedNamespaces string) error {
glog.Infof("Processing notification. reportID: %s", notification.ReportID)
func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *opapolicy.PolicyNotification, scanInfo *opapolicy.ScanInfo) error {
opaSessionObj := cautils.NewOPASessionObj(nil, nil)
// validate notification
// TODO
// get policies
glog.Infof(fmt.Sprintf("Getting %d policies from backend. reportID: %s", len(notification.Rules), notification.ReportID))
cautils.ProgressTextDisplay("Downloading framework definitions")
frameworks, err := policyHandler.GetPoliciesFromBackend(notification)
frameworks, err := policyHandler.getPolicies(notification)
if err != nil {
return err
}
if len(frameworks) == 0 {
err := fmt.Errorf("Could not download any policies, please check previous logs")
return err
return fmt.Errorf("empty list of frameworks")
}
opaSessionObj.Frameworks = frameworks
cautils.SuccessTextDisplay("Downloaded framework")
// store policies as configmaps
// TODO
// get k8s resources
cautils.ProgressTextDisplay("Accessing Kubernetes objects")
glog.Infof(fmt.Sprintf("Getting kubernetes objects. reportID: %s", notification.ReportID))
k8sResources, err := policyHandler.getK8sResources(frameworks, &notification.Designators, excludedNamespaces)
if err != nil || len(*k8sResources) == 0 {
glog.Error(err)
} else {
cautils.SuccessTextDisplay("Accessed successfully to Kubernetes objects, lets start!!!")
k8sResources, err := policyHandler.getResources(notification, opaSessionObj, scanInfo)
if err != nil {
return err
}
if k8sResources == nil || len(*k8sResources) == 0 {
return fmt.Errorf("empty list of resources")
}
opaSessionObj.K8SResources = k8sResources
@@ -65,3 +52,35 @@ func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *opap
*policyHandler.processPolicy <- opaSessionObj
return nil
}
func (policyHandler *PolicyHandler) getPolicies(notification *opapolicy.PolicyNotification) ([]opapolicy.Framework, error) {
cautils.ProgressTextDisplay("Downloading framework definitions")
// TODO - support load policies from local file
frameworks, err := policyHandler.GetPoliciesFromBackend(notification)
if err != nil {
return frameworks, err
}
if len(frameworks) == 0 {
err := fmt.Errorf("could not download any policies, please check previous logs")
return frameworks, err
}
cautils.SuccessTextDisplay("Downloaded framework")
return frameworks, nil
}
func (policyHandler *PolicyHandler) getResources(notification *opapolicy.PolicyNotification, opaSessionObj *cautils.OPASessionObj, scanInfo *opapolicy.ScanInfo) (*cautils.K8SResources, error) {
var k8sResources *cautils.K8SResources
var err error
if len(scanInfo.InputPatterns) > 0 {
k8sResources, err = policyHandler.loadResources(opaSessionObj.Frameworks, scanInfo)
} else {
k8sResources, err = policyHandler.getK8sResources(opaSessionObj.Frameworks, &notification.Designators, scanInfo.ExcludedNamespaces)
}
return k8sResources, err
}

View File

@@ -20,6 +20,9 @@ import (
const SelectAllResources = "*"
func (policyHandler *PolicyHandler) getK8sResources(frameworks []opapolicy.Framework, designator *armotypes.PortalDesignator, excludedNamespaces string) (*cautils.K8SResources, error) {
// get k8s resources
cautils.ProgressTextDisplay("Accessing Kubernetes objects")
// build resources map
k8sResourcesMap := setResourceMap(frameworks)
@@ -31,6 +34,7 @@ func (policyHandler *PolicyHandler) getK8sResources(frameworks []opapolicy.Frame
return k8sResourcesMap, err
}
cautils.SuccessTextDisplay("Accessed successfully to Kubernetes objects, lets start!!!")
return k8sResourcesMap, nil
}
@@ -66,7 +70,7 @@ func (policyHandler *PolicyHandler) pullSingleResource(resource *schema.GroupVer
listOptions.FieldSelector += "metadata.namespace!=" + excludedNamespace + ","
}
}
if labels != nil && len(labels) > 0 {
if len(labels) > 0 {
set := k8slabels.Set(labels)
listOptions.LabelSelector = set.AsSelector().String()
}

View File

@@ -33,7 +33,7 @@ type Printer struct {
func NewPrinter(opaSessionObj *chan *cautils.OPASessionObj, printerType string) *Printer {
return &Printer{
opaSessionObj: opaSessionObj,
summary: NewSummery(),
summary: NewSummary(),
printerType: printerType,
}
}
@@ -42,9 +42,8 @@ func (printer *Printer) ActionPrint() {
for {
opaSessionObj := <-*printer.opaSessionObj
if printer.printerType == PrettyPrinter {
printer.SummerySetup(opaSessionObj.PostureReport)
printer.SummarySetup(opaSessionObj.PostureReport)
printer.PrintResults()
printer.PrintSummaryTable()
} else if printer.printerType == JsonPrinter {
@@ -66,7 +65,7 @@ func (printer *Printer) ActionPrint() {
os.Exit(1)
}
os.Stdout.Write(postureReportStr)
} else {
} else if !cautils.IsSilent() {
fmt.Println("unknown output printer")
os.Exit(1)
}
@@ -77,19 +76,19 @@ func (printer *Printer) ActionPrint() {
}
}
func (printer *Printer) SummerySetup(postureReport *opapolicy.PostureReport) {
func (printer *Printer) SummarySetup(postureReport *opapolicy.PostureReport) {
for _, fr := range postureReport.FrameworkReports {
for _, cr := range fr.ControlReports {
if len(cr.RuleReports) == 0 {
continue
}
workloadsSummery := listResultSummery(cr.RuleReports)
mapResources := groupByNamespace(workloadsSummery)
workloadsSummary := listResultSummary(cr.RuleReports)
mapResources := groupByNamespace(workloadsSummary)
printer.summary[cr.Name] = ControlSummery{
printer.summary[cr.Name] = ControlSummary{
TotalResources: cr.GetNumberOfResources(),
TotalFailed: len(workloadsSummery),
WorkloadSummery: mapResources,
TotalFailed: len(workloadsSummary),
WorkloadSummary: mapResources,
Description: cr.Description,
Remediation: cr.Remediation,
}
@@ -101,46 +100,46 @@ func (printer *Printer) SummerySetup(postureReport *opapolicy.PostureReport) {
func (printer *Printer) PrintResults() {
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummery := printer.summary[printer.sortedControlNames[i]]
printer.printTitle(printer.sortedControlNames[i], &controlSummery)
printer.printResult(printer.sortedControlNames[i], &controlSummery)
controlSummary := printer.summary[printer.sortedControlNames[i]]
printer.printTitle(printer.sortedControlNames[i], &controlSummary)
printer.printResult(printer.sortedControlNames[i], &controlSummary)
if printer.summary[printer.sortedControlNames[i]].TotalResources > 0 {
printer.printSummery(printer.sortedControlNames[i], &controlSummery)
printer.printSummary(printer.sortedControlNames[i], &controlSummary)
}
}
}
func (print *Printer) printSummery(controlName string, controlSummery *ControlSummery) {
func (print *Printer) printSummary(controlName string, controlSummary *ControlSummary) {
cautils.SimpleDisplay(os.Stdout, "Summary - ")
cautils.SuccessDisplay(os.Stdout, "Passed:%v ", controlSummery.TotalResources-controlSummery.TotalFailed)
cautils.FailureDisplay(os.Stdout, "Failed:%v ", controlSummery.TotalFailed)
cautils.InfoDisplay(os.Stdout, "Total:%v\n", controlSummery.TotalResources)
if controlSummery.TotalFailed > 0 {
cautils.DescriptionDisplay(os.Stdout, "Remediation: %v\n", controlSummery.Remediation)
cautils.SuccessDisplay(os.Stdout, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed)
cautils.FailureDisplay(os.Stdout, "Failed:%v ", controlSummary.TotalFailed)
cautils.InfoDisplay(os.Stdout, "Total:%v\n", controlSummary.TotalResources)
if controlSummary.TotalFailed > 0 {
cautils.DescriptionDisplay(os.Stdout, "Remediation: %v\n", controlSummary.Remediation)
}
cautils.DescriptionDisplay(os.Stdout, "\n")
}
func (printer *Printer) printTitle(controlName string, controlSummery *ControlSummery) {
func (printer *Printer) printTitle(controlName string, controlSummary *ControlSummary) {
cautils.InfoDisplay(os.Stdout, "[control: %s] ", controlName)
if controlSummery.TotalResources == 0 {
if controlSummary.TotalResources == 0 {
cautils.InfoDisplay(os.Stdout, "resources not found %v\n", emoji.ConfusedFace)
} else if controlSummery.TotalFailed == 0 {
} else if controlSummary.TotalFailed == 0 {
cautils.SuccessDisplay(os.Stdout, "passed %v\n", emoji.ThumbsUp)
} else {
cautils.FailureDisplay(os.Stdout, "failed %v\n", emoji.SadButRelievedFace)
}
cautils.DescriptionDisplay(os.Stdout, "Description: %s\n", controlSummery.Description)
cautils.DescriptionDisplay(os.Stdout, "Description: %s\n", controlSummary.Description)
}
func (printer *Printer) printResult(controlName string, controlSummery *ControlSummery) {
func (printer *Printer) printResult(controlName string, controlSummary *ControlSummary) {
indent := INDENT
for ns, rsc := range controlSummery.WorkloadSummery {
for ns, rsc := range controlSummary.WorkloadSummary {
preIndent := indent
if ns != "" {
cautils.SimpleDisplay(os.Stdout, "%sNamespace %s\n", indent, ns)
@@ -156,7 +155,7 @@ func (printer *Printer) printResult(controlName string, controlSummery *ControlS
}
func generateRow(control string, cs ControlSummery) []string {
func generateRow(control string, cs ControlSummary) []string {
row := []string{control}
row = append(row, cs.ToSlice()...)
row = append(row, fmt.Sprintf("%d%s", percentage(cs.TotalResources, cs.TotalFailed), "%"))
@@ -195,10 +194,10 @@ func (printer *Printer) PrintSummaryTable() {
sumFailed := 0
for i := 0; i < len(printer.sortedControlNames); i++ {
controlSummery := printer.summary[printer.sortedControlNames[i]]
summaryTable.Append(generateRow(printer.sortedControlNames[i], controlSummery))
sumFailed += controlSummery.TotalFailed
sumTotal += controlSummery.TotalResources
controlSummary := printer.summary[printer.sortedControlNames[i]]
summaryTable.Append(generateRow(printer.sortedControlNames[i], controlSummary))
sumFailed += controlSummary.TotalFailed
sumTotal += controlSummary.TotalResources
}
summaryTable.SetFooter(generateFooter(len(printer.summary), sumFailed, sumTotal))
summaryTable.Render()

View File

@@ -4,34 +4,34 @@ import (
"fmt"
)
type Summary map[string]ControlSummery
type Summary map[string]ControlSummary
func NewSummery() Summary {
return make(map[string]ControlSummery)
func NewSummary() Summary {
return make(map[string]ControlSummary)
}
type ControlSummery struct {
type ControlSummary struct {
TotalResources int
TotalFailed int
Description string
Remediation string
WorkloadSummery map[string][]WorkloadSummery // <namespace>:[<WorkloadSummery>]
WorkloadSummary map[string][]WorkloadSummary // <namespace>:[<WorkloadSummary>]
}
type WorkloadSummery struct {
type WorkloadSummary struct {
Kind string
Name string
Namespace string
Group string
}
func (controlSummery *ControlSummery) ToSlice() []string {
func (controlSummary *ControlSummary) ToSlice() []string {
s := []string{}
s = append(s, fmt.Sprintf("%d", controlSummery.TotalFailed))
s = append(s, fmt.Sprintf("%d", controlSummery.TotalResources))
s = append(s, fmt.Sprintf("%d", controlSummary.TotalFailed))
s = append(s, fmt.Sprintf("%d", controlSummary.TotalResources))
return s
}
func (workloadSummery *WorkloadSummery) ToString() string {
return fmt.Sprintf("/%s/%s/%s/%s", workloadSummery.Group, workloadSummery.Namespace, workloadSummery.Kind, workloadSummery.Name)
func (workloadSummary *WorkloadSummary) ToString() string {
return fmt.Sprintf("/%s/%s/%s/%s", workloadSummary.Group, workloadSummary.Namespace, workloadSummary.Kind, workloadSummary.Name)
}

View File

@@ -7,26 +7,26 @@ import (
"kube-escape/cautils/opapolicy"
)
// Group workloads by namespace - return {"namespace": <[]WorkloadSummery>}
func groupByNamespace(resources []WorkloadSummery) map[string][]WorkloadSummery {
mapResources := make(map[string][]WorkloadSummery)
// Group workloads by namespace - return {"namespace": <[]WorkloadSummary>}
func groupByNamespace(resources []WorkloadSummary) map[string][]WorkloadSummary {
mapResources := make(map[string][]WorkloadSummary)
for i := range resources {
if r, ok := mapResources[resources[i].Namespace]; ok {
r = append(r, resources[i])
mapResources[resources[i].Namespace] = r
} else {
mapResources[resources[i].Namespace] = []WorkloadSummery{resources[i]}
mapResources[resources[i].Namespace] = []WorkloadSummary{resources[i]}
}
}
return mapResources
}
func listResultSummery(ruleReports []opapolicy.RuleReport) []WorkloadSummery {
workloadsSummery := []WorkloadSummery{}
func listResultSummary(ruleReports []opapolicy.RuleReport) []WorkloadSummary {
workloadsSummary := []WorkloadSummary{}
track := map[string]bool{}
for c := range ruleReports {
for _, ruleReport := range ruleReports[c].RuleResponses {
resource, err := ruleResultSummery(ruleReport.AlertObject)
resource, err := ruleResultSummary(ruleReport.AlertObject)
if err != nil {
fmt.Println(err.Error())
continue
@@ -36,18 +36,18 @@ func listResultSummery(ruleReports []opapolicy.RuleReport) []WorkloadSummery {
for i := range resource {
if ok := track[resource[i].ToString()]; !ok {
track[resource[i].ToString()] = true
workloadsSummery = append(workloadsSummery, resource[i])
workloadsSummary = append(workloadsSummary, resource[i])
}
}
}
}
return workloadsSummery
return workloadsSummary
}
func ruleResultSummery(obj opapolicy.AlertObject) ([]WorkloadSummery, error) {
resource := []WorkloadSummery{}
func ruleResultSummary(obj opapolicy.AlertObject) ([]WorkloadSummary, error) {
resource := []WorkloadSummary{}
for i := range obj.K8SApiObjects {
r, err := newWorkloadSummery(obj.K8SApiObjects[i])
r, err := newWorkloadSummary(obj.K8SApiObjects[i])
if err != nil {
return resource, err
}
@@ -57,8 +57,8 @@ func ruleResultSummery(obj opapolicy.AlertObject) ([]WorkloadSummery, error) {
return resource, nil
}
func newWorkloadSummery(obj map[string]interface{}) (*WorkloadSummery, error) {
r := &WorkloadSummery{}
func newWorkloadSummary(obj map[string]interface{}) (*WorkloadSummary, error) {
r := &WorkloadSummary{}
workload := k8sinterface.NewWorkloadObj(obj)
if workload == nil {